cache

package
v0.3.0-alpha.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 23, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Overview

Package cache is the content-hash finding cache (PRD §19, optimization 2). Each analysed file's RAW output — findings and mapped surface, before path criticality is applied — is stored under a key naming the analyzer, the file's path and the file's bytes. A hit means the same analyzer already analysed exactly those bytes at exactly that path, so a recurring full scan costs about as much as an incremental one.

That affordability is the point, not speed for its own sake: the full scan is the honest one — the only scan that can prune the baseline, and the only one whose "not blocked" means what it appears to mean. If the full scan were expensive and the narrowed one cheap, every caller would narrow, and codefit would degrade into a tool that permanently looks through a slit.

Two invariants govern everything here:

  • A warm scan and a cold scan are IDENTICAL, not merely equivalent. That is why an Entry carries the surface as well as the findings, and why the key names the file's path as well as its bytes.
  • The cache is never the reason an audit does not happen. A missing, unreadable or corrupt entry is a miss; a failed write is a note.

Entries are stored by GENERATION — Dir/<generation>/<key>.json, where the generation labels the analyzer that wrote them. That is what makes the store collectable: because the analyzer identity is part of every key, each codefit build orphans the whole previous generation at once, so the unit that has to be droppable is a generation and not an entry. Open prunes, once per process: the current generation always survives, along with the two most recently modified others, and entries in the current generation that have not been written in 30 days are collected.

The prune DELETES FILES, so it only ever recognises the two shapes this package writes itself — a generation directory of 16 hex characters and an entry file of a 64-hex key. Anything else under Dir belongs to whoever put it there and is never touched at any age. It is also best effort and reports nothing: a cache that cannot clean itself still has to work.

It is wired into the security sensor's walk, consulted per file, and OPT-IN: a project with no cache: section in .codefit.yaml has it off. The database dimension is deliberately not cached — its inputs are configured schema paths rather than a walk, and a schema reconstructed from an ordered set of migrations does not obviously invalidate per file.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Identity added in v0.2.7

func Identity() (string, error)

Identity is the running analyzer's identity: the hex SHA-256 of the executable codefit is running as, computed once per process and memoized.

The analysis of a file is a pure function of (file bytes, analyzer), so a cache key must name both. The obvious alternative — internal/version.Version — does not work and fails exactly where it matters most: it is the constant "v0.1.0-dev" for any plain go build, go run or go test, so during development, where the rules change several times an hour, every build would present the same key and the rule author would be the first person a stale entry bit.

Hashing the binary covers EVERY input that can change a verdict — the YAML rules, the Go-coded detectors, the provider's parser, the surface queries — because all of them are in the binary. Under go run and go test the executable is a fresh temporary build, so editing a rule changes the identity automatically. Two builds of identical source produce different binaries and therefore a miss: wasted work, never a stale verdict, which is the safe direction.

The cost is one SHA-256 of a ~5 MB binary, once. Against a walk that parses hundreds of files it does not register.

An error means the identity is UNKNOWN, and an unknown key input means do not reuse: callers disable caching for the run rather than guess.

Types

type Cache

type Cache struct {
	Dir      string
	Analyzer string
}

Cache stores one Entry per (analyzer, file, content) under Dir. A hit means the same analyzer already analysed exactly these bytes at exactly this path, so its output can be reused instead of recomputed (PRD §19, optimization 2).

Analyzer is the identity of the binary whose rules produced the entries — see Identity. Build a cache with Open to bind it to the running analyzer. A Cache with an empty Analyzer produces no keys at all, so it can never read or write: an unknown analyzer means do not reuse.

func Open added in v0.2.7

func Open(dir string) (*Cache, error)

Open builds a cache under dir bound to the RUNNING analyzer's identity. It fails when that identity cannot be resolved, which is the caller's signal to scan without a cache for this run rather than key on the file alone.

Opening also PRUNES, once per process per generation: superseded generations, stale entries and the flat entries of the pre-generation layout are collected here. The prune is best effort and reports nothing — see [Cache.prune]. It is done on Open rather than on a schedule because the store only ever grows between runs, and Open is the moment codefit knows which generation is the one in use.

func (*Cache) Get

func (c *Cache) Get(key string) (Entry, bool)

Get returns the entry stored under key and whether it was present. A missing, unreadable or corrupt entry is a MISS, never an error: the caller analyses the file normally. The cache may never be the reason an audit does not happen.

An entry that was stored EMPTY is a hit. A file that produced no findings and no surface is the ordinary case in a healthy repository, and re-analysing it forever would make the cache do nothing where it matters most. That is precisely why an entry has to PROVE it is one: parsing is not provenance, and an unproven empty hit is a clean verdict codefit never computed (ADR 0053).

So the payload must name this key. Anything else — a stray {} an editor or a sync tool left in .codefit/cache, a half-restored backup, a well-formed entry sitting at another key's path, an entry written before the stamp existed — is a miss, and a miss is just an ordinary analysis. The check does not enumerate the shapes that can go wrong; it rejects everything that cannot answer for itself, which is the only version of that question with a complete answer.

func (*Cache) Key added in v0.2.7

func (c *Cache) Key(file string, content []byte) string

Key is the entry key for a file: the SHA-256 of the analyzer identity, the file's project-relative path and the file's content.

All three are named because all three can change the answer (spec R2):

  • the ANALYZER, because the rules, the detectors, the parser and the surface queries all live in the binary. Keying on the file alone would let a codefit upgrade report "clean" under rules it never ran.
  • the PATH, because every finding and surface item carries its File and a fingerprint derived from it. Two files with identical bytes are a real and ordinary thing; sharing one entry between them would report the first file's path for the second and break R1.
  • the CONTENT, which is what the cache exists to notice.

It returns "" when the cache has no analyzer identity, so a cache that does not know what produced its entries silently reads and writes nothing.

func (*Cache) Set

func (c *Cache) Set(key string, e Entry) error

Set stores an entry as a JSON file under key. The error is returned rather than swallowed so the caller can report it; it is never a reason to fail a scan.

The write is ATOMIC — a temp file in the same directory, then a rename, the same shape the committed baseline uses. codefit is an MCP server, so two tools over one project (an agent firing scan-security and scan-all together) can reach the same entry path at once, and os.WriteFile truncates before it writes. A torn entry degrades safely on read (invalid JSON is a miss), but it degrades into re-analysing the file the cache exists to skip, and a crash mid-write would leave that behind permanently.

Set STAMPS the key into the entry before marshalling, so what it writes can prove to a later reader whose answer it is. The caller's e.Key is overwritten rather than trusted: the key an entry claims is the key it was stored under, never a value the caller supplied alongside a different one.

type Entry added in v0.2.7

type Entry struct {
	// Key is the key this entry is the answer to. Written by Set, verified by
	// Get, which returns a miss when it does not match the key it was asked
	// for. Without it, any syntactically valid JSON that simply lacks these
	// fields — null, {}, {"unrelated":1} — unmarshals into a zero Entry and is
	// served as a HIT, and a hit with no findings and no surface asserts that
	// this analyzer analysed exactly these bytes and found nothing. That is the
	// cache manufacturing a clean verdict for a file nothing ever read.
	Key      string                 `json:"key"`
	Findings []findings.Finding     `json:"findings"`
	Surface  []findings.SurfaceItem `json:"surface"`
}

Entry is the RAW output of one file's analysis: everything the sensor computes for that file, before any severity is adjusted by path criticality.

It holds BOTH halves on purpose. The sensor's per-file analysis returns findings AND mapped surface, so an entry that stored only the findings would serve a warm scan that silently lost the surface — a cache that can change the output is not a cache, it is a blind spot (spec R1/R3).

Storing the PRE-criticality output is likewise not an implementation detail: path criticality is applied by the sensor after the per-file analysis returns, so a cached entry survives an edit to .codefit.yaml's path_criticality and the next scan re-weights severities without invalidating a single entry. Caching the adjusted findings would serve stale severities after every config edit.

The entry is SELF-DESCRIBING: Key names the key it was stored under, stamped by Cache.Set and verified by Cache.Get. It is not a second addressing scheme — nothing reads a path out of an entry — it is the entry's proof of provenance, because VALID JSON IS NOT ONE (ADR 0053).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL