Documentation
¶
Overview ¶
Package snapshotcache stores derived saga state outside every saga.
Building the state a reviewer request needs — the loaded document, the Git comparison, and the coverage report over it — costs a saga load, two `git diff` invocations, and a full coverage evaluation. None of that changes while the bytes it was built from do not, so it is worth keeping. Keeping it is only safe under four rules, and this package exists to hold all four in one place rather than spread across the handlers that benefit from them.
**The key is the fingerprint.** A generation is addressed by the exact inputs it was derived from, so there is no invalidation step that could be forgotten or run late. Changed inputs produce a different address, which misses. A miss is always safe; a hit can never be stale, because a hit means the inputs are byte-identical to the ones that produced it. Inputs that cannot be described exactly — a `WORKTREE` head, whose content no cheap probe pins down — make the key invalid, and an invalid key is never stored and never found.
**Creation is atomic.** A generation is populated in a staging directory and published with a single rename, so a reader either sees a complete generation or sees nothing. A failed or abandoned build leaves no directory behind, and a reader never observes one being written.
**The cache is disposable.** Deleting any part of it, at any time, changes only how long the next answer takes and never what the answer is. Nothing is stored here that cannot be rebuilt from the saga and the source checkout.
**The cache is never inside a saga.** A saga is a Git-native directory that people merge. Derived bytes in it would be committed by accident, would conflict on every rebuild, and would be an opaque binary artifact in a format built to be reviewed as text. Generations live under the user's cache directory, keyed by the saga's absolute path, so the saga tree is untouched and merge behaviour is unchanged.
This package deliberately stores directories rather than a database. What a request needs is a small, named part of the derived state — the atoms for one file, the rollup for one target — and a directory of independently readable parts serves that with one os.ReadFile, keeping resident memory proportional to what was asked for rather than to the size of the comparison. A query engine would add a dependency, a second durability model, and lock and journal files that must be kept out of the saga, and would buy nothing that addressing and partitioning do not already give.
Index ¶
Constants ¶
const DirEnv = "CHANGE_SAGA_CACHE_DIR"
DirEnv redirects the cache root. Tests set it so they never touch the user's real cache, and it gives an operator a way to place generations on a different filesystem.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Key ¶
Key addresses one generation of derived state.
Saga is the absolute path of the saga the state describes; it separates unrelated sagas that would otherwise share a cache. Tree and Source are the two fingerprints the reviewer server already computes on every request: the saga's own bytes, and the identity of the exact source comparison. Either one empty means the input could not be described exactly, which makes the whole key invalid rather than risking a stale review.
type State ¶
type State int
State is what a handler needs in order to answer a request that arrives while the derived state does not yet exist.
const ( // Absent means nothing is cached and nobody is building it. Absent State = iota // Building means another request is building this exact generation. A // handler can answer with progress instead of blocking or starting a second // build of the same work. Building // Ready means a complete generation is on disk. Ready )
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is a rooted collection of generations.
func Default ¶
Default roots a store under the user's cache directory, or under DirEnv when it is set. It follows the placement the CLI already uses for detached server state, which keeps every derived artifact this tool writes in one predictable place outside every saga.
func Open ¶
Open roots a store at an explicit directory. The directory is created if it does not exist.
func (*Store) Build ¶
Build returns the directory of a complete generation, creating it if needed.
populate receives an empty staging directory and writes the derived state into it. It runs at most once per generation per process: concurrent callers for the same key wait for the first one and share its result, so a burst of requests after a change performs the work once rather than once per request. If populate returns an error, nothing is published and the error is returned to every waiter.
An invalid key builds into a caller-owned temporary directory that is removed before returning, so an uncacheable saga still gets its state without leaving anything behind. The returned directory is empty in that case and the boolean reports that the result was not cached.
func (*Store) Discard ¶
Discard removes every generation of one saga. It is always safe: the next request rebuilds, and rebuilding is what produced these bytes in the first place.
func (*Store) Lookup ¶
Lookup returns the directory of a complete generation for the key. A miss is reported for an invalid key, for a generation that was never built, and for one whose manifest does not name this key — the last of which means the directory is damaged rather than merely absent, and is treated the same way, because a rebuild is always correct.
func (*Store) Prune ¶
Prune keeps the newest generations of one saga and removes the rest, along with any staging directory an interrupted build left behind. Generations are ordered by modification time, so the ones a reviewer is switching between survive and the ones superseded long ago do not.
keep below one is refused rather than silently emptying the bucket: removing every generation is what Discard is for, and a caller that computed keep from a configuration value should hear about a zero.