about summary refs log tree commit diff
path: root/tvix/store/protos
diff options
context:
space:
mode:
authorFlorian Klink <flokli@flokli.de>2023-09-21T19·32+0300
committerclbot <clbot@tvl.fyi>2023-09-22T12·51+0000
commit32f41458c0a0f62bf906021ef096c465ccc45581 (patch)
tree3aaab8c453871f39c46fb43f8278aa933b24519d /tvix/store/protos
parentd8ef0cfb4a859af7e33828b013356412d02532da (diff)
refactor(tvix): move castore into tvix-castore crate r/6629
This splits the pure content-addressed layers from tvix-store into a
`castore` crate, and only leaves PathInfo related things, as well as the
CLI entrypoint in the tvix-store crate.

Notable changes:
 - `fixtures` and `utils` had to be moved out of the `test` cfg, so they
   can be imported from tvix-store.
 - Some ad-hoc fixtures in the test were moved to proper fixtures in the
   same step.
 - The protos are now created by a (more static) recipe in the protos/
   directory.

The (now two) golang targets are commented out, as it's not possible to
update them properly in the same CL. This will be done by a followup CL
once this is merged (and whitby deployed)

Bug: https://b.tvl.fyi/issues/301

Change-Id: I8d675d4bf1fb697eb7d479747c1b1e3635718107
Reviewed-on: https://cl.tvl.fyi/c/depot/+/9370
Reviewed-by: tazjin <tazjin@tvl.su>
Reviewed-by: flokli <flokli@flokli.de>
Autosubmit: flokli <flokli@flokli.de>
Tested-by: BuildkiteCI
Reviewed-by: Connor Brewster <cbrewster@hey.com>
Diffstat (limited to 'tvix/store/protos')
-rw-r--r--tvix/store/protos/castore.go164
-rw-r--r--tvix/store/protos/castore.pb.go450
-rw-r--r--tvix/store/protos/castore.proto62
-rw-r--r--tvix/store/protos/castore_test.go271
-rw-r--r--tvix/store/protos/default.nix16
-rw-r--r--tvix/store/protos/pathinfo.pb.go233
-rw-r--r--tvix/store/protos/pathinfo.proto12
-rw-r--r--tvix/store/protos/rpc_blobstore.pb.go412
-rw-r--r--tvix/store/protos/rpc_blobstore.proto52
-rw-r--r--tvix/store/protos/rpc_blobstore_grpc.pb.go274
-rw-r--r--tvix/store/protos/rpc_directory.pb.go271
-rw-r--r--tvix/store/protos/rpc_directory.proto48
-rw-r--r--tvix/store/protos/rpc_directory_grpc.pb.go238
-rw-r--r--tvix/store/protos/rpc_pathinfo.pb.go74
-rw-r--r--tvix/store/protos/rpc_pathinfo.proto3
-rw-r--r--tvix/store/protos/rpc_pathinfo_grpc.pb.go13
16 files changed, 102 insertions, 2491 deletions
diff --git a/tvix/store/protos/castore.go b/tvix/store/protos/castore.go
deleted file mode 100644
index 4ab7ab42887a..000000000000
--- a/tvix/store/protos/castore.go
+++ /dev/null
@@ -1,164 +0,0 @@
-package storev1
-
-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() uint32 {
-	var size uint32
-	size = uint32(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 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 the name for validity, check a potential digest for length,
-	// then check for sorting in the current list, and uniqueness across all three lists.
-	for _, directoryNode := range d.Directories {
-		directoryName := directoryNode.GetName()
-
-		// check name for validity
-		if !isValidName(directoryName) {
-			return fmt.Errorf("invalid name for DirectoryNode: %v", directoryName)
-		}
-
-		// check digest to be 32 bytes
-		digestLen := len(directoryNode.GetDigest())
-		if digestLen != 32 {
-			return fmt.Errorf("invalid digest length for DirectoryNode: %d", digestLen)
-		}
-
-		// 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()
-
-		// check name for validity
-		if !isValidName(fileName) {
-			return fmt.Errorf("invalid name for FileNode: %v", fileName)
-		}
-
-		// check digest to be 32 bytes
-		digestLen := len(fileNode.GetDigest())
-		if digestLen != 32 {
-			return fmt.Errorf("invalid digest length for FileNode: %d", digestLen)
-		}
-
-		// 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()
-
-		// check name for validity
-		if !isValidName(symlinkName) {
-			return fmt.Errorf("invalid name for SymlinkNode: %v", symlinkName)
-		}
-
-		// 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/store/protos/castore.pb.go b/tvix/store/protos/castore.pb.go
deleted file mode 100644
index 074b39d548e3..000000000000
--- a/tvix/store/protos/castore.pb.go
+++ /dev/null
@@ -1,450 +0,0 @@
-// 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.31.0
-// 	protoc        (unknown)
-// source: tvix/store/protos/castore.proto
-
-package storev1
-
-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_store_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_store_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_store_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 uint32 `protobuf:"varint,3,opt,name=size,proto3" json:"size,omitempty"`
-}
-
-func (x *DirectoryNode) Reset() {
-	*x = DirectoryNode{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_tvix_store_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_store_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_store_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() uint32 {
-	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 uint32 `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_store_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_store_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_store_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() uint32 {
-	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_store_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_store_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_store_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
-}
-
-var File_tvix_store_protos_castore_proto protoreflect.FileDescriptor
-
-var file_tvix_store_protos_castore_proto_rawDesc = []byte{
-	0x0a, 0x1f, 0x74, 0x76, 0x69, 0x78, 0x2f, 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, 0x0d, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31,
-	0x22, 0xb2, 0x01, 0x0a, 0x09, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x3e,
-	0x0a, 0x0b, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20,
-	0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 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, 0x2d,
-	0x0a, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e,
-	0x74, 0x76, 0x69, 0x78, 0x2e, 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, 0x36, 0x0a,
-	0x08, 0x73, 0x79, 0x6d, 0x6c, 0x69, 0x6e, 0x6b, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32,
-	0x1a, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 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, 0x0d,
-	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, 0x0d, 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, 0x42, 0x28, 0x5a,
-	0x26, 0x63, 0x6f, 0x64, 0x65, 0x2e, 0x74, 0x76, 0x6c, 0x2e, 0x66, 0x79, 0x69, 0x2f, 0x74, 0x76,
-	0x69, 0x78, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x3b,
-	0x73, 0x74, 0x6f, 0x72, 0x65, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
-}
-
-var (
-	file_tvix_store_protos_castore_proto_rawDescOnce sync.Once
-	file_tvix_store_protos_castore_proto_rawDescData = file_tvix_store_protos_castore_proto_rawDesc
-)
-
-func file_tvix_store_protos_castore_proto_rawDescGZIP() []byte {
-	file_tvix_store_protos_castore_proto_rawDescOnce.Do(func() {
-		file_tvix_store_protos_castore_proto_rawDescData = protoimpl.X.CompressGZIP(file_tvix_store_protos_castore_proto_rawDescData)
-	})
-	return file_tvix_store_protos_castore_proto_rawDescData
-}
-
-var file_tvix_store_protos_castore_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
-var file_tvix_store_protos_castore_proto_goTypes = []interface{}{
-	(*Directory)(nil),     // 0: tvix.store.v1.Directory
-	(*DirectoryNode)(nil), // 1: tvix.store.v1.DirectoryNode
-	(*FileNode)(nil),      // 2: tvix.store.v1.FileNode
-	(*SymlinkNode)(nil),   // 3: tvix.store.v1.SymlinkNode
-}
-var file_tvix_store_protos_castore_proto_depIdxs = []int32{
-	1, // 0: tvix.store.v1.Directory.directories:type_name -> tvix.store.v1.DirectoryNode
-	2, // 1: tvix.store.v1.Directory.files:type_name -> tvix.store.v1.FileNode
-	3, // 2: tvix.store.v1.Directory.symlinks:type_name -> tvix.store.v1.SymlinkNode
-	3, // [3:3] is the sub-list for method output_type
-	3, // [3:3] is the sub-list for method input_type
-	3, // [3:3] is the sub-list for extension type_name
-	3, // [3:3] is the sub-list for extension extendee
-	0, // [0:3] is the sub-list for field type_name
-}
-
-func init() { file_tvix_store_protos_castore_proto_init() }
-func file_tvix_store_protos_castore_proto_init() {
-	if File_tvix_store_protos_castore_proto != nil {
-		return
-	}
-	if !protoimpl.UnsafeEnabled {
-		file_tvix_store_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_store_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_store_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_store_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
-			}
-		}
-	}
-	type x struct{}
-	out := protoimpl.TypeBuilder{
-		File: protoimpl.DescBuilder{
-			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
-			RawDescriptor: file_tvix_store_protos_castore_proto_rawDesc,
-			NumEnums:      0,
-			NumMessages:   4,
-			NumExtensions: 0,
-			NumServices:   0,
-		},
-		GoTypes:           file_tvix_store_protos_castore_proto_goTypes,
-		DependencyIndexes: file_tvix_store_protos_castore_proto_depIdxs,
-		MessageInfos:      file_tvix_store_protos_castore_proto_msgTypes,
-	}.Build()
-	File_tvix_store_protos_castore_proto = out.File
-	file_tvix_store_protos_castore_proto_rawDesc = nil
-	file_tvix_store_protos_castore_proto_goTypes = nil
-	file_tvix_store_protos_castore_proto_depIdxs = nil
-}
diff --git a/tvix/store/protos/castore.proto b/tvix/store/protos/castore.proto
deleted file mode 100644
index 347815107198..000000000000
--- a/tvix/store/protos/castore.proto
+++ /dev/null
@@ -1,62 +0,0 @@
-// SPDX-FileCopyrightText: edef <edef@unfathomable.blue>
-// SPDX-License-Identifier: OSL-3.0 OR MIT OR Apache-2.0
-
-syntax = "proto3";
-
-package tvix.store.v1;
-
-option go_package = "code.tvl.fyi/tvix/store/protos;storev1";
-
-// 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.
-    uint32 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
-    uint32 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;
-}
diff --git a/tvix/store/protos/castore_test.go b/tvix/store/protos/castore_test.go
deleted file mode 100644
index 15a2554bbb57..000000000000
--- a/tvix/store/protos/castore_test.go
+++ /dev/null
@@ -1,271 +0,0 @@
-package storev1_test
-
-import (
-	"testing"
-
-	storev1pb "code.tvl.fyi/tvix/store/protos"
-	"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 := storev1pb.Directory{
-			Directories: []*storev1pb.DirectoryNode{},
-			Files:       []*storev1pb.FileNode{},
-			Symlinks:    []*storev1pb.SymlinkNode{},
-		}
-
-		assert.Equal(t, uint32(0), d.Size())
-	})
-
-	t.Run("containing single empty directory", func(t *testing.T) {
-		d := storev1pb.Directory{
-			Directories: []*storev1pb.DirectoryNode{{
-				Name:   []byte([]byte("foo")),
-				Digest: dummyDigest,
-				Size:   0,
-			}},
-			Files:    []*storev1pb.FileNode{},
-			Symlinks: []*storev1pb.SymlinkNode{},
-		}
-
-		assert.Equal(t, uint32(1), d.Size())
-	})
-
-	t.Run("containing single non-empty directory", func(t *testing.T) {
-		d := storev1pb.Directory{
-			Directories: []*storev1pb.DirectoryNode{{
-				Name:   []byte("foo"),
-				Digest: dummyDigest,
-				Size:   4,
-			}},
-			Files:    []*storev1pb.FileNode{},
-			Symlinks: []*storev1pb.SymlinkNode{},
-		}
-
-		assert.Equal(t, uint32(5), d.Size())
-	})
-
-	t.Run("containing single file", func(t *testing.T) {
-		d := storev1pb.Directory{
-			Directories: []*storev1pb.DirectoryNode{},
-			Files: []*storev1pb.FileNode{{
-				Name:       []byte("foo"),
-				Digest:     dummyDigest,
-				Size:       42,
-				Executable: false,
-			}},
-			Symlinks: []*storev1pb.SymlinkNode{},
-		}
-
-		assert.Equal(t, uint32(1), d.Size())
-	})
-
-	t.Run("containing single symlink", func(t *testing.T) {
-		d := storev1pb.Directory{
-			Directories: []*storev1pb.DirectoryNode{},
-			Files:       []*storev1pb.FileNode{},
-			Symlinks: []*storev1pb.SymlinkNode{{
-				Name:   []byte("foo"),
-				Target: []byte("bar"),
-			}},
-		}
-
-		assert.Equal(t, uint32(1), d.Size())
-	})
-
-}
-func TestDirectoryDigest(t *testing.T) {
-	d := storev1pb.Directory{
-		Directories: []*storev1pb.DirectoryNode{},
-		Files:       []*storev1pb.FileNode{},
-		Symlinks:    []*storev1pb.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 := storev1pb.Directory{
-			Directories: []*storev1pb.DirectoryNode{},
-			Files:       []*storev1pb.FileNode{},
-			Symlinks:    []*storev1pb.SymlinkNode{},
-		}
-
-		assert.NoError(t, d.Validate())
-	})
-
-	t.Run("invalid names", func(t *testing.T) {
-		{
-			d := storev1pb.Directory{
-				Directories: []*storev1pb.DirectoryNode{{
-					Name:   []byte{},
-					Digest: dummyDigest,
-					Size:   42,
-				}},
-				Files:    []*storev1pb.FileNode{},
-				Symlinks: []*storev1pb.SymlinkNode{},
-			}
-
-			assert.ErrorContains(t, d.Validate(), "invalid name")
-		}
-		{
-			d := storev1pb.Directory{
-				Directories: []*storev1pb.DirectoryNode{{
-					Name:   []byte("."),
-					Digest: dummyDigest,
-					Size:   42,
-				}},
-				Files:    []*storev1pb.FileNode{},
-				Symlinks: []*storev1pb.SymlinkNode{},
-			}
-
-			assert.ErrorContains(t, d.Validate(), "invalid name")
-		}
-		{
-			d := storev1pb.Directory{
-				Directories: []*storev1pb.DirectoryNode{},
-				Files: []*storev1pb.FileNode{{
-					Name:       []byte(".."),
-					Digest:     dummyDigest,
-					Size:       42,
-					Executable: false,
-				}},
-				Symlinks: []*storev1pb.SymlinkNode{},
-			}
-
-			assert.ErrorContains(t, d.Validate(), "invalid name")
-		}
-		{
-			d := storev1pb.Directory{
-				Directories: []*storev1pb.DirectoryNode{},
-				Files:       []*storev1pb.FileNode{},
-				Symlinks: []*storev1pb.SymlinkNode{{
-					Name:   []byte("\x00"),
-					Target: []byte("foo"),
-				}},
-			}
-
-			assert.ErrorContains(t, d.Validate(), "invalid name")
-		}
-		{
-			d := storev1pb.Directory{
-				Directories: []*storev1pb.DirectoryNode{},
-				Files:       []*storev1pb.FileNode{},
-				Symlinks: []*storev1pb.SymlinkNode{{
-					Name:   []byte("foo/bar"),
-					Target: []byte("foo"),
-				}},
-			}
-
-			assert.ErrorContains(t, d.Validate(), "invalid name")
-		}
-	})
-
-	t.Run("invalid digest", func(t *testing.T) {
-		d := storev1pb.Directory{
-			Directories: []*storev1pb.DirectoryNode{{
-				Name:   []byte("foo"),
-				Digest: nil,
-				Size:   42,
-			}},
-			Files:    []*storev1pb.FileNode{},
-			Symlinks: []*storev1pb.SymlinkNode{},
-		}
-
-		assert.ErrorContains(t, d.Validate(), "invalid digest length")
-	})
-
-	t.Run("sorting", func(t *testing.T) {
-		// "b" comes before "a", bad.
-		{
-			d := storev1pb.Directory{
-				Directories: []*storev1pb.DirectoryNode{{
-					Name:   []byte("b"),
-					Digest: dummyDigest,
-					Size:   42,
-				}, {
-					Name:   []byte("a"),
-					Digest: dummyDigest,
-					Size:   42,
-				}},
-				Files:    []*storev1pb.FileNode{},
-				Symlinks: []*storev1pb.SymlinkNode{},
-			}
-			assert.ErrorContains(t, d.Validate(), "is not in sorted order")
-		}
-
-		// "a" exists twice, bad.
-		{
-			d := storev1pb.Directory{
-				Directories: []*storev1pb.DirectoryNode{{
-					Name:   []byte("a"),
-					Digest: dummyDigest,
-					Size:   42,
-				}},
-				Files: []*storev1pb.FileNode{{
-					Name:       []byte("a"),
-					Digest:     dummyDigest,
-					Size:       42,
-					Executable: false,
-				}},
-				Symlinks: []*storev1pb.SymlinkNode{},
-			}
-			assert.ErrorContains(t, d.Validate(), "duplicate name")
-		}
-
-		// "a" comes before "b", all good.
-		{
-			d := storev1pb.Directory{
-				Directories: []*storev1pb.DirectoryNode{{
-					Name:   []byte("a"),
-					Digest: dummyDigest,
-					Size:   42,
-				}, {
-					Name:   []byte("b"),
-					Digest: dummyDigest,
-					Size:   42,
-				}},
-				Files:    []*storev1pb.FileNode{},
-				Symlinks: []*storev1pb.SymlinkNode{},
-			}
-			assert.NoError(t, d.Validate(), "shouldn't error")
-		}
-
-		// [b, c] and [a] are both properly sorted.
-		{
-			d := storev1pb.Directory{
-				Directories: []*storev1pb.DirectoryNode{{
-					Name:   []byte("b"),
-					Digest: dummyDigest,
-					Size:   42,
-				}, {
-					Name:   []byte("c"),
-					Digest: dummyDigest,
-					Size:   42,
-				}},
-				Files: []*storev1pb.FileNode{},
-				Symlinks: []*storev1pb.SymlinkNode{{
-					Name:   []byte("a"),
-					Target: []byte("foo"),
-				}},
-			}
-			assert.NoError(t, d.Validate(), "shouldn't error")
-		}
-	})
-}
diff --git a/tvix/store/protos/default.nix b/tvix/store/protos/default.nix
deleted file mode 100644
index d5c44842229d..000000000000
--- a/tvix/store/protos/default.nix
+++ /dev/null
@@ -1,16 +0,0 @@
-# Target containing just the proto files.
-
-{ depot, lib, ... }:
-
-let
-  inherit (lib.strings) hasSuffix;
-  inherit (builtins) attrNames filter readDir;
-
-  protoFileNames = filter (hasSuffix ".proto") (attrNames (readDir ./.));
-  protoFiles = map (f: ./. + ("/" + f)) protoFileNames;
-in
-depot.nix.sparseTree {
-  name = "tvix-store-protos";
-  root = depot.path.origSrc;
-  paths = protoFiles;
-}
diff --git a/tvix/store/protos/pathinfo.pb.go b/tvix/store/protos/pathinfo.pb.go
index 126fc34af27d..1e5479ac8f75 100644
--- a/tvix/store/protos/pathinfo.pb.go
+++ b/tvix/store/protos/pathinfo.pb.go
@@ -10,6 +10,7 @@
 package storev1
 
 import (
+	protos "code.tvl.fyi/tvix/castore/protos"
 	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
 	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
 	reflect "reflect"
@@ -31,7 +32,7 @@ type PathInfo struct {
 	unknownFields protoimpl.UnknownFields
 
 	// The path can be a directory, file or symlink.
-	Node *Node `protobuf:"bytes,1,opt,name=node,proto3" json:"node,omitempty"`
+	Node *protos.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.
@@ -72,7 +73,7 @@ func (*PathInfo) Descriptor() ([]byte, []int) {
 	return file_tvix_store_protos_pathinfo_proto_rawDescGZIP(), []int{0}
 }
 
-func (x *PathInfo) GetNode() *Node {
+func (x *PathInfo) GetNode() *protos.Node {
 	if x != nil {
 		return x.Node
 	}
@@ -93,101 +94,6 @@ func (x *PathInfo) GetNarinfo() *NARInfo {
 	return nil
 }
 
-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_store_protos_pathinfo_proto_msgTypes[1]
-		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_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 Node.ProtoReflect.Descriptor instead.
-func (*Node) Descriptor() ([]byte, []int) {
-	return file_tvix_store_protos_pathinfo_proto_rawDescGZIP(), []int{1}
-}
-
-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() {}
-
 // 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.
@@ -219,7 +125,7 @@ type NARInfo struct {
 func (x *NARInfo) Reset() {
 	*x = NARInfo{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_tvix_store_protos_pathinfo_proto_msgTypes[2]
+		mi := &file_tvix_store_protos_pathinfo_proto_msgTypes[1]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -232,7 +138,7 @@ func (x *NARInfo) String() string {
 func (*NARInfo) ProtoMessage() {}
 
 func (x *NARInfo) ProtoReflect() protoreflect.Message {
-	mi := &file_tvix_store_protos_pathinfo_proto_msgTypes[2]
+	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 {
@@ -245,7 +151,7 @@ func (x *NARInfo) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use NARInfo.ProtoReflect.Descriptor instead.
 func (*NARInfo) Descriptor() ([]byte, []int) {
-	return file_tvix_store_protos_pathinfo_proto_rawDescGZIP(), []int{2}
+	return file_tvix_store_protos_pathinfo_proto_rawDescGZIP(), []int{1}
 }
 
 func (x *NARInfo) GetNarSize() uint64 {
@@ -289,7 +195,7 @@ type NARInfo_Signature struct {
 func (x *NARInfo_Signature) Reset() {
 	*x = NARInfo_Signature{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_tvix_store_protos_pathinfo_proto_msgTypes[3]
+		mi := &file_tvix_store_protos_pathinfo_proto_msgTypes[2]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -302,7 +208,7 @@ func (x *NARInfo_Signature) String() string {
 func (*NARInfo_Signature) ProtoMessage() {}
 
 func (x *NARInfo_Signature) ProtoReflect() protoreflect.Message {
-	mi := &file_tvix_store_protos_pathinfo_proto_msgTypes[3]
+	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 {
@@ -315,7 +221,7 @@ func (x *NARInfo_Signature) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use NARInfo_Signature.ProtoReflect.Descriptor instead.
 func (*NARInfo_Signature) Descriptor() ([]byte, []int) {
-	return file_tvix_store_protos_pathinfo_proto_rawDescGZIP(), []int{2, 0}
+	return file_tvix_store_protos_pathinfo_proto_rawDescGZIP(), []int{1, 0}
 }
 
 func (x *NARInfo_Signature) GetName() string {
@@ -338,46 +244,35 @@ 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, 0x1f, 0x74, 0x76, 0x69, 0x78, 0x2f, 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, 0x85, 0x01, 0x0a, 0x08, 0x50, 0x61, 0x74, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x12,
-	0x27, 0x0a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e,
-	0x74, 0x76, 0x69, 0x78, 0x2e, 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, 0xb3, 0x01, 0x0a, 0x04, 0x4e,
-	0x6f, 0x64, 0x65, 0x12, 0x3c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79,
-	0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 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, 0x2d, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
-	0x17, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 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, 0x36, 0x0a, 0x07, 0x73, 0x79, 0x6d, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28,
-	0x0b, 0x32, 0x1a, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 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,
-	0x22, 0xe3, 0x01, 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, 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, 0x42, 0x28, 0x5a, 0x26, 0x63, 0x6f, 0x64, 0x65, 0x2e, 0x74,
-	0x76, 0x6c, 0x2e, 0x66, 0x79, 0x69, 0x2f, 0x74, 0x76, 0x69, 0x78, 0x2f, 0x73, 0x74, 0x6f, 0x72,
-	0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x76, 0x31,
-	0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	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, 0xe3,
+	0x01, 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, 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, 0x42, 0x28, 0x5a, 0x26, 0x63, 0x6f, 0x64, 0x65, 0x2e, 0x74, 0x76, 0x6c,
+	0x2e, 0x66, 0x79, 0x69, 0x2f, 0x74, 0x76, 0x69, 0x78, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x76, 0x31, 0x62, 0x06,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -392,28 +287,22 @@ func file_tvix_store_protos_pathinfo_proto_rawDescGZIP() []byte {
 	return file_tvix_store_protos_pathinfo_proto_rawDescData
 }
 
-var file_tvix_store_protos_pathinfo_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
+var file_tvix_store_protos_pathinfo_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
 var file_tvix_store_protos_pathinfo_proto_goTypes = []interface{}{
 	(*PathInfo)(nil),          // 0: tvix.store.v1.PathInfo
-	(*Node)(nil),              // 1: tvix.store.v1.Node
-	(*NARInfo)(nil),           // 2: tvix.store.v1.NARInfo
-	(*NARInfo_Signature)(nil), // 3: tvix.store.v1.NARInfo.Signature
-	(*DirectoryNode)(nil),     // 4: tvix.store.v1.DirectoryNode
-	(*FileNode)(nil),          // 5: tvix.store.v1.FileNode
-	(*SymlinkNode)(nil),       // 6: tvix.store.v1.SymlinkNode
+	(*NARInfo)(nil),           // 1: tvix.store.v1.NARInfo
+	(*NARInfo_Signature)(nil), // 2: tvix.store.v1.NARInfo.Signature
+	(*protos.Node)(nil),       // 3: tvix.castore.v1.Node
 }
 var file_tvix_store_protos_pathinfo_proto_depIdxs = []int32{
-	1, // 0: tvix.store.v1.PathInfo.node:type_name -> tvix.store.v1.Node
-	2, // 1: tvix.store.v1.PathInfo.narinfo:type_name -> tvix.store.v1.NARInfo
-	4, // 2: tvix.store.v1.Node.directory:type_name -> tvix.store.v1.DirectoryNode
-	5, // 3: tvix.store.v1.Node.file:type_name -> tvix.store.v1.FileNode
-	6, // 4: tvix.store.v1.Node.symlink:type_name -> tvix.store.v1.SymlinkNode
-	3, // 5: tvix.store.v1.NARInfo.signatures:type_name -> tvix.store.v1.NARInfo.Signature
-	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
+	3, // 0: tvix.store.v1.PathInfo.node:type_name -> tvix.castore.v1.Node
+	1, // 1: tvix.store.v1.PathInfo.narinfo:type_name -> tvix.store.v1.NARInfo
+	2, // 2: tvix.store.v1.NARInfo.signatures:type_name -> tvix.store.v1.NARInfo.Signature
+	3, // [3:3] is the sub-list for method output_type
+	3, // [3:3] is the sub-list for method input_type
+	3, // [3:3] is the sub-list for extension type_name
+	3, // [3:3] is the sub-list for extension extendee
+	0, // [0:3] is the sub-list for field type_name
 }
 
 func init() { file_tvix_store_protos_pathinfo_proto_init() }
@@ -421,7 +310,6 @@ func file_tvix_store_protos_pathinfo_proto_init() {
 	if File_tvix_store_protos_pathinfo_proto != nil {
 		return
 	}
-	file_tvix_store_protos_castore_proto_init()
 	if !protoimpl.UnsafeEnabled {
 		file_tvix_store_protos_pathinfo_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*PathInfo); i {
@@ -436,18 +324,6 @@ func file_tvix_store_protos_pathinfo_proto_init() {
 			}
 		}
 		file_tvix_store_protos_pathinfo_proto_msgTypes[1].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_store_protos_pathinfo_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*NARInfo); i {
 			case 0:
 				return &v.state
@@ -459,7 +335,7 @@ func file_tvix_store_protos_pathinfo_proto_init() {
 				return nil
 			}
 		}
-		file_tvix_store_protos_pathinfo_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+		file_tvix_store_protos_pathinfo_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*NARInfo_Signature); i {
 			case 0:
 				return &v.state
@@ -472,18 +348,13 @@ func file_tvix_store_protos_pathinfo_proto_init() {
 			}
 		}
 	}
-	file_tvix_store_protos_pathinfo_proto_msgTypes[1].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_store_protos_pathinfo_proto_rawDesc,
 			NumEnums:      0,
-			NumMessages:   4,
+			NumMessages:   3,
 			NumExtensions: 0,
 			NumServices:   0,
 		},
diff --git a/tvix/store/protos/pathinfo.proto b/tvix/store/protos/pathinfo.proto
index 896d4aa225ac..aa98c6df9a2d 100644
--- a/tvix/store/protos/pathinfo.proto
+++ b/tvix/store/protos/pathinfo.proto
@@ -4,7 +4,7 @@ syntax = "proto3";
 
 package tvix.store.v1;
 
-import "tvix/store/protos/castore.proto";
+import "tvix/castore/protos/castore.proto";
 
 option go_package = "code.tvl.fyi/tvix/store/protos;storev1";
 
@@ -12,7 +12,7 @@ option go_package = "code.tvl.fyi/tvix/store/protos;storev1";
 // That's a single element inside /nix/store.
 message PathInfo {
     // The path can be a directory, file or symlink.
-    Node node = 1;
+    tvix.castore.v1.Node node = 1;
 
     // List of references (output path hashes)
     // This really is the raw *bytes*, after decoding nixbase32, and not a
@@ -23,14 +23,6 @@ message PathInfo {
     NARInfo narinfo = 3;
 }
 
-message Node {
-    oneof node {
-        DirectoryNode directory = 1;
-        FileNode file = 2;
-        SymlinkNode symlink = 3;
-    }
-}
-
 // 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.
diff --git a/tvix/store/protos/rpc_blobstore.pb.go b/tvix/store/protos/rpc_blobstore.pb.go
deleted file mode 100644
index 987d75e3b997..000000000000
--- a/tvix/store/protos/rpc_blobstore.pb.go
+++ /dev/null
@@ -1,412 +0,0 @@
-// SPDX-License-Identifier: MIT
-// Copyright © 2022 The Tvix Authors
-
-// Code generated by protoc-gen-go. DO NOT EDIT.
-// versions:
-// 	protoc-gen-go v1.31.0
-// 	protoc        (unknown)
-// source: tvix/store/protos/rpc_blobstore.proto
-
-package storev1
-
-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"`
-}
-
-func (x *StatBlobRequest) Reset() {
-	*x = StatBlobRequest{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_tvix_store_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_store_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_store_protos_rpc_blobstore_proto_rawDescGZIP(), []int{0}
-}
-
-func (x *StatBlobRequest) GetDigest() []byte {
-	if x != nil {
-		return x.Digest
-	}
-	return nil
-}
-
-type BlobMeta struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-}
-
-func (x *BlobMeta) Reset() {
-	*x = BlobMeta{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_tvix_store_protos_rpc_blobstore_proto_msgTypes[1]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *BlobMeta) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*BlobMeta) ProtoMessage() {}
-
-func (x *BlobMeta) ProtoReflect() protoreflect.Message {
-	mi := &file_tvix_store_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 BlobMeta.ProtoReflect.Descriptor instead.
-func (*BlobMeta) Descriptor() ([]byte, []int) {
-	return file_tvix_store_protos_rpc_blobstore_proto_rawDescGZIP(), []int{1}
-}
-
-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_store_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_store_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_store_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_store_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_store_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_store_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_store_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_store_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_store_protos_rpc_blobstore_proto_rawDescGZIP(), []int{4}
-}
-
-func (x *PutBlobResponse) GetDigest() []byte {
-	if x != nil {
-		return x.Digest
-	}
-	return nil
-}
-
-var File_tvix_store_protos_rpc_blobstore_proto protoreflect.FileDescriptor
-
-var file_tvix_store_protos_rpc_blobstore_proto_rawDesc = []byte{
-	0x0a, 0x25, 0x74, 0x76, 0x69, 0x78, 0x2f, 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, 0x0d, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x73, 0x74,
-	0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x22, 0x29, 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, 0x22, 0x0a, 0x0a, 0x08, 0x42, 0x6c, 0x6f, 0x62, 0x4d, 0x65, 0x74, 0x61, 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, 0xd5, 0x01, 0x0a, 0x0b, 0x42, 0x6c, 0x6f, 0x62, 0x53, 0x65, 0x72,
-	0x76, 0x69, 0x63, 0x65, 0x12, 0x3f, 0x0a, 0x04, 0x53, 0x74, 0x61, 0x74, 0x12, 0x1e, 0x2e, 0x74,
-	0x76, 0x69, 0x78, 0x2e, 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, 0x17, 0x2e, 0x74,
-	0x76, 0x69, 0x78, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f,
-	0x62, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x42, 0x0a, 0x04, 0x52, 0x65, 0x61, 0x64, 0x12, 0x1e, 0x2e,
-	0x74, 0x76, 0x69, 0x78, 0x2e, 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, 0x18, 0x2e,
-	0x74, 0x76, 0x69, 0x78, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c,
-	0x6f, 0x62, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x30, 0x01, 0x12, 0x41, 0x0a, 0x03, 0x50, 0x75, 0x74,
-	0x12, 0x18, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31,
-	0x2e, 0x42, 0x6c, 0x6f, 0x62, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x1a, 0x1e, 0x2e, 0x74, 0x76, 0x69,
-	0x78, 0x2e, 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, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x3b, 0x73,
-	0x74, 0x6f, 0x72, 0x65, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
-}
-
-var (
-	file_tvix_store_protos_rpc_blobstore_proto_rawDescOnce sync.Once
-	file_tvix_store_protos_rpc_blobstore_proto_rawDescData = file_tvix_store_protos_rpc_blobstore_proto_rawDesc
-)
-
-func file_tvix_store_protos_rpc_blobstore_proto_rawDescGZIP() []byte {
-	file_tvix_store_protos_rpc_blobstore_proto_rawDescOnce.Do(func() {
-		file_tvix_store_protos_rpc_blobstore_proto_rawDescData = protoimpl.X.CompressGZIP(file_tvix_store_protos_rpc_blobstore_proto_rawDescData)
-	})
-	return file_tvix_store_protos_rpc_blobstore_proto_rawDescData
-}
-
-var file_tvix_store_protos_rpc_blobstore_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
-var file_tvix_store_protos_rpc_blobstore_proto_goTypes = []interface{}{
-	(*StatBlobRequest)(nil), // 0: tvix.store.v1.StatBlobRequest
-	(*BlobMeta)(nil),        // 1: tvix.store.v1.BlobMeta
-	(*ReadBlobRequest)(nil), // 2: tvix.store.v1.ReadBlobRequest
-	(*BlobChunk)(nil),       // 3: tvix.store.v1.BlobChunk
-	(*PutBlobResponse)(nil), // 4: tvix.store.v1.PutBlobResponse
-}
-var file_tvix_store_protos_rpc_blobstore_proto_depIdxs = []int32{
-	0, // 0: tvix.store.v1.BlobService.Stat:input_type -> tvix.store.v1.StatBlobRequest
-	2, // 1: tvix.store.v1.BlobService.Read:input_type -> tvix.store.v1.ReadBlobRequest
-	3, // 2: tvix.store.v1.BlobService.Put:input_type -> tvix.store.v1.BlobChunk
-	1, // 3: tvix.store.v1.BlobService.Stat:output_type -> tvix.store.v1.BlobMeta
-	3, // 4: tvix.store.v1.BlobService.Read:output_type -> tvix.store.v1.BlobChunk
-	4, // 5: tvix.store.v1.BlobService.Put:output_type -> tvix.store.v1.PutBlobResponse
-	3, // [3:6] is the sub-list for method output_type
-	0, // [0:3] 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_blobstore_proto_init() }
-func file_tvix_store_protos_rpc_blobstore_proto_init() {
-	if File_tvix_store_protos_rpc_blobstore_proto != nil {
-		return
-	}
-	if !protoimpl.UnsafeEnabled {
-		file_tvix_store_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_store_protos_rpc_blobstore_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*BlobMeta); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_tvix_store_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_store_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_store_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
-			}
-		}
-	}
-	type x struct{}
-	out := protoimpl.TypeBuilder{
-		File: protoimpl.DescBuilder{
-			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
-			RawDescriptor: file_tvix_store_protos_rpc_blobstore_proto_rawDesc,
-			NumEnums:      0,
-			NumMessages:   5,
-			NumExtensions: 0,
-			NumServices:   1,
-		},
-		GoTypes:           file_tvix_store_protos_rpc_blobstore_proto_goTypes,
-		DependencyIndexes: file_tvix_store_protos_rpc_blobstore_proto_depIdxs,
-		MessageInfos:      file_tvix_store_protos_rpc_blobstore_proto_msgTypes,
-	}.Build()
-	File_tvix_store_protos_rpc_blobstore_proto = out.File
-	file_tvix_store_protos_rpc_blobstore_proto_rawDesc = nil
-	file_tvix_store_protos_rpc_blobstore_proto_goTypes = nil
-	file_tvix_store_protos_rpc_blobstore_proto_depIdxs = nil
-}
diff --git a/tvix/store/protos/rpc_blobstore.proto b/tvix/store/protos/rpc_blobstore.proto
deleted file mode 100644
index 2ca3df60a81b..000000000000
--- a/tvix/store/protos/rpc_blobstore.proto
+++ /dev/null
@@ -1,52 +0,0 @@
-// SPDX-License-Identifier: MIT
-// Copyright © 2022 The Tvix Authors
-syntax = "proto3";
-
-package tvix.store.v1;
-
-option go_package = "code.tvl.fyi/tvix/store/protos;storev1";
-
-service BlobService {
-    // In the future, Stat will expose more metadata about a given blob,
-    // such as more granular chunking, baos.
-    // For now, it's only used to check for the existence of a blob, as asking
-    // this for a non-existing Blob will return a Status::not_found gRPC error.
-    rpc Stat(StatBlobRequest) returns (BlobMeta);
-
-    // Read returns a stream of BlobChunk, which is just a stream of bytes with
-    // the digest specified in ReadBlobRequest.
-    //
-    // The server may decide on whatever chunking it may seem fit as a size for
-    // the individual BlobChunk sent in the response stream.
-    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.
-    rpc Put(stream BlobChunk) returns (PutBlobResponse);
-}
-
-message StatBlobRequest {
-    // The blake3 digest of the blob requested
-    bytes digest = 1;
-}
-
-message BlobMeta {
-}
-
-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/store/protos/rpc_blobstore_grpc.pb.go b/tvix/store/protos/rpc_blobstore_grpc.pb.go
deleted file mode 100644
index 531ebcd2c637..000000000000
--- a/tvix/store/protos/rpc_blobstore_grpc.pb.go
+++ /dev/null
@@ -1,274 +0,0 @@
-// 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_blobstore.proto
-
-package storev1
-
-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.store.v1.BlobService/Stat"
-	BlobService_Read_FullMethodName = "/tvix.store.v1.BlobService/Read"
-	BlobService_Put_FullMethodName  = "/tvix.store.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 {
-	// In the future, Stat will expose more metadata about a given blob,
-	// such as more granular chunking, baos.
-	// For now, it's only used to check for the existence of a blob, as asking
-	// this for a non-existing Blob will return a Status::not_found gRPC error.
-	Stat(ctx context.Context, in *StatBlobRequest, opts ...grpc.CallOption) (*BlobMeta, error)
-	// Read returns a stream of BlobChunk, which is just a stream of bytes with
-	// the digest specified in ReadBlobRequest.
-	//
-	// The server may decide on whatever chunking it may seem fit as a size for
-	// the individual BlobChunk sent in the response stream.
-	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.
-	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) (*BlobMeta, error) {
-	out := new(BlobMeta)
-	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 {
-	// In the future, Stat will expose more metadata about a given blob,
-	// such as more granular chunking, baos.
-	// For now, it's only used to check for the existence of a blob, as asking
-	// this for a non-existing Blob will return a Status::not_found gRPC error.
-	Stat(context.Context, *StatBlobRequest) (*BlobMeta, error)
-	// Read returns a stream of BlobChunk, which is just a stream of bytes with
-	// the digest specified in ReadBlobRequest.
-	//
-	// The server may decide on whatever chunking it may seem fit as a size for
-	// the individual BlobChunk sent in the response stream.
-	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.
-	Put(BlobService_PutServer) error
-	mustEmbedUnimplementedBlobServiceServer()
-}
-
-// UnimplementedBlobServiceServer must be embedded to have forward compatible implementations.
-type UnimplementedBlobServiceServer struct {
-}
-
-func (UnimplementedBlobServiceServer) Stat(context.Context, *StatBlobRequest) (*BlobMeta, 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.store.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/store/protos/rpc_blobstore.proto",
-}
diff --git a/tvix/store/protos/rpc_directory.pb.go b/tvix/store/protos/rpc_directory.pb.go
deleted file mode 100644
index ac5384677b1e..000000000000
--- a/tvix/store/protos/rpc_directory.pb.go
+++ /dev/null
@@ -1,271 +0,0 @@
-// SPDX-License-Identifier: MIT
-// Copyright © 2022 The Tvix Authors
-
-// Code generated by protoc-gen-go. DO NOT EDIT.
-// versions:
-// 	protoc-gen-go v1.31.0
-// 	protoc        (unknown)
-// source: tvix/store/protos/rpc_directory.proto
-
-package storev1
-
-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_store_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_store_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_store_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_store_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_store_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_store_protos_rpc_directory_proto_rawDescGZIP(), []int{1}
-}
-
-func (x *PutDirectoryResponse) GetRootDigest() []byte {
-	if x != nil {
-		return x.RootDigest
-	}
-	return nil
-}
-
-var File_tvix_store_protos_rpc_directory_proto protoreflect.FileDescriptor
-
-var file_tvix_store_protos_rpc_directory_proto_rawDesc = []byte{
-	0x0a, 0x25, 0x74, 0x76, 0x69, 0x78, 0x2f, 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, 0x0d, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x73, 0x74,
-	0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x1f, 0x74, 0x76, 0x69, 0x78, 0x2f, 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, 0xa1, 0x01, 0x0a, 0x10, 0x44,
-	0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12,
-	0x45, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x22, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 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, 0x18, 0x2e, 0x74, 0x76, 0x69,
-	0x78, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x72, 0x65, 0x63,
-	0x74, 0x6f, 0x72, 0x79, 0x30, 0x01, 0x12, 0x46, 0x0a, 0x03, 0x50, 0x75, 0x74, 0x12, 0x18, 0x2e,
-	0x74, 0x76, 0x69, 0x78, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69,
-	0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x1a, 0x23, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 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, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-	0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
-}
-
-var (
-	file_tvix_store_protos_rpc_directory_proto_rawDescOnce sync.Once
-	file_tvix_store_protos_rpc_directory_proto_rawDescData = file_tvix_store_protos_rpc_directory_proto_rawDesc
-)
-
-func file_tvix_store_protos_rpc_directory_proto_rawDescGZIP() []byte {
-	file_tvix_store_protos_rpc_directory_proto_rawDescOnce.Do(func() {
-		file_tvix_store_protos_rpc_directory_proto_rawDescData = protoimpl.X.CompressGZIP(file_tvix_store_protos_rpc_directory_proto_rawDescData)
-	})
-	return file_tvix_store_protos_rpc_directory_proto_rawDescData
-}
-
-var file_tvix_store_protos_rpc_directory_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
-var file_tvix_store_protos_rpc_directory_proto_goTypes = []interface{}{
-	(*GetDirectoryRequest)(nil),  // 0: tvix.store.v1.GetDirectoryRequest
-	(*PutDirectoryResponse)(nil), // 1: tvix.store.v1.PutDirectoryResponse
-	(*Directory)(nil),            // 2: tvix.store.v1.Directory
-}
-var file_tvix_store_protos_rpc_directory_proto_depIdxs = []int32{
-	0, // 0: tvix.store.v1.DirectoryService.Get:input_type -> tvix.store.v1.GetDirectoryRequest
-	2, // 1: tvix.store.v1.DirectoryService.Put:input_type -> tvix.store.v1.Directory
-	2, // 2: tvix.store.v1.DirectoryService.Get:output_type -> tvix.store.v1.Directory
-	1, // 3: tvix.store.v1.DirectoryService.Put:output_type -> tvix.store.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_store_protos_rpc_directory_proto_init() }
-func file_tvix_store_protos_rpc_directory_proto_init() {
-	if File_tvix_store_protos_rpc_directory_proto != nil {
-		return
-	}
-	file_tvix_store_protos_castore_proto_init()
-	if !protoimpl.UnsafeEnabled {
-		file_tvix_store_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_store_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_store_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_store_protos_rpc_directory_proto_rawDesc,
-			NumEnums:      0,
-			NumMessages:   2,
-			NumExtensions: 0,
-			NumServices:   1,
-		},
-		GoTypes:           file_tvix_store_protos_rpc_directory_proto_goTypes,
-		DependencyIndexes: file_tvix_store_protos_rpc_directory_proto_depIdxs,
-		MessageInfos:      file_tvix_store_protos_rpc_directory_proto_msgTypes,
-	}.Build()
-	File_tvix_store_protos_rpc_directory_proto = out.File
-	file_tvix_store_protos_rpc_directory_proto_rawDesc = nil
-	file_tvix_store_protos_rpc_directory_proto_goTypes = nil
-	file_tvix_store_protos_rpc_directory_proto_depIdxs = nil
-}
diff --git a/tvix/store/protos/rpc_directory.proto b/tvix/store/protos/rpc_directory.proto
deleted file mode 100644
index 0aeed5c3c0e1..000000000000
--- a/tvix/store/protos/rpc_directory.proto
+++ /dev/null
@@ -1,48 +0,0 @@
-// SPDX-License-Identifier: MIT
-// Copyright © 2022 The Tvix Authors
-syntax = "proto3";
-
-package tvix.store.v1;
-
-import "tvix/store/protos/castore.proto";
-
-option go_package = "code.tvl.fyi/tvix/store/protos;storev1";
-
-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.
-  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/store/protos/rpc_directory_grpc.pb.go b/tvix/store/protos/rpc_directory_grpc.pb.go
deleted file mode 100644
index a578dbd89d39..000000000000
--- a/tvix/store/protos/rpc_directory_grpc.pb.go
+++ /dev/null
@@ -1,238 +0,0 @@
-// 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_directory.proto
-
-package storev1
-
-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.store.v1.DirectoryService/Get"
-	DirectoryService_Put_FullMethodName = "/tvix.store.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.
-	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.
-	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.store.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/store/protos/rpc_directory.proto",
-}
diff --git a/tvix/store/protos/rpc_pathinfo.pb.go b/tvix/store/protos/rpc_pathinfo.pb.go
index 293cb5a7c321..8a3c10a82101 100644
--- a/tvix/store/protos/rpc_pathinfo.pb.go
+++ b/tvix/store/protos/rpc_pathinfo.pb.go
@@ -10,6 +10,7 @@
 package storev1
 
 import (
+	protos "code.tvl.fyi/tvix/castore/protos"
 	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
 	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
 	reflect "reflect"
@@ -205,39 +206,42 @@ var file_tvix_store_protos_rpc_pathinfo_proto_rawDesc = []byte{
 	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x73, 0x74, 0x6f,
 	0x72, 0x65, 0x2e, 0x76, 0x31, 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, 0x9e, 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, 0x48, 0x0a, 0x0c, 0x43, 0x61, 0x6c,
-	0x63, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x4e, 0x41, 0x52, 0x12, 0x13, 0x2e, 0x74, 0x76, 0x69, 0x78,
-	0x2e, 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, 0x28, 0x5a, 0x26, 0x63, 0x6f,
-	0x64, 0x65, 0x2e, 0x74, 0x76, 0x6c, 0x2e, 0x66, 0x79, 0x69, 0x2f, 0x74, 0x76, 0x69, 0x78, 0x2f,
-	0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x3b, 0x73, 0x74, 0x6f,
-	0x72, 0x65, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 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, 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,
+	0x28, 0x5a, 0x26, 0x63, 0x6f, 0x64, 0x65, 0x2e, 0x74, 0x76, 0x6c, 0x2e, 0x66, 0x79, 0x69, 0x2f,
+	0x74, 0x76, 0x69, 0x78, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x73, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x33,
 }
 
 var (
@@ -258,12 +262,12 @@ var file_tvix_store_protos_rpc_pathinfo_proto_goTypes = []interface{}{
 	(*ListPathInfoRequest)(nil),  // 1: tvix.store.v1.ListPathInfoRequest
 	(*CalculateNARResponse)(nil), // 2: tvix.store.v1.CalculateNARResponse
 	(*PathInfo)(nil),             // 3: tvix.store.v1.PathInfo
-	(*Node)(nil),                 // 4: tvix.store.v1.Node
+	(*protos.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.store.v1.Node
+	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
diff --git a/tvix/store/protos/rpc_pathinfo.proto b/tvix/store/protos/rpc_pathinfo.proto
index e1d6cd774144..1930e87de004 100644
--- a/tvix/store/protos/rpc_pathinfo.proto
+++ b/tvix/store/protos/rpc_pathinfo.proto
@@ -5,6 +5,7 @@ syntax = "proto3";
 package tvix.store.v1;
 
 import "tvix/store/protos/pathinfo.proto";
+import "tvix/castore/protos/castore.proto";
 
 option go_package = "code.tvl.fyi/tvix/store/protos;storev1";
 
@@ -40,7 +41,7 @@ service PathInfoService {
     //
     // 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(Node) returns (CalculateNARResponse);
+    rpc CalculateNAR(tvix.castore.v1.Node) returns (CalculateNARResponse);
 
     // Return a stream of PathInfo messages matching the criteria specified in
     // ListPathInfoRequest.
diff --git a/tvix/store/protos/rpc_pathinfo_grpc.pb.go b/tvix/store/protos/rpc_pathinfo_grpc.pb.go
index d7b6711c0310..10d8a7ffa49c 100644
--- a/tvix/store/protos/rpc_pathinfo_grpc.pb.go
+++ b/tvix/store/protos/rpc_pathinfo_grpc.pb.go
@@ -10,6 +10,7 @@
 package storev1
 
 import (
+	protos "code.tvl.fyi/tvix/castore/protos"
 	context "context"
 	grpc "google.golang.org/grpc"
 	codes "google.golang.org/grpc/codes"
@@ -60,7 +61,7 @@ type PathInfoServiceClient interface {
 	//
 	// 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 *Node, opts ...grpc.CallOption) (*CalculateNARResponse, error)
+	CalculateNAR(ctx context.Context, in *protos.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)
@@ -92,7 +93,7 @@ func (c *pathInfoServiceClient) Put(ctx context.Context, in *PathInfo, opts ...g
 	return out, nil
 }
 
-func (c *pathInfoServiceClient) CalculateNAR(ctx context.Context, in *Node, opts ...grpc.CallOption) (*CalculateNARResponse, error) {
+func (c *pathInfoServiceClient) CalculateNAR(ctx context.Context, in *protos.Node, opts ...grpc.CallOption) (*CalculateNARResponse, error) {
 	out := new(CalculateNARResponse)
 	err := c.cc.Invoke(ctx, PathInfoService_CalculateNAR_FullMethodName, in, out, opts...)
 	if err != nil {
@@ -165,7 +166,7 @@ type PathInfoServiceServer interface {
 	//
 	// 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, *Node) (*CalculateNARResponse, error)
+	CalculateNAR(context.Context, *protos.Node) (*CalculateNARResponse, error)
 	// Return a stream of PathInfo messages matching the criteria specified in
 	// ListPathInfoRequest.
 	List(*ListPathInfoRequest, PathInfoService_ListServer) error
@@ -182,7 +183,7 @@ func (UnimplementedPathInfoServiceServer) Get(context.Context, *GetPathInfoReque
 func (UnimplementedPathInfoServiceServer) Put(context.Context, *PathInfo) (*PathInfo, error) {
 	return nil, status.Errorf(codes.Unimplemented, "method Put not implemented")
 }
-func (UnimplementedPathInfoServiceServer) CalculateNAR(context.Context, *Node) (*CalculateNARResponse, error) {
+func (UnimplementedPathInfoServiceServer) CalculateNAR(context.Context, *protos.Node) (*CalculateNARResponse, error) {
 	return nil, status.Errorf(codes.Unimplemented, "method CalculateNAR not implemented")
 }
 func (UnimplementedPathInfoServiceServer) List(*ListPathInfoRequest, PathInfoService_ListServer) error {
@@ -238,7 +239,7 @@ func _PathInfoService_Put_Handler(srv interface{}, ctx context.Context, dec func
 }
 
 func _PathInfoService_CalculateNAR_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
-	in := new(Node)
+	in := new(protos.Node)
 	if err := dec(in); err != nil {
 		return nil, err
 	}
@@ -250,7 +251,7 @@ func _PathInfoService_CalculateNAR_Handler(srv interface{}, ctx context.Context,
 		FullMethod: PathInfoService_CalculateNAR_FullMethodName,
 	}
 	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
-		return srv.(PathInfoServiceServer).CalculateNAR(ctx, req.(*Node))
+		return srv.(PathInfoServiceServer).CalculateNAR(ctx, req.(*protos.Node))
 	}
 	return interceptor(ctx, in, info, handler)
 }