about summary refs log tree commit diff
path: root/tvix/castore/protos
diff options
context:
space:
mode:
authorFlorian Klink <flokli@flokli.de>2023-10-17T10·53+0100
committerflokli <flokli@flokli.de>2023-10-17T19·51+0000
commite38733a955f6f6fc8962cf0e669d9cf4696bc14d (patch)
tree52dd4e750ab54870d782add53527f29fe5c2e3d2 /tvix/castore/protos
parent0325ae3ba328ac7b4215057d2c00ac467dd9d820 (diff)
chore(tvix): move castore golang bindings to tvix/castore-go r/6843
Have `tvix/castore/protos` only contain the protos, no go noise.

Make the `.pb.go` file generation a pure Nix build
at `//tvix/castore/protos:go-bindings`, and have a script at
`//tvix:castore-go-generate` (TBD) that copies the results to
`tvix/castore-go`.

`//tvix:castore-go`, with sources in `tvix/castore-go` now contains the
tooling around the generated bindings, and the generated bindings
themselves (So go mod replace workflows still work).

An additional CI step is added from there to ensure idempotenty of
the .pb.go files.

The code.tvl.fyi webserver config is updated to the new source code
path. I'm still unsure if we want to also update the go.mod name. While
being a backwards-incompatible change, it'll probbaly make it easier
where to find these files, and the amount of external consumers is still
low enough.

Part of b/323.

Change-Id: I2edadd118c22ec08e57c693f6cc2ef3261c62489
Reviewed-on: https://cl.tvl.fyi/c/depot/+/9787
Reviewed-by: Connor Brewster <cbrewster@hey.com>
Tested-by: BuildkiteCI
Diffstat (limited to 'tvix/castore/protos')
-rw-r--r--tvix/castore/protos/castore.go212
-rw-r--r--tvix/castore/protos/castore.pb.go580
-rw-r--r--tvix/castore/protos/castore_test.go298
-rw-r--r--tvix/castore/protos/default.nix33
-rw-r--r--tvix/castore/protos/go.mod22
-rw-r--r--tvix/castore/protos/go.sum99
-rw-r--r--tvix/castore/protos/rename_node.go38
-rw-r--r--tvix/castore/protos/rpc_blobstore.pb.go414
-rw-r--r--tvix/castore/protos/rpc_blobstore_grpc.pb.go274
-rw-r--r--tvix/castore/protos/rpc_directory.pb.go273
-rw-r--r--tvix/castore/protos/rpc_directory_grpc.pb.go238
11 files changed, 33 insertions, 2448 deletions
diff --git a/tvix/castore/protos/castore.go b/tvix/castore/protos/castore.go
deleted file mode 100644
index c9e3757885..0000000000
--- a/tvix/castore/protos/castore.go
+++ /dev/null
@@ -1,212 +0,0 @@
-package castorev1
-
-import (
-	"bytes"
-	"encoding/base64"
-	"fmt"
-
-	"google.golang.org/protobuf/proto"
-	"lukechampine.com/blake3"
-)
-
-// The size of a directory is calculated by summing up the numbers of
-// `directories`, `files` and `symlinks`, and for each directory, its size
-// field.
-func (d *Directory) Size() 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 ensures a DirectoryNode has a valid name and correct digest len.
-func (n *DirectoryNode) Validate() error {
-	if len(n.Digest) != 32 {
-		return fmt.Errorf("invalid digest length for %s, expected %d, got %d", n.Name, 32, len(n.Digest))
-	}
-
-	if !isValidName(n.Name) {
-		return fmt.Errorf("invalid node name: %s", n.Name)
-	}
-
-	return nil
-}
-
-// Validate ensures a FileNode has a valid name and correct digest len.
-func (n *FileNode) Validate() error {
-	if len(n.Digest) != 32 {
-		return fmt.Errorf("invalid digest length for %s, expected %d, got %d", n.Name, 32, len(n.Digest))
-	}
-
-	if !isValidName(n.Name) {
-		return fmt.Errorf("invalid node name: %s", n.Name)
-	}
-
-	return nil
-}
-
-// Validate ensures a SymlinkNode has a valid name and target.
-func (n *SymlinkNode) Validate() error {
-	if len(n.Target) == 0 || bytes.Contains(n.Target, []byte{0}) {
-		return fmt.Errorf("invalid symlink target: %s", n.Target)
-	}
-
-	if !isValidName(n.Name) {
-		return fmt.Errorf("invalid node name: %s", n.Name)
-	}
-
-	return nil
-}
-
-// Validate ensures a node is valid, by dispatching to the per-type validation functions.
-func (n *Node) Validate() error {
-	if node := n.GetDirectory(); node != nil {
-		if err := node.Validate(); err != nil {
-			return fmt.Errorf("SymlinkNode failed validation: %w", err)
-		}
-	} else if node := n.GetFile(); node != nil {
-		if err := node.Validate(); err != nil {
-			return fmt.Errorf("FileNode failed validation: %w", err)
-		}
-	} else if node := n.GetSymlink(); node != nil {
-		if err := node.Validate(); err != nil {
-			return fmt.Errorf("SymlinkNode failed validation: %w", err)
-		}
-
-	} else {
-		// this would only happen if we introduced a new type
-		return fmt.Errorf("no specific node found")
-	}
-
-	return nil
-}
-
-// Validate thecks the Directory message for invalid data, such as:
-// - violations of name restrictions
-// - invalid digest lengths
-// - not properly sorted lists
-// - duplicate names in the three lists
-func (d *Directory) Validate() error {
-	// seenNames contains all seen names so far.
-	// We populate this to ensure node names are unique across all three lists.
-	seenNames := make(map[string]interface{})
-
-	// We also track the last seen name in each of the three lists,
-	// to ensure nodes are sorted by their names.
-	var lastDirectoryName, lastFileName, lastSymlinkName []byte
-
-	// helper function to only insert in sorted order.
-	// used with the three lists above.
-	// Note this consumes a *pointer to* a string,  as it mutates it.
-	insertIfGt := func(lastName *[]byte, name []byte) error {
-		// update if it's greater than the previous name
-		if bytes.Compare(name, *lastName) == 1 {
-			*lastName = name
-			return nil
-		} else {
-			return fmt.Errorf("%v is not in sorted order", name)
-		}
-	}
-
-	// insertOnce inserts into seenNames if the key doesn't exist yet.
-	insertOnce := func(name []byte) error {
-		encoded := base64.StdEncoding.EncodeToString(name)
-		if _, found := seenNames[encoded]; found {
-			return fmt.Errorf("duplicate name: %v", string(name))
-		}
-		seenNames[encoded] = nil
-		return nil
-	}
-
-	// Loop over all Directories, Files and Symlinks individually,
-	// check them for validity, then check for sorting in the current list, and
-	// uniqueness across all three lists.
-	for _, directoryNode := range d.Directories {
-		directoryName := directoryNode.GetName()
-
-		if err := directoryNode.Validate(); err != nil {
-			return fmt.Errorf("DirectoryNode %s failed validation: %w", directoryName, err)
-		}
-
-		// ensure names are sorted
-		if err := insertIfGt(&lastDirectoryName, directoryName); err != nil {
-			return err
-		}
-
-		// add to seenNames
-		if err := insertOnce(directoryName); err != nil {
-			return err
-		}
-
-	}
-
-	for _, fileNode := range d.Files {
-		fileName := fileNode.GetName()
-
-		if err := fileNode.Validate(); err != nil {
-			return fmt.Errorf("FileNode %s failed validation: %w", fileName, err)
-		}
-
-		// ensure names are sorted
-		if err := insertIfGt(&lastFileName, fileName); err != nil {
-			return err
-		}
-
-		// add to seenNames
-		if err := insertOnce(fileName); err != nil {
-			return err
-		}
-	}
-
-	for _, symlinkNode := range d.Symlinks {
-		symlinkName := symlinkNode.GetName()
-
-		if err := symlinkNode.Validate(); err != nil {
-			return fmt.Errorf("SymlinkNode %s failed validation: %w", symlinkName, err)
-		}
-
-		// ensure names are sorted
-		if err := insertIfGt(&lastSymlinkName, symlinkName); err != nil {
-			return err
-		}
-
-		// add to seenNames
-		if err := insertOnce(symlinkName); err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
diff --git a/tvix/castore/protos/castore.pb.go b/tvix/castore/protos/castore.pb.go
deleted file mode 100644
index 5323d6c923..0000000000
--- a/tvix/castore/protos/castore.pb.go
+++ /dev/null
@@ -1,580 +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/castore/protos/castore.proto
-
-package castorev1
-
-import (
-	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
-	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
-	reflect "reflect"
-	sync "sync"
-)
-
-const (
-	// Verify that this generated code is sufficiently up-to-date.
-	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
-	// Verify that runtime/protoimpl is sufficiently up-to-date.
-	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
-)
-
-// A Directory can contain Directory, File or Symlink nodes.
-// Each of these nodes have a name attribute, which is the basename in that directory
-// and node type specific attributes.
-// The name attribute:
-//   - MUST not contain slashes or null bytes
-//   - MUST not be '.' or '..'
-//   - MUST be unique across all three lists
-//
-// Elements in each list need to be lexicographically ordered by the name
-// attribute.
-type Directory struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	Directories []*DirectoryNode `protobuf:"bytes,1,rep,name=directories,proto3" json:"directories,omitempty"`
-	Files       []*FileNode      `protobuf:"bytes,2,rep,name=files,proto3" json:"files,omitempty"`
-	Symlinks    []*SymlinkNode   `protobuf:"bytes,3,rep,name=symlinks,proto3" json:"symlinks,omitempty"`
-}
-
-func (x *Directory) Reset() {
-	*x = Directory{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_tvix_castore_protos_castore_proto_msgTypes[0]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *Directory) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*Directory) ProtoMessage() {}
-
-func (x *Directory) ProtoReflect() protoreflect.Message {
-	mi := &file_tvix_castore_protos_castore_proto_msgTypes[0]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use Directory.ProtoReflect.Descriptor instead.
-func (*Directory) Descriptor() ([]byte, []int) {
-	return file_tvix_castore_protos_castore_proto_rawDescGZIP(), []int{0}
-}
-
-func (x *Directory) GetDirectories() []*DirectoryNode {
-	if x != nil {
-		return x.Directories
-	}
-	return nil
-}
-
-func (x *Directory) GetFiles() []*FileNode {
-	if x != nil {
-		return x.Files
-	}
-	return nil
-}
-
-func (x *Directory) GetSymlinks() []*SymlinkNode {
-	if x != nil {
-		return x.Symlinks
-	}
-	return nil
-}
-
-// A DirectoryNode represents a directory in a Directory.
-type DirectoryNode struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	// The (base)name of the directory
-	Name []byte `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
-	// The blake3 hash of a Directory message, serialized in protobuf canonical form.
-	Digest []byte `protobuf:"bytes,2,opt,name=digest,proto3" json:"digest,omitempty"`
-	// Number of child elements in the Directory referred to by `digest`.
-	// Calculated by summing up the numbers of `directories`, `files` and
-	// `symlinks`, and for each directory, its size field. Used for inode
-	// number calculation.
-	// This field is precisely as verifiable as any other Merkle tree edge.
-	// Resolve `digest`, and you can compute it incrementally. Resolve the
-	// entire tree, and you can fully compute it from scratch.
-	// A credulous implementation won't reject an excessive size, but this is
-	// harmless: you'll have some ordinals without nodes. Undersizing is
-	// obvious and easy to reject: you won't have an ordinal for some nodes.
-	Size uint32 `protobuf:"varint,3,opt,name=size,proto3" json:"size,omitempty"`
-}
-
-func (x *DirectoryNode) Reset() {
-	*x = DirectoryNode{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_tvix_castore_protos_castore_proto_msgTypes[1]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *DirectoryNode) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*DirectoryNode) ProtoMessage() {}
-
-func (x *DirectoryNode) ProtoReflect() protoreflect.Message {
-	mi := &file_tvix_castore_protos_castore_proto_msgTypes[1]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use DirectoryNode.ProtoReflect.Descriptor instead.
-func (*DirectoryNode) Descriptor() ([]byte, []int) {
-	return file_tvix_castore_protos_castore_proto_rawDescGZIP(), []int{1}
-}
-
-func (x *DirectoryNode) GetName() []byte {
-	if x != nil {
-		return x.Name
-	}
-	return nil
-}
-
-func (x *DirectoryNode) GetDigest() []byte {
-	if x != nil {
-		return x.Digest
-	}
-	return nil
-}
-
-func (x *DirectoryNode) GetSize() 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_castore_protos_castore_proto_msgTypes[2]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *FileNode) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*FileNode) ProtoMessage() {}
-
-func (x *FileNode) ProtoReflect() protoreflect.Message {
-	mi := &file_tvix_castore_protos_castore_proto_msgTypes[2]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use FileNode.ProtoReflect.Descriptor instead.
-func (*FileNode) Descriptor() ([]byte, []int) {
-	return file_tvix_castore_protos_castore_proto_rawDescGZIP(), []int{2}
-}
-
-func (x *FileNode) GetName() []byte {
-	if x != nil {
-		return x.Name
-	}
-	return nil
-}
-
-func (x *FileNode) GetDigest() []byte {
-	if x != nil {
-		return x.Digest
-	}
-	return nil
-}
-
-func (x *FileNode) GetSize() 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_castore_protos_castore_proto_msgTypes[3]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *SymlinkNode) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*SymlinkNode) ProtoMessage() {}
-
-func (x *SymlinkNode) ProtoReflect() protoreflect.Message {
-	mi := &file_tvix_castore_protos_castore_proto_msgTypes[3]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use SymlinkNode.ProtoReflect.Descriptor instead.
-func (*SymlinkNode) Descriptor() ([]byte, []int) {
-	return file_tvix_castore_protos_castore_proto_rawDescGZIP(), []int{3}
-}
-
-func (x *SymlinkNode) GetName() []byte {
-	if x != nil {
-		return x.Name
-	}
-	return nil
-}
-
-func (x *SymlinkNode) GetTarget() []byte {
-	if x != nil {
-		return x.Target
-	}
-	return nil
-}
-
-// A Node is either a DirectoryNode, FileNode or SymlinkNode.
-type Node struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	// Types that are assignable to Node:
-	//
-	//	*Node_Directory
-	//	*Node_File
-	//	*Node_Symlink
-	Node isNode_Node `protobuf_oneof:"node"`
-}
-
-func (x *Node) Reset() {
-	*x = Node{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_tvix_castore_protos_castore_proto_msgTypes[4]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *Node) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*Node) ProtoMessage() {}
-
-func (x *Node) ProtoReflect() protoreflect.Message {
-	mi := &file_tvix_castore_protos_castore_proto_msgTypes[4]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use Node.ProtoReflect.Descriptor instead.
-func (*Node) Descriptor() ([]byte, []int) {
-	return file_tvix_castore_protos_castore_proto_rawDescGZIP(), []int{4}
-}
-
-func (m *Node) GetNode() isNode_Node {
-	if m != nil {
-		return m.Node
-	}
-	return nil
-}
-
-func (x *Node) GetDirectory() *DirectoryNode {
-	if x, ok := x.GetNode().(*Node_Directory); ok {
-		return x.Directory
-	}
-	return nil
-}
-
-func (x *Node) GetFile() *FileNode {
-	if x, ok := x.GetNode().(*Node_File); ok {
-		return x.File
-	}
-	return nil
-}
-
-func (x *Node) GetSymlink() *SymlinkNode {
-	if x, ok := x.GetNode().(*Node_Symlink); ok {
-		return x.Symlink
-	}
-	return nil
-}
-
-type isNode_Node interface {
-	isNode_Node()
-}
-
-type Node_Directory struct {
-	Directory *DirectoryNode `protobuf:"bytes,1,opt,name=directory,proto3,oneof"`
-}
-
-type Node_File struct {
-	File *FileNode `protobuf:"bytes,2,opt,name=file,proto3,oneof"`
-}
-
-type Node_Symlink struct {
-	Symlink *SymlinkNode `protobuf:"bytes,3,opt,name=symlink,proto3,oneof"`
-}
-
-func (*Node_Directory) isNode_Node() {}
-
-func (*Node_File) isNode_Node() {}
-
-func (*Node_Symlink) isNode_Node() {}
-
-var File_tvix_castore_protos_castore_proto protoreflect.FileDescriptor
-
-var file_tvix_castore_protos_castore_proto_rawDesc = []byte{
-	0x0a, 0x21, 0x74, 0x76, 0x69, 0x78, 0x2f, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x70,
-	0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72,
-	0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72,
-	0x65, 0x2e, 0x76, 0x31, 0x22, 0xb8, 0x01, 0x0a, 0x09, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f,
-	0x72, 0x79, 0x12, 0x40, 0x0a, 0x0b, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x69, 0x65,
-	0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63,
-	0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74,
-	0x6f, 0x72, 0x79, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x0b, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f,
-	0x72, 0x69, 0x65, 0x73, 0x12, 0x2f, 0x0a, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20,
-	0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63, 0x61, 0x73, 0x74, 0x6f,
-	0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x05,
-	0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x38, 0x0a, 0x08, 0x73, 0x79, 0x6d, 0x6c, 0x69, 0x6e, 0x6b,
-	0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63,
-	0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6d, 0x6c, 0x69, 0x6e,
-	0x6b, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x08, 0x73, 0x79, 0x6d, 0x6c, 0x69, 0x6e, 0x6b, 0x73, 0x22,
-	0x4f, 0x0a, 0x0d, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x4e, 0x6f, 0x64, 0x65,
-	0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04,
-	0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x02,
-	0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04,
-	0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 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, 0x22, 0xb9, 0x01, 0x0a, 0x04, 0x4e, 0x6f, 0x64, 0x65,
-	0x12, 0x3e, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63, 0x61, 0x73, 0x74, 0x6f,
-	0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x4e,
-	0x6f, 0x64, 0x65, 0x48, 0x00, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79,
-	0x12, 0x2f, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19,
-	0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31,
-	0x2e, 0x46, 0x69, 0x6c, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x48, 0x00, 0x52, 0x04, 0x66, 0x69, 0x6c,
-	0x65, 0x12, 0x38, 0x0a, 0x07, 0x73, 0x79, 0x6d, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x03, 0x20, 0x01,
-	0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72,
-	0x65, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6d, 0x6c, 0x69, 0x6e, 0x6b, 0x4e, 0x6f, 0x64, 0x65,
-	0x48, 0x00, 0x52, 0x07, 0x73, 0x79, 0x6d, 0x6c, 0x69, 0x6e, 0x6b, 0x42, 0x06, 0x0a, 0x04, 0x6e,
-	0x6f, 0x64, 0x65, 0x42, 0x2c, 0x5a, 0x2a, 0x63, 0x6f, 0x64, 0x65, 0x2e, 0x74, 0x76, 0x6c, 0x2e,
-	0x66, 0x79, 0x69, 0x2f, 0x74, 0x76, 0x69, 0x78, 0x2f, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65,
-	0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x3b, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x76,
-	0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
-}
-
-var (
-	file_tvix_castore_protos_castore_proto_rawDescOnce sync.Once
-	file_tvix_castore_protos_castore_proto_rawDescData = file_tvix_castore_protos_castore_proto_rawDesc
-)
-
-func file_tvix_castore_protos_castore_proto_rawDescGZIP() []byte {
-	file_tvix_castore_protos_castore_proto_rawDescOnce.Do(func() {
-		file_tvix_castore_protos_castore_proto_rawDescData = protoimpl.X.CompressGZIP(file_tvix_castore_protos_castore_proto_rawDescData)
-	})
-	return file_tvix_castore_protos_castore_proto_rawDescData
-}
-
-var file_tvix_castore_protos_castore_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
-var file_tvix_castore_protos_castore_proto_goTypes = []interface{}{
-	(*Directory)(nil),     // 0: tvix.castore.v1.Directory
-	(*DirectoryNode)(nil), // 1: tvix.castore.v1.DirectoryNode
-	(*FileNode)(nil),      // 2: tvix.castore.v1.FileNode
-	(*SymlinkNode)(nil),   // 3: tvix.castore.v1.SymlinkNode
-	(*Node)(nil),          // 4: tvix.castore.v1.Node
-}
-var file_tvix_castore_protos_castore_proto_depIdxs = []int32{
-	1, // 0: tvix.castore.v1.Directory.directories:type_name -> tvix.castore.v1.DirectoryNode
-	2, // 1: tvix.castore.v1.Directory.files:type_name -> tvix.castore.v1.FileNode
-	3, // 2: tvix.castore.v1.Directory.symlinks:type_name -> tvix.castore.v1.SymlinkNode
-	1, // 3: tvix.castore.v1.Node.directory:type_name -> tvix.castore.v1.DirectoryNode
-	2, // 4: tvix.castore.v1.Node.file:type_name -> tvix.castore.v1.FileNode
-	3, // 5: tvix.castore.v1.Node.symlink:type_name -> tvix.castore.v1.SymlinkNode
-	6, // [6:6] is the sub-list for method output_type
-	6, // [6:6] is the sub-list for method input_type
-	6, // [6:6] is the sub-list for extension type_name
-	6, // [6:6] is the sub-list for extension extendee
-	0, // [0:6] is the sub-list for field type_name
-}
-
-func init() { file_tvix_castore_protos_castore_proto_init() }
-func file_tvix_castore_protos_castore_proto_init() {
-	if File_tvix_castore_protos_castore_proto != nil {
-		return
-	}
-	if !protoimpl.UnsafeEnabled {
-		file_tvix_castore_protos_castore_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*Directory); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_tvix_castore_protos_castore_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*DirectoryNode); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_tvix_castore_protos_castore_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*FileNode); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_tvix_castore_protos_castore_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*SymlinkNode); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_tvix_castore_protos_castore_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*Node); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-	}
-	file_tvix_castore_protos_castore_proto_msgTypes[4].OneofWrappers = []interface{}{
-		(*Node_Directory)(nil),
-		(*Node_File)(nil),
-		(*Node_Symlink)(nil),
-	}
-	type x struct{}
-	out := protoimpl.TypeBuilder{
-		File: protoimpl.DescBuilder{
-			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
-			RawDescriptor: file_tvix_castore_protos_castore_proto_rawDesc,
-			NumEnums:      0,
-			NumMessages:   5,
-			NumExtensions: 0,
-			NumServices:   0,
-		},
-		GoTypes:           file_tvix_castore_protos_castore_proto_goTypes,
-		DependencyIndexes: file_tvix_castore_protos_castore_proto_depIdxs,
-		MessageInfos:      file_tvix_castore_protos_castore_proto_msgTypes,
-	}.Build()
-	File_tvix_castore_protos_castore_proto = out.File
-	file_tvix_castore_protos_castore_proto_rawDesc = nil
-	file_tvix_castore_protos_castore_proto_goTypes = nil
-	file_tvix_castore_protos_castore_proto_depIdxs = nil
-}
diff --git a/tvix/castore/protos/castore_test.go b/tvix/castore/protos/castore_test.go
deleted file mode 100644
index fda87a6cfb..0000000000
--- a/tvix/castore/protos/castore_test.go
+++ /dev/null
@@ -1,298 +0,0 @@
-package castorev1_test
-
-import (
-	"testing"
-
-	castorev1pb "code.tvl.fyi/tvix/castore/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 := castorev1pb.Directory{
-			Directories: []*castorev1pb.DirectoryNode{},
-			Files:       []*castorev1pb.FileNode{},
-			Symlinks:    []*castorev1pb.SymlinkNode{},
-		}
-
-		assert.Equal(t, uint32(0), d.Size())
-	})
-
-	t.Run("containing single empty directory", func(t *testing.T) {
-		d := castorev1pb.Directory{
-			Directories: []*castorev1pb.DirectoryNode{{
-				Name:   []byte([]byte("foo")),
-				Digest: dummyDigest,
-				Size:   0,
-			}},
-			Files:    []*castorev1pb.FileNode{},
-			Symlinks: []*castorev1pb.SymlinkNode{},
-		}
-
-		assert.Equal(t, uint32(1), d.Size())
-	})
-
-	t.Run("containing single non-empty directory", func(t *testing.T) {
-		d := castorev1pb.Directory{
-			Directories: []*castorev1pb.DirectoryNode{{
-				Name:   []byte("foo"),
-				Digest: dummyDigest,
-				Size:   4,
-			}},
-			Files:    []*castorev1pb.FileNode{},
-			Symlinks: []*castorev1pb.SymlinkNode{},
-		}
-
-		assert.Equal(t, uint32(5), d.Size())
-	})
-
-	t.Run("containing single file", func(t *testing.T) {
-		d := castorev1pb.Directory{
-			Directories: []*castorev1pb.DirectoryNode{},
-			Files: []*castorev1pb.FileNode{{
-				Name:       []byte("foo"),
-				Digest:     dummyDigest,
-				Size:       42,
-				Executable: false,
-			}},
-			Symlinks: []*castorev1pb.SymlinkNode{},
-		}
-
-		assert.Equal(t, uint32(1), d.Size())
-	})
-
-	t.Run("containing single symlink", func(t *testing.T) {
-		d := castorev1pb.Directory{
-			Directories: []*castorev1pb.DirectoryNode{},
-			Files:       []*castorev1pb.FileNode{},
-			Symlinks: []*castorev1pb.SymlinkNode{{
-				Name:   []byte("foo"),
-				Target: []byte("bar"),
-			}},
-		}
-
-		assert.Equal(t, uint32(1), d.Size())
-	})
-
-}
-func TestDirectoryDigest(t *testing.T) {
-	d := castorev1pb.Directory{
-		Directories: []*castorev1pb.DirectoryNode{},
-		Files:       []*castorev1pb.FileNode{},
-		Symlinks:    []*castorev1pb.SymlinkNode{},
-	}
-
-	dgst, err := d.Digest()
-	assert.NoError(t, err, "calling Digest() on a directory shouldn't error")
-	assert.Equal(t, []byte{
-		0xaf, 0x13, 0x49, 0xb9, 0xf5, 0xf9, 0xa1, 0xa6, 0xa0, 0x40, 0x4d, 0xea, 0x36, 0xdc,
-		0xc9, 0x49, 0x9b, 0xcb, 0x25, 0xc9, 0xad, 0xc1, 0x12, 0xb7, 0xcc, 0x9a, 0x93, 0xca,
-		0xe4, 0x1f, 0x32, 0x62,
-	}, dgst)
-}
-
-func TestDirectoryValidate(t *testing.T) {
-	t.Run("empty", func(t *testing.T) {
-		d := castorev1pb.Directory{
-			Directories: []*castorev1pb.DirectoryNode{},
-			Files:       []*castorev1pb.FileNode{},
-			Symlinks:    []*castorev1pb.SymlinkNode{},
-		}
-
-		assert.NoError(t, d.Validate())
-	})
-
-	t.Run("invalid names", func(t *testing.T) {
-		{
-			d := castorev1pb.Directory{
-				Directories: []*castorev1pb.DirectoryNode{{
-					Name:   []byte{},
-					Digest: dummyDigest,
-					Size:   42,
-				}},
-				Files:    []*castorev1pb.FileNode{},
-				Symlinks: []*castorev1pb.SymlinkNode{},
-			}
-
-			assert.ErrorContains(t, d.Validate(), "invalid node name")
-		}
-		{
-			d := castorev1pb.Directory{
-				Directories: []*castorev1pb.DirectoryNode{{
-					Name:   []byte("."),
-					Digest: dummyDigest,
-					Size:   42,
-				}},
-				Files:    []*castorev1pb.FileNode{},
-				Symlinks: []*castorev1pb.SymlinkNode{},
-			}
-
-			assert.ErrorContains(t, d.Validate(), "invalid node name")
-		}
-		{
-			d := castorev1pb.Directory{
-				Directories: []*castorev1pb.DirectoryNode{},
-				Files: []*castorev1pb.FileNode{{
-					Name:       []byte(".."),
-					Digest:     dummyDigest,
-					Size:       42,
-					Executable: false,
-				}},
-				Symlinks: []*castorev1pb.SymlinkNode{},
-			}
-
-			assert.ErrorContains(t, d.Validate(), "invalid node name")
-		}
-		{
-			d := castorev1pb.Directory{
-				Directories: []*castorev1pb.DirectoryNode{},
-				Files:       []*castorev1pb.FileNode{},
-				Symlinks: []*castorev1pb.SymlinkNode{{
-					Name:   []byte("\x00"),
-					Target: []byte("foo"),
-				}},
-			}
-
-			assert.ErrorContains(t, d.Validate(), "invalid node name")
-		}
-		{
-			d := castorev1pb.Directory{
-				Directories: []*castorev1pb.DirectoryNode{},
-				Files:       []*castorev1pb.FileNode{},
-				Symlinks: []*castorev1pb.SymlinkNode{{
-					Name:   []byte("foo/bar"),
-					Target: []byte("foo"),
-				}},
-			}
-
-			assert.ErrorContains(t, d.Validate(), "invalid node name")
-		}
-	})
-
-	t.Run("invalid digest", func(t *testing.T) {
-		d := castorev1pb.Directory{
-			Directories: []*castorev1pb.DirectoryNode{{
-				Name:   []byte("foo"),
-				Digest: nil,
-				Size:   42,
-			}},
-			Files:    []*castorev1pb.FileNode{},
-			Symlinks: []*castorev1pb.SymlinkNode{},
-		}
-
-		assert.ErrorContains(t, d.Validate(), "invalid digest length")
-	})
-
-	t.Run("invalid symlink targets", func(t *testing.T) {
-		{
-			d := castorev1pb.Directory{
-				Directories: []*castorev1pb.DirectoryNode{},
-				Files:       []*castorev1pb.FileNode{},
-				Symlinks: []*castorev1pb.SymlinkNode{{
-					Name:   []byte("foo"),
-					Target: []byte{},
-				}},
-			}
-
-			assert.ErrorContains(t, d.Validate(), "invalid symlink target")
-		}
-		{
-			d := castorev1pb.Directory{
-				Directories: []*castorev1pb.DirectoryNode{},
-				Files:       []*castorev1pb.FileNode{},
-				Symlinks: []*castorev1pb.SymlinkNode{{
-					Name:   []byte("foo"),
-					Target: []byte{0x66, 0x6f, 0x6f, 0},
-				}},
-			}
-
-			assert.ErrorContains(t, d.Validate(), "invalid symlink target")
-		}
-	})
-
-	t.Run("sorting", func(t *testing.T) {
-		// "b" comes before "a", bad.
-		{
-			d := castorev1pb.Directory{
-				Directories: []*castorev1pb.DirectoryNode{{
-					Name:   []byte("b"),
-					Digest: dummyDigest,
-					Size:   42,
-				}, {
-					Name:   []byte("a"),
-					Digest: dummyDigest,
-					Size:   42,
-				}},
-				Files:    []*castorev1pb.FileNode{},
-				Symlinks: []*castorev1pb.SymlinkNode{},
-			}
-			assert.ErrorContains(t, d.Validate(), "is not in sorted order")
-		}
-
-		// "a" exists twice, bad.
-		{
-			d := castorev1pb.Directory{
-				Directories: []*castorev1pb.DirectoryNode{{
-					Name:   []byte("a"),
-					Digest: dummyDigest,
-					Size:   42,
-				}},
-				Files: []*castorev1pb.FileNode{{
-					Name:       []byte("a"),
-					Digest:     dummyDigest,
-					Size:       42,
-					Executable: false,
-				}},
-				Symlinks: []*castorev1pb.SymlinkNode{},
-			}
-			assert.ErrorContains(t, d.Validate(), "duplicate name")
-		}
-
-		// "a" comes before "b", all good.
-		{
-			d := castorev1pb.Directory{
-				Directories: []*castorev1pb.DirectoryNode{{
-					Name:   []byte("a"),
-					Digest: dummyDigest,
-					Size:   42,
-				}, {
-					Name:   []byte("b"),
-					Digest: dummyDigest,
-					Size:   42,
-				}},
-				Files:    []*castorev1pb.FileNode{},
-				Symlinks: []*castorev1pb.SymlinkNode{},
-			}
-			assert.NoError(t, d.Validate(), "shouldn't error")
-		}
-
-		// [b, c] and [a] are both properly sorted.
-		{
-			d := castorev1pb.Directory{
-				Directories: []*castorev1pb.DirectoryNode{{
-					Name:   []byte("b"),
-					Digest: dummyDigest,
-					Size:   42,
-				}, {
-					Name:   []byte("c"),
-					Digest: dummyDigest,
-					Size:   42,
-				}},
-				Files: []*castorev1pb.FileNode{},
-				Symlinks: []*castorev1pb.SymlinkNode{{
-					Name:   []byte("a"),
-					Target: []byte("foo"),
-				}},
-			}
-			assert.NoError(t, d.Validate(), "shouldn't error")
-		}
-	})
-}
diff --git a/tvix/castore/protos/default.nix b/tvix/castore/protos/default.nix
new file mode 100644
index 0000000000..e2e2f910ee
--- /dev/null
+++ b/tvix/castore/protos/default.nix
@@ -0,0 +1,33 @@
+{ depot, pkgs, ... }: {
+  # Produces the golang bindings.
+  go-bindings = pkgs.stdenv.mkDerivation {
+    name = "go-bindings";
+
+    src = depot.nix.sparseTree {
+      name = "castore-protos";
+      root = depot.path.origSrc;
+      paths = [
+        ./castore.proto
+        ./rpc_blobstore.proto
+        ./rpc_directory.proto
+        ../../../buf.yaml
+        ../../../buf.gen.yaml
+      ];
+    };
+
+    nativeBuildInputs = [
+      pkgs.buf
+      pkgs.protoc-gen-go
+      pkgs.protoc-gen-go-grpc
+    ];
+
+    buildPhase = ''
+      export HOME=$TMPDIR
+      buf lint
+      buf generate
+
+      mkdir -p $out
+      cp tvix/castore/protos/*.pb.go $out/
+    '';
+  };
+}
diff --git a/tvix/castore/protos/go.mod b/tvix/castore/protos/go.mod
deleted file mode 100644
index 9048aa205c..0000000000
--- a/tvix/castore/protos/go.mod
+++ /dev/null
@@ -1,22 +0,0 @@
-module code.tvl.fyi/tvix/castore/protos
-
-go 1.19
-
-require (
-	github.com/stretchr/testify v1.8.1
-	google.golang.org/grpc v1.51.0
-	google.golang.org/protobuf v1.31.0
-	lukechampine.com/blake3 v1.1.7
-)
-
-require (
-	github.com/davecgh/go-spew v1.1.1 // indirect
-	github.com/golang/protobuf v1.5.2 // indirect
-	github.com/klauspost/cpuid/v2 v2.0.9 // indirect
-	github.com/pmezard/go-difflib v1.0.0 // indirect
-	golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
-	golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
-	golang.org/x/text v0.4.0 // indirect
-	google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
-	gopkg.in/yaml.v3 v3.0.1 // indirect
-)
diff --git a/tvix/castore/protos/go.sum b/tvix/castore/protos/go.sum
deleted file mode 100644
index 535b8e32f0..0000000000
--- a/tvix/castore/protos/go.sum
+++ /dev/null
@@ -1,99 +0,0 @@
-cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
-github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
-github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
-github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
-github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
-github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
-github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
-github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
-github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
-github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
-github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
-github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
-github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
-github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
-github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
-github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
-github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
-golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
-golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
-golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
-golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
-golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
-google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
-google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
-google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
-google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U=
-google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww=
-google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
-google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
-google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
-google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
-google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
-google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
-google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
-gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
-lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=
diff --git a/tvix/castore/protos/rename_node.go b/tvix/castore/protos/rename_node.go
deleted file mode 100644
index 80537b16d3..0000000000
--- a/tvix/castore/protos/rename_node.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package castorev1
-
-// RenamedNode returns a node with a new name.
-func RenamedNode(node *Node, name string) *Node {
-	if directoryNode := node.GetDirectory(); directoryNode != nil {
-		return &Node{
-			Node: &Node_Directory{
-				Directory: &DirectoryNode{
-					Name:   []byte(name),
-					Digest: directoryNode.GetDigest(),
-					Size:   directoryNode.GetSize(),
-				},
-			},
-		}
-	} else if fileNode := node.GetFile(); fileNode != nil {
-		return &Node{
-			Node: &Node_File{
-				File: &FileNode{
-					Name:       []byte(name),
-					Digest:     fileNode.GetDigest(),
-					Size:       fileNode.GetSize(),
-					Executable: fileNode.GetExecutable(),
-				},
-			},
-		}
-	} else if symlinkNode := node.GetSymlink(); symlinkNode != nil {
-		return &Node{
-			Node: &Node_Symlink{
-				Symlink: &SymlinkNode{
-					Name:   []byte(name),
-					Target: symlinkNode.GetTarget(),
-				},
-			},
-		}
-	} else {
-		panic("unreachable")
-	}
-}
diff --git a/tvix/castore/protos/rpc_blobstore.pb.go b/tvix/castore/protos/rpc_blobstore.pb.go
deleted file mode 100644
index 1afc826744..0000000000
--- a/tvix/castore/protos/rpc_blobstore.pb.go
+++ /dev/null
@@ -1,414 +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/castore/protos/rpc_blobstore.proto
-
-package castorev1
-
-import (
-	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
-	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
-	reflect "reflect"
-	sync "sync"
-)
-
-const (
-	// Verify that this generated code is sufficiently up-to-date.
-	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
-	// Verify that runtime/protoimpl is sufficiently up-to-date.
-	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
-)
-
-type StatBlobRequest struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	// The blake3 digest of the blob requested
-	Digest []byte `protobuf:"bytes,1,opt,name=digest,proto3" json:"digest,omitempty"`
-}
-
-func (x *StatBlobRequest) Reset() {
-	*x = StatBlobRequest{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_tvix_castore_protos_rpc_blobstore_proto_msgTypes[0]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *StatBlobRequest) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*StatBlobRequest) ProtoMessage() {}
-
-func (x *StatBlobRequest) ProtoReflect() protoreflect.Message {
-	mi := &file_tvix_castore_protos_rpc_blobstore_proto_msgTypes[0]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use StatBlobRequest.ProtoReflect.Descriptor instead.
-func (*StatBlobRequest) Descriptor() ([]byte, []int) {
-	return file_tvix_castore_protos_rpc_blobstore_proto_rawDescGZIP(), []int{0}
-}
-
-func (x *StatBlobRequest) GetDigest() []byte {
-	if x != nil {
-		return x.Digest
-	}
-	return nil
-}
-
-type BlobMeta struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-}
-
-func (x *BlobMeta) Reset() {
-	*x = BlobMeta{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_tvix_castore_protos_rpc_blobstore_proto_msgTypes[1]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *BlobMeta) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*BlobMeta) ProtoMessage() {}
-
-func (x *BlobMeta) ProtoReflect() protoreflect.Message {
-	mi := &file_tvix_castore_protos_rpc_blobstore_proto_msgTypes[1]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use BlobMeta.ProtoReflect.Descriptor instead.
-func (*BlobMeta) Descriptor() ([]byte, []int) {
-	return file_tvix_castore_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_castore_protos_rpc_blobstore_proto_msgTypes[2]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *ReadBlobRequest) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*ReadBlobRequest) ProtoMessage() {}
-
-func (x *ReadBlobRequest) ProtoReflect() protoreflect.Message {
-	mi := &file_tvix_castore_protos_rpc_blobstore_proto_msgTypes[2]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use ReadBlobRequest.ProtoReflect.Descriptor instead.
-func (*ReadBlobRequest) Descriptor() ([]byte, []int) {
-	return file_tvix_castore_protos_rpc_blobstore_proto_rawDescGZIP(), []int{2}
-}
-
-func (x *ReadBlobRequest) GetDigest() []byte {
-	if x != nil {
-		return x.Digest
-	}
-	return nil
-}
-
-// This represents some bytes of a blob.
-// Blobs are sent in smaller chunks to keep message sizes manageable.
-type BlobChunk struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
-}
-
-func (x *BlobChunk) Reset() {
-	*x = BlobChunk{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_tvix_castore_protos_rpc_blobstore_proto_msgTypes[3]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *BlobChunk) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*BlobChunk) ProtoMessage() {}
-
-func (x *BlobChunk) ProtoReflect() protoreflect.Message {
-	mi := &file_tvix_castore_protos_rpc_blobstore_proto_msgTypes[3]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use BlobChunk.ProtoReflect.Descriptor instead.
-func (*BlobChunk) Descriptor() ([]byte, []int) {
-	return file_tvix_castore_protos_rpc_blobstore_proto_rawDescGZIP(), []int{3}
-}
-
-func (x *BlobChunk) GetData() []byte {
-	if x != nil {
-		return x.Data
-	}
-	return nil
-}
-
-type PutBlobResponse struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	// The blake3 digest of the data that was sent.
-	Digest []byte `protobuf:"bytes,1,opt,name=digest,proto3" json:"digest,omitempty"`
-}
-
-func (x *PutBlobResponse) Reset() {
-	*x = PutBlobResponse{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_tvix_castore_protos_rpc_blobstore_proto_msgTypes[4]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *PutBlobResponse) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*PutBlobResponse) ProtoMessage() {}
-
-func (x *PutBlobResponse) ProtoReflect() protoreflect.Message {
-	mi := &file_tvix_castore_protos_rpc_blobstore_proto_msgTypes[4]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use PutBlobResponse.ProtoReflect.Descriptor instead.
-func (*PutBlobResponse) Descriptor() ([]byte, []int) {
-	return file_tvix_castore_protos_rpc_blobstore_proto_rawDescGZIP(), []int{4}
-}
-
-func (x *PutBlobResponse) GetDigest() []byte {
-	if x != nil {
-		return x.Digest
-	}
-	return nil
-}
-
-var File_tvix_castore_protos_rpc_blobstore_proto protoreflect.FileDescriptor
-
-var file_tvix_castore_protos_rpc_blobstore_proto_rawDesc = []byte{
-	0x0a, 0x27, 0x74, 0x76, 0x69, 0x78, 0x2f, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x70,
-	0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x72, 0x70, 0x63, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x73, 0x74,
-	0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x74, 0x76, 0x69, 0x78, 0x2e,
-	0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x22, 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, 0xe1, 0x01, 0x0a, 0x0b, 0x42, 0x6c, 0x6f,
-	0x62, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x43, 0x0a, 0x04, 0x53, 0x74, 0x61, 0x74,
-	0x12, 0x20, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e,
-	0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65,
-	0x73, 0x74, 0x1a, 0x19, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72,
-	0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x62, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x46, 0x0a,
-	0x04, 0x52, 0x65, 0x61, 0x64, 0x12, 0x20, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63, 0x61, 0x73,
-	0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x42, 0x6c, 0x6f, 0x62,
-	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63,
-	0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x62, 0x43, 0x68,
-	0x75, 0x6e, 0x6b, 0x30, 0x01, 0x12, 0x45, 0x0a, 0x03, 0x50, 0x75, 0x74, 0x12, 0x1a, 0x2e, 0x74,
-	0x76, 0x69, 0x78, 0x2e, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x42,
-	0x6c, 0x6f, 0x62, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x1a, 0x20, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e,
-	0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x74, 0x42, 0x6c,
-	0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x42, 0x2c, 0x5a, 0x2a,
-	0x63, 0x6f, 0x64, 0x65, 0x2e, 0x74, 0x76, 0x6c, 0x2e, 0x66, 0x79, 0x69, 0x2f, 0x74, 0x76, 0x69,
-	0x78, 0x2f, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-	0x3b, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
-	0x6f, 0x33,
-}
-
-var (
-	file_tvix_castore_protos_rpc_blobstore_proto_rawDescOnce sync.Once
-	file_tvix_castore_protos_rpc_blobstore_proto_rawDescData = file_tvix_castore_protos_rpc_blobstore_proto_rawDesc
-)
-
-func file_tvix_castore_protos_rpc_blobstore_proto_rawDescGZIP() []byte {
-	file_tvix_castore_protos_rpc_blobstore_proto_rawDescOnce.Do(func() {
-		file_tvix_castore_protos_rpc_blobstore_proto_rawDescData = protoimpl.X.CompressGZIP(file_tvix_castore_protos_rpc_blobstore_proto_rawDescData)
-	})
-	return file_tvix_castore_protos_rpc_blobstore_proto_rawDescData
-}
-
-var file_tvix_castore_protos_rpc_blobstore_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
-var file_tvix_castore_protos_rpc_blobstore_proto_goTypes = []interface{}{
-	(*StatBlobRequest)(nil), // 0: tvix.castore.v1.StatBlobRequest
-	(*BlobMeta)(nil),        // 1: tvix.castore.v1.BlobMeta
-	(*ReadBlobRequest)(nil), // 2: tvix.castore.v1.ReadBlobRequest
-	(*BlobChunk)(nil),       // 3: tvix.castore.v1.BlobChunk
-	(*PutBlobResponse)(nil), // 4: tvix.castore.v1.PutBlobResponse
-}
-var file_tvix_castore_protos_rpc_blobstore_proto_depIdxs = []int32{
-	0, // 0: tvix.castore.v1.BlobService.Stat:input_type -> tvix.castore.v1.StatBlobRequest
-	2, // 1: tvix.castore.v1.BlobService.Read:input_type -> tvix.castore.v1.ReadBlobRequest
-	3, // 2: tvix.castore.v1.BlobService.Put:input_type -> tvix.castore.v1.BlobChunk
-	1, // 3: tvix.castore.v1.BlobService.Stat:output_type -> tvix.castore.v1.BlobMeta
-	3, // 4: tvix.castore.v1.BlobService.Read:output_type -> tvix.castore.v1.BlobChunk
-	4, // 5: tvix.castore.v1.BlobService.Put:output_type -> tvix.castore.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_castore_protos_rpc_blobstore_proto_init() }
-func file_tvix_castore_protos_rpc_blobstore_proto_init() {
-	if File_tvix_castore_protos_rpc_blobstore_proto != nil {
-		return
-	}
-	if !protoimpl.UnsafeEnabled {
-		file_tvix_castore_protos_rpc_blobstore_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*StatBlobRequest); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_tvix_castore_protos_rpc_blobstore_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*BlobMeta); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_tvix_castore_protos_rpc_blobstore_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*ReadBlobRequest); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_tvix_castore_protos_rpc_blobstore_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*BlobChunk); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_tvix_castore_protos_rpc_blobstore_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*PutBlobResponse); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-	}
-	type x struct{}
-	out := protoimpl.TypeBuilder{
-		File: protoimpl.DescBuilder{
-			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
-			RawDescriptor: file_tvix_castore_protos_rpc_blobstore_proto_rawDesc,
-			NumEnums:      0,
-			NumMessages:   5,
-			NumExtensions: 0,
-			NumServices:   1,
-		},
-		GoTypes:           file_tvix_castore_protos_rpc_blobstore_proto_goTypes,
-		DependencyIndexes: file_tvix_castore_protos_rpc_blobstore_proto_depIdxs,
-		MessageInfos:      file_tvix_castore_protos_rpc_blobstore_proto_msgTypes,
-	}.Build()
-	File_tvix_castore_protos_rpc_blobstore_proto = out.File
-	file_tvix_castore_protos_rpc_blobstore_proto_rawDesc = nil
-	file_tvix_castore_protos_rpc_blobstore_proto_goTypes = nil
-	file_tvix_castore_protos_rpc_blobstore_proto_depIdxs = nil
-}
diff --git a/tvix/castore/protos/rpc_blobstore_grpc.pb.go b/tvix/castore/protos/rpc_blobstore_grpc.pb.go
deleted file mode 100644
index 0876bcc4e9..0000000000
--- a/tvix/castore/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/castore/protos/rpc_blobstore.proto
-
-package castorev1
-
-import (
-	context "context"
-	grpc "google.golang.org/grpc"
-	codes "google.golang.org/grpc/codes"
-	status "google.golang.org/grpc/status"
-)
-
-// This is a compile-time assertion to ensure that this generated file
-// is compatible with the grpc package it is being compiled against.
-// Requires gRPC-Go v1.32.0 or later.
-const _ = grpc.SupportPackageIsVersion7
-
-const (
-	BlobService_Stat_FullMethodName = "/tvix.castore.v1.BlobService/Stat"
-	BlobService_Read_FullMethodName = "/tvix.castore.v1.BlobService/Read"
-	BlobService_Put_FullMethodName  = "/tvix.castore.v1.BlobService/Put"
-)
-
-// BlobServiceClient is the client API for BlobService service.
-//
-// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
-type BlobServiceClient interface {
-	// 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.castore.v1.BlobService",
-	HandlerType: (*BlobServiceServer)(nil),
-	Methods: []grpc.MethodDesc{
-		{
-			MethodName: "Stat",
-			Handler:    _BlobService_Stat_Handler,
-		},
-	},
-	Streams: []grpc.StreamDesc{
-		{
-			StreamName:    "Read",
-			Handler:       _BlobService_Read_Handler,
-			ServerStreams: true,
-		},
-		{
-			StreamName:    "Put",
-			Handler:       _BlobService_Put_Handler,
-			ClientStreams: true,
-		},
-	},
-	Metadata: "tvix/castore/protos/rpc_blobstore.proto",
-}
diff --git a/tvix/castore/protos/rpc_directory.pb.go b/tvix/castore/protos/rpc_directory.pb.go
deleted file mode 100644
index f658c6b60c..0000000000
--- a/tvix/castore/protos/rpc_directory.pb.go
+++ /dev/null
@@ -1,273 +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/castore/protos/rpc_directory.proto
-
-package castorev1
-
-import (
-	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
-	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
-	reflect "reflect"
-	sync "sync"
-)
-
-const (
-	// Verify that this generated code is sufficiently up-to-date.
-	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
-	// Verify that runtime/protoimpl is sufficiently up-to-date.
-	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
-)
-
-type GetDirectoryRequest struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	// Types that are assignable to ByWhat:
-	//
-	//	*GetDirectoryRequest_Digest
-	ByWhat isGetDirectoryRequest_ByWhat `protobuf_oneof:"by_what"`
-	// If set to true, recursively resolve all child Directory messages.
-	// Directory messages SHOULD be streamed in a recursive breadth-first walk,
-	// but other orders are also fine, as long as Directory messages are only
-	// sent after they are referred to from previously sent Directory messages.
-	Recursive bool `protobuf:"varint,2,opt,name=recursive,proto3" json:"recursive,omitempty"`
-}
-
-func (x *GetDirectoryRequest) Reset() {
-	*x = GetDirectoryRequest{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_tvix_castore_protos_rpc_directory_proto_msgTypes[0]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *GetDirectoryRequest) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*GetDirectoryRequest) ProtoMessage() {}
-
-func (x *GetDirectoryRequest) ProtoReflect() protoreflect.Message {
-	mi := &file_tvix_castore_protos_rpc_directory_proto_msgTypes[0]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use GetDirectoryRequest.ProtoReflect.Descriptor instead.
-func (*GetDirectoryRequest) Descriptor() ([]byte, []int) {
-	return file_tvix_castore_protos_rpc_directory_proto_rawDescGZIP(), []int{0}
-}
-
-func (m *GetDirectoryRequest) GetByWhat() isGetDirectoryRequest_ByWhat {
-	if m != nil {
-		return m.ByWhat
-	}
-	return nil
-}
-
-func (x *GetDirectoryRequest) GetDigest() []byte {
-	if x, ok := x.GetByWhat().(*GetDirectoryRequest_Digest); ok {
-		return x.Digest
-	}
-	return nil
-}
-
-func (x *GetDirectoryRequest) GetRecursive() bool {
-	if x != nil {
-		return x.Recursive
-	}
-	return false
-}
-
-type isGetDirectoryRequest_ByWhat interface {
-	isGetDirectoryRequest_ByWhat()
-}
-
-type GetDirectoryRequest_Digest struct {
-	// The blake3 hash of the (root) Directory message, serialized in
-	// protobuf canonical form.
-	// Keep in mind this can be a subtree of another root.
-	Digest []byte `protobuf:"bytes,1,opt,name=digest,proto3,oneof"`
-}
-
-func (*GetDirectoryRequest_Digest) isGetDirectoryRequest_ByWhat() {}
-
-type PutDirectoryResponse struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	RootDigest []byte `protobuf:"bytes,1,opt,name=root_digest,json=rootDigest,proto3" json:"root_digest,omitempty"`
-}
-
-func (x *PutDirectoryResponse) Reset() {
-	*x = PutDirectoryResponse{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_tvix_castore_protos_rpc_directory_proto_msgTypes[1]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *PutDirectoryResponse) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*PutDirectoryResponse) ProtoMessage() {}
-
-func (x *PutDirectoryResponse) ProtoReflect() protoreflect.Message {
-	mi := &file_tvix_castore_protos_rpc_directory_proto_msgTypes[1]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use PutDirectoryResponse.ProtoReflect.Descriptor instead.
-func (*PutDirectoryResponse) Descriptor() ([]byte, []int) {
-	return file_tvix_castore_protos_rpc_directory_proto_rawDescGZIP(), []int{1}
-}
-
-func (x *PutDirectoryResponse) GetRootDigest() []byte {
-	if x != nil {
-		return x.RootDigest
-	}
-	return nil
-}
-
-var File_tvix_castore_protos_rpc_directory_proto protoreflect.FileDescriptor
-
-var file_tvix_castore_protos_rpc_directory_proto_rawDesc = []byte{
-	0x0a, 0x27, 0x74, 0x76, 0x69, 0x78, 0x2f, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x70,
-	0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f, 0x72, 0x70, 0x63, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74,
-	0x6f, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x74, 0x76, 0x69, 0x78, 0x2e,
-	0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x21, 0x74, 0x76, 0x69, 0x78,
-	0x2f, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x2f,
-	0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x58, 0x0a,
-	0x13, 0x47, 0x65, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71,
-	0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x01,
-	0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x1c,
-	0x0a, 0x09, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
-	0x08, 0x52, 0x09, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x42, 0x09, 0x0a, 0x07,
-	0x62, 0x79, 0x5f, 0x77, 0x68, 0x61, 0x74, 0x22, 0x37, 0x0a, 0x14, 0x50, 0x75, 0x74, 0x44, 0x69,
-	0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
-	0x1f, 0x0a, 0x0b, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x01,
-	0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x72, 0x6f, 0x6f, 0x74, 0x44, 0x69, 0x67, 0x65, 0x73, 0x74,
-	0x32, 0xa9, 0x01, 0x0a, 0x10, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x53, 0x65,
-	0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x49, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x24, 0x2e, 0x74,
-	0x76, 0x69, 0x78, 0x2e, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x47,
-	0x65, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65,
-	0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72,
-	0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x30, 0x01,
-	0x12, 0x4a, 0x0a, 0x03, 0x50, 0x75, 0x74, 0x12, 0x1a, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63,
-	0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74,
-	0x6f, 0x72, 0x79, 0x1a, 0x25, 0x2e, 0x74, 0x76, 0x69, 0x78, 0x2e, 0x63, 0x61, 0x73, 0x74, 0x6f,
-	0x72, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x74, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f,
-	0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x42, 0x2c, 0x5a, 0x2a,
-	0x63, 0x6f, 0x64, 0x65, 0x2e, 0x74, 0x76, 0x6c, 0x2e, 0x66, 0x79, 0x69, 0x2f, 0x74, 0x76, 0x69,
-	0x78, 0x2f, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73,
-	0x3b, 0x63, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
-	0x6f, 0x33,
-}
-
-var (
-	file_tvix_castore_protos_rpc_directory_proto_rawDescOnce sync.Once
-	file_tvix_castore_protos_rpc_directory_proto_rawDescData = file_tvix_castore_protos_rpc_directory_proto_rawDesc
-)
-
-func file_tvix_castore_protos_rpc_directory_proto_rawDescGZIP() []byte {
-	file_tvix_castore_protos_rpc_directory_proto_rawDescOnce.Do(func() {
-		file_tvix_castore_protos_rpc_directory_proto_rawDescData = protoimpl.X.CompressGZIP(file_tvix_castore_protos_rpc_directory_proto_rawDescData)
-	})
-	return file_tvix_castore_protos_rpc_directory_proto_rawDescData
-}
-
-var file_tvix_castore_protos_rpc_directory_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
-var file_tvix_castore_protos_rpc_directory_proto_goTypes = []interface{}{
-	(*GetDirectoryRequest)(nil),  // 0: tvix.castore.v1.GetDirectoryRequest
-	(*PutDirectoryResponse)(nil), // 1: tvix.castore.v1.PutDirectoryResponse
-	(*Directory)(nil),            // 2: tvix.castore.v1.Directory
-}
-var file_tvix_castore_protos_rpc_directory_proto_depIdxs = []int32{
-	0, // 0: tvix.castore.v1.DirectoryService.Get:input_type -> tvix.castore.v1.GetDirectoryRequest
-	2, // 1: tvix.castore.v1.DirectoryService.Put:input_type -> tvix.castore.v1.Directory
-	2, // 2: tvix.castore.v1.DirectoryService.Get:output_type -> tvix.castore.v1.Directory
-	1, // 3: tvix.castore.v1.DirectoryService.Put:output_type -> tvix.castore.v1.PutDirectoryResponse
-	2, // [2:4] is the sub-list for method output_type
-	0, // [0:2] is the sub-list for method input_type
-	0, // [0:0] is the sub-list for extension type_name
-	0, // [0:0] is the sub-list for extension extendee
-	0, // [0:0] is the sub-list for field type_name
-}
-
-func init() { file_tvix_castore_protos_rpc_directory_proto_init() }
-func file_tvix_castore_protos_rpc_directory_proto_init() {
-	if File_tvix_castore_protos_rpc_directory_proto != nil {
-		return
-	}
-	file_tvix_castore_protos_castore_proto_init()
-	if !protoimpl.UnsafeEnabled {
-		file_tvix_castore_protos_rpc_directory_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*GetDirectoryRequest); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_tvix_castore_protos_rpc_directory_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*PutDirectoryResponse); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-	}
-	file_tvix_castore_protos_rpc_directory_proto_msgTypes[0].OneofWrappers = []interface{}{
-		(*GetDirectoryRequest_Digest)(nil),
-	}
-	type x struct{}
-	out := protoimpl.TypeBuilder{
-		File: protoimpl.DescBuilder{
-			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
-			RawDescriptor: file_tvix_castore_protos_rpc_directory_proto_rawDesc,
-			NumEnums:      0,
-			NumMessages:   2,
-			NumExtensions: 0,
-			NumServices:   1,
-		},
-		GoTypes:           file_tvix_castore_protos_rpc_directory_proto_goTypes,
-		DependencyIndexes: file_tvix_castore_protos_rpc_directory_proto_depIdxs,
-		MessageInfos:      file_tvix_castore_protos_rpc_directory_proto_msgTypes,
-	}.Build()
-	File_tvix_castore_protos_rpc_directory_proto = out.File
-	file_tvix_castore_protos_rpc_directory_proto_rawDesc = nil
-	file_tvix_castore_protos_rpc_directory_proto_goTypes = nil
-	file_tvix_castore_protos_rpc_directory_proto_depIdxs = nil
-}
diff --git a/tvix/castore/protos/rpc_directory_grpc.pb.go b/tvix/castore/protos/rpc_directory_grpc.pb.go
deleted file mode 100644
index f19e457d86..0000000000
--- a/tvix/castore/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/castore/protos/rpc_directory.proto
-
-package castorev1
-
-import (
-	context "context"
-	grpc "google.golang.org/grpc"
-	codes "google.golang.org/grpc/codes"
-	status "google.golang.org/grpc/status"
-)
-
-// This is a compile-time assertion to ensure that this generated file
-// is compatible with the grpc package it is being compiled against.
-// Requires gRPC-Go v1.32.0 or later.
-const _ = grpc.SupportPackageIsVersion7
-
-const (
-	DirectoryService_Get_FullMethodName = "/tvix.castore.v1.DirectoryService/Get"
-	DirectoryService_Put_FullMethodName = "/tvix.castore.v1.DirectoryService/Put"
-)
-
-// DirectoryServiceClient is the client API for DirectoryService service.
-//
-// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
-type DirectoryServiceClient interface {
-	// Get retrieves a stream of Directory messages, by using the lookup
-	// parameters in GetDirectoryRequest.
-	// Keep in mind multiple DirectoryNodes in different parts of the graph might
-	// have the same digest if they have the same underlying contents,
-	// so sending subsequent ones can be omitted.
-	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.castore.v1.DirectoryService",
-	HandlerType: (*DirectoryServiceServer)(nil),
-	Methods:     []grpc.MethodDesc{},
-	Streams: []grpc.StreamDesc{
-		{
-			StreamName:    "Get",
-			Handler:       _DirectoryService_Get_Handler,
-			ServerStreams: true,
-		},
-		{
-			StreamName:    "Put",
-			Handler:       _DirectoryService_Put_Handler,
-			ClientStreams: true,
-		},
-	},
-	Metadata: "tvix/castore/protos/rpc_directory.proto",
-}