go-cask

CASK — Content-Addressable Store Kit

CI Go Reference License

CASK is a Git-like, content-addressable store for Go: bytes are keyed by their content digest, objects stay immutable, and typed application models sit on top of the generic core.

Table of contents

Design decisions

A single-host content-addressable store. Each named spec is the normative contract:

Repository layout

Core interfaces at a glance

cas layers a non-generic byte layer (Digest, Backend + backends) under a generic typed layer (Object[T], Codec[T], Store[T], Walker[T]), with caching wrappers on top. A store also carries the client’s Hasher — the algorithm seam. Apps build their own Object[T] models on Store[T].

classDiagram
    direction TB
    class Digest {
        +String() string
        +Equal(other Digest) bool
    }
    class Hasher {
        <<interface>>
        +Digest(r io.Reader) (Digest, error)
        +Validate(d Digest) error
    }
    class Backend {
        <<interface>>
        +Put(ctx, d, r) error
        +Get(ctx, d) io.ReadCloser
        +Exists(ctx, d) (bool, error)
        +Delete(ctx, d) error
        +List(ctx) ([]Digest, error)
        +Stats(ctx) (*Stats, error)
    }
    class Object~T~ {
        <<interface>>
        +Type() string
        +References() []Digest
    }
    class Codec~T~ {
        <<interface>>
        +Encode(v T) ([]byte, error)
        +Decode(data []byte) (T, error)
    }
    class Store~T~ {
        +Put(ctx, obj T) (Digest, error)
        +Get(ctx, d) (T, error)
        +Delete(ctx, d) error
    }
    class Walker~T~ {
        +Walk(ctx, d) error
    }
    Store~T~ o-- Backend : raw
    Store~T~ o-- Codec~T~ : codec
    Store~T~ o-- Hasher : hasher
    Store~T~ ..> Object~T~ : stores
    Walker~T~ ..> Store~T~ : reads via Get

cas stays hash- and codec-agnostic by design, but the repo recommends a practical default policy for new durable data:

Legacy or compatibility-only hashes should not be used for new content-addressed data: MD5 and SHA-1 are migration-only or compatibility choices, not the default for a CAS.

Security note

Use cryptographic hashes for object identity and integrity. For new data, prefer SHA-256 or SHA-512/256. Do not use MD5 or SHA-1 for new content-addressed data, even when a legacy system still emits them; they are not recommended for new objects or new interoperability contracts.

Upgrading

Current patch release: v1.4.2. This maintenance release documents the default policy as SHA-256 + flate compression for durable payloads, refreshes the benchmark guidance to keep benchmark winners distinct from the project default, and keeps the canonical benchmark matrix in JSON for review and future analysis.

v1.3.0 is a breaking MINOR: the core is hash-agnostic (cas.Hashcas.Digest + a client-injected cas.Hasher), gitlike.NewRepository takes a gitlike.Codecs set, object invariants moved to cas.Validator, and the filesystem layout lost its algorithm directory. Read the [v1.3.0] section of CHANGELOG.md and docs/specs/operations.md §5 before pointing this build at an existing store — objects written by v1.2.0 are not migrated.

Quick start

import (
    fs "github.com/dmundt/go-cask/cas/backend/fs" // or use the mem backend
    "github.com/dmundt/go-cask/cas"
    jsoncodec "github.com/dmundt/go-cask/cas/codec/json"
    sha256 "github.com/dmundt/go-cask/cas/hash/sha256"
    "github.com/dmundt/go-cask/gitlike"
)

raw, _ := fs.New("./objects")  // backend
// typed layer: the client supplies both the hasher and the codecs, so the
// repository names neither the algorithm nor the wire format.
repo := gitlike.NewRepository(raw, sha256.New(), gitlike.Codecs{
    Blob:   jsoncodec.New[*gitlike.Blob](),
    Tree:   jsoncodec.New[*gitlike.Tree](),
    Commit: jsoncodec.New[*gitlike.Commit](),
    Tag:    jsoncodec.New[*gitlike.Tag](),
})
d, _ := repo.Blobs.Put(ctx, andgitlike.Blob{Data: []byte("hello")})
blob, _ := repo.Blobs.Get(ctx, d)                 // *gitlike.Blob

For tests/ephemeral use, swap the backend:

mem "github.com/dmundt/go-cask/cas/backend/mem" // declares package memory
raw := mem.New() // fast, deterministic, not persistent

The specification set

docs/specs/ is the complete design contract: core architecture, coding guidelines, library design, performance, testing, consistency (GC/pruning), viewer HTTP surface, viewer design and security, versioning, defaults, examples, and extensions.

Note: the documentation tree under docs/ follows the OKF frontmatter layout (type, title, description, version for each document, with docs/index.md as the top-level rule index).

Key references:

Use docs/index.md to find the matching spec for a change area.

Building and testing

go build ./...
go vet ./...
go test -race ./...
gofmt -l .

Requires Go 1.27 (toolchain self-managing; library baseline Go 1.24+, needed for the omitzero JSON tags used by cas.Digest reference fields). See CONTRIBUTING.md for the workflow, and benchmarks/README.md for running/reading the benchmarks.

License

MIT — see LICENSE. Copyright (c) 2026 Daniel Mundt.