sqlite

package
v0.0.0-...-ea53456 Latest Latest
Warning

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

Go to latest
Published: Oct 10, 2026 License: Apache-2.0 Imports: 44 Imported by: 0

Documentation

Overview

Package sqlite is the one store of Section 12: it owns SQL, unit publication, generation pinning, retention metadata and transactions. It imports only internal/model and the pinned driver; no provider, CLI or config package.

Index

Constants

View Source
const MaxTermOffsets = 512

MaxTermOffsets bounds the offsets one TermOccurrence carries.

View Source
const ReasonNotFound = "not_found"

ReasonNotFound is the Details["reason"] value that marks a lookup which found no row, so a caller can tell an absent snapshot or file from a malformed argument under the shared CTX_ARGUMENT_INVALID code.

Variables

View Source
var Fingerprint = func() string {
	sum := sha256.Sum256([]byte(schemaSQL))
	return hex.EncodeToString(sum[:])
}()

Fingerprint is the SHA-256 of the embedded DDL text. It is stored in schema_meta and folded into every AnalysisKey (Section 9.1).

Functions

func IsGenerationCollected

func IsGenerationCollected(err error) bool

IsGenerationCollected reports whether err is a pin refused because the generation it named has been collected.

func NewGraphReader

func NewGraphReader(ctx context.Context, r *PinnedReader) (graph.GraphReader, error)

NewGraphReader opens the packed adjacency of the reader's pinned generation. A generation with no generation_graph row was never published with one, which is a corrupt store rather than an empty graph: the row is written last, so its absence means the build did not finish.

func OwnerLeaseConflict

func OwnerLeaseConflict(err error) bool

OwnerLeaseConflict reports whether err is that refusal, so an idempotent acquire can treat the lease the owner already holds as its own answer.

func SetTempDir

func SetTempDir(dir string) error

SetTempDir names the directory the engine creates its temporary files in for the rest of the process: the spill files of a sort that outgrows its cache, the statement journal of a savepoint past statementJournalSpillBytes, and the temporary tables of a query too large for memory. The engine's own default is the first writable one of the process temp directory and the system's, which on a host whose temp directory is a memory filesystem turns a sort of a large table into a memory allocation of its size. The product calls this once at start-up with a directory under its data directory, so every temporary file lives on the disk the user gave the data and is bounded by that disk, not by memory.

The directory is a process-wide engine setting. It is set through a throwaway connection, before the store or any other database of the process is opened, and never changed afterwards: the engine reads it without a lock whenever it opens a temporary file.

It is also where the pool of those temporaries lives. The file-system shim serves the engine's unnamed delete-on-close opens from this directory's scratch arena, so what the engine would create and unlink once per sort is taken and given back instead; the directory the engine is told about is the fallback for a process that reaches a temporary before a store is opened.

Types

type AliasTable

type AliasTable struct {
	// contains filtered or unexported fields
}

AliasTable is every alias row of a unit's declared dependencies, loaded in one read so resolving a unit's candidates costs no read per candidate. It is the one alias path: the index build and the provider test harness both resolve through it. It is immutable once loaded and safe for concurrent use.

The heap holds the aliases of the declared dependencies and nothing else. A unit declares at most its own file's filesystem unit, whose aliases are that file's path chain, the file and its classified node, so the table is sized by one path's depth, never by the repository.

func (*AliasTable) LookupAliases

func (t *AliasTable) LookupAliases(_ context.Context, units []model.UnitID, scopeKey, nativeKey string) ([]StoredAlias, error)

LookupAliases returns every distinct identity that (scopeKey, nativeKey) is aliased to by the given units, from the loaded table: a copy of the key's identities ordered by canonical key and then node id, which depends only on the sealed units' alias rows and never on the order they were written.

The table holds one merged list per key, so it can answer only the whole set it was loaded for. A unit set that differs from it, by a unit the table never read or by one it read and the caller left out, is refused as an internal fault: the answer would carry aliases of rows the caller did not name, or miss aliases of rows it did.

type CarriedUnit

type CarriedUnit struct {
	Unit                 model.UnitID
	ProviderID, ScopeKey string
	Carry                Carry
}

CarriedUnit is one stale member of a generation with its provenance distance, which is what the coordinator projects into the capability report.

type Carry

type Carry struct{ DistanceGenerations, DistanceFiles int }

Carry is how far behind a carried unit is: the number of generations, and the number of changed files, between the unit's snapshot and the one the generation is being built over. Both surface as the provenance distance on the capability's state (Section 13.3).

type CarryOverStats

type CarryOverStats struct {
	Nodes       int64
	Relations   int64
	Aliases     int64
	SearchUnits int64
	Evidence    int64
}

CarryOverStats reports what a carry-over inherited. The Section 11.1 bound on evidence per fact is not applied here: SealUnit applies it to every unit, delta-built or not, so the two cannot diverge.

type CoverageCounts

type CoverageCounts struct {
	Required  int64
	FullyRead int64
	Served    int64
	Waived    int64
}

CoverageCounts is the one coverage aggregate's whole answer, for one actor's session, over the required_full files in scope. It is a struct rather than four positional int64s because Served and FullyRead differ only by the waiver clause: a transposed pair would read as a plausible count and silently answer a different question.

FullyRead is read completeness (Section 17.1) -- every required file fully confirmed at its pinned hash, whether or not it also carries a waiver. Served is the reported fully_served_files: fully confirmed AND not waived. Waived is the waiver count, audited beside coverage and never as coverage, so strict readiness -- which no waiver ever grants -- stays a separate question (Section 17.3).

type ExcludedEntries

type ExcludedEntries func(yield func(model.ExcludedContextEntry) error) error

ExcludedEntries is a manifest's exclusion projection as a push sequence: it yields every excluded entry in ordinal order and returns the first error yield reported, or its own read failure, unchanged.

It must be RESTARTABLE -- callable more than once, yielding the identical sequence each time -- because the compiler folds the canonical manifest hash over the same exclusions it then writes, and a one-shot cursor would hash a sequence and persist an empty one. A sorted run over a spooled projection satisfies this; a channel drained by the first caller does not. A nil sequence is a manifest with no exclusions.

type NodeFilter

type NodeFilter struct {
	Name            string
	QualifiedName   string
	QualifiedPrefix string
	Kinds           []model.NodeKind
}

NodeFilter selects nodes by exact name, exact qualified name or qualified name prefix (indexed range scan), optionally restricted to kinds.

type OccurrenceStream

type OccurrenceStream struct {
	// contains filtered or unexported fields
}

OccurrenceStream pulls one term's posting list in page-sized refills from a single statement, so peak memory is one page no matter how frequent the term is. A refill always ends on a document boundary, with the first row of the next document carried as lookahead: cutting a document in half would split its (column, offsets) groups and understate Count.

func (*OccurrenceStream) Close

func (s *OccurrenceStream) Close() error

Close releases the statement. Safe to call more than once.

func (*OccurrenceStream) Next

func (s *OccurrenceStream) Next(ctx context.Context, limit int) ([]TermOccurrence, error)

Next returns the next refill: the grouped occurrences of whole documents, at least limit rows unless the stream is exhausted, in the same (document, column, offset) order the former per-page statement produced. A nil result with a nil error means the stream is exhausted.

type Options

type Options struct {
	BusyTimeout time.Duration // busy_timeout, default 5s
	// ReadConnections sizes the short-read pool: unit states, dependency
	// aliases and every query's short reads.
	ReadConnections int
	// PostingConnections sizes the posting-stream pool, which only a query's
	// candidate walk reads (OpenPostings).
	PostingConnections int
	WriterCacheKiB     int   // writer_cache_kib, default 1 GiB; also the ingestion group bound
	ReaderCacheKiB     int   // reader_cache_kib, default 4096
	BatchRecords       int   // index.batch_records, default 1000
	BatchBytes         int64 // index.batch_bytes, default 4 MiB
	// MaxJSONBytes bounds every stored JSON column that has no tighter model
	// ceiling (manifest requests, capsules). Default 8 MiB, the Section 20.1
	// context.max_manifest_bytes / max_capsule_bytes default.
	MaxJSONBytes int64
	// MaxEvidencePerFact is the effective per-fact evidence clip the providers
	// of this process emitted under: index.max_evidence_per_fact when the
	// operator set one, otherwise the model's record ceiling. Seal enforces it
	// over the union of fresh and carried occurrences, so a delta cannot leave
	// a fact holding more than the run's providers were allowed to publish.
	// Zero selects the record ceiling, which is what unlimited means here.
	MaxEvidencePerFact int
	// Synchronous is the synchronous mode of the WRITER connection:
	// "normal" (the default, and what config.SynchronousNormal spells) or
	// "full". Readers are pinned to FULL regardless -- they are query_only
	// and never write, so the mode is behaviourally inert for them, and
	// pinning it keeps every pooled connection's pragma set verified.
	// An empty value selects the default; an unrecognized value fails Open.
	// See docs/adr/ADR-0004-wal-synchronous-mode.md.
	Synchronous string
	// ReadOnly opens the store for a process that answers questions and
	// changes nothing. No writer connection is opened at all, so this process
	// can neither wait on the write transaction another process holds nor
	// delay it: the reader pool is `query_only` and reads a write-ahead log
	// snapshot, which never blocks on a writer. Every connection is opened
	// read-only at the FILE level besides, which is what query_only does not
	// do: see openPool, and the workspace such a process would otherwise
	// rewrite when it closed. A database that is not there is therefore not
	// created; it is the workspace nothing has published, reported as such. The schema fingerprint is
	// verified by reading instead of inside a write transaction, and every
	// mutating entry point refuses with CTX_INTERNAL, because a command that
	// mutates must be composed with the writer rather than discover at runtime
	// that it cannot.
	ReadOnly bool
	// LazyWriter opens a writing store that performs no write at all until
	// something actually writes. The writer pool is built, but database/sql
	// establishes no connection until a statement runs on it, so the only
	// write a writing open ever performed -- the schema check inside an
	// immediate transaction -- is what this removes: the schema is verified by
	// READING, and created only when the cache holds no tables at all, which
	// is a cache no run has ever built here and therefore one no run can be
	// holding a transaction against.
	//
	// It exists for the process that both answers and indexes: it must come up
	// and serve beside an index another process is already running, and a
	// schema check in a write transaction queues behind that run's ingestion
	// group and is refused `database is busy: begin` when the busy timeout
	// runs out. Ignored when ReadOnly is set: that store has no writer.
	LazyWriter bool
}

Options are the storage settings of Section 20.1 `[storage]` plus the `[index]` batch bounds the store enforces on unit writes. For the numeric settings zero selects the documented default; no value means unlimited. The two connection counts have no default: they are derived from the work that reads each pool (config.ReadConnections, config.PostingConnections), which only the caller can see, and Open refuses a store opened without them.

type PackedStream

type PackedStream struct {
	// contains filtered or unexported fields
}

PackedStream merges one term's posting lists across the generation's segments by document rowid, in page-sized refills. A refill always ends on a document boundary, so a document's (column, count) groups are never split across two refills, and the rowids it delivers strictly ascend -- the order the candidate walk requires.

func (*PackedStream) Close

func (s *PackedStream) Close() error

Close releases the stream. It holds no statement; the method exists so a packed stream and a live one are the same contract to the caller.

func (*PackedStream) Next

func (s *PackedStream) Next(ctx context.Context, limit int) ([]TermOccurrence, error)

Next returns the next refill: the grouped occurrences of whole documents, at least limit rows unless the lists are exhausted. A nil result with a nil error means they are exhausted.

type PageClamps

type PageClamps = pagination.PageClamps

The page-clamp collector lives in internal/pagination, beside the cursors its notice tells the caller to continue with, so a package that drains one into its answer -- the graph engine, the search service, the coverage session -- does not have to import the store for a type the store only writes into.

PageClamps and WithPageClamps are re-exported because this package's callers install a collector on the context they hand to the store and drain it into their answer's notices, so they name the collector type without importing the pagination package for it alone.

func WithPageClamps

func WithPageClamps(ctx context.Context) (context.Context, *PageClamps)

WithPageClamps returns a context that collects page-bound clamps and the collector to read them from once the reads are done.

type PinnedReader

type PinnedReader struct {
	// contains filtered or unexported fields
}

PinnedReader reads exactly one generation. A writer-bearing process pins it together with its retention lease in one short write transaction, so the generation cannot be collected while the reader is open; a process that opened the store read-only holds it by snapshot instead, one deferred read transaction per call. Every read joins visible membership through generation_units; staging, failed and non-member units are never returned. Close releases the lease, if there is one.

func (*PinnedReader) Binding

func (r *PinnedReader) Binding() model.Binding

Binding is the generation every result from this reader is qualified by.

func (*PinnedReader) Capabilities

func (r *PinnedReader) Capabilities(ctx context.Context) ([]model.CapabilityState, error)

Capabilities returns the generation's stored capability report -- every row of it. The bare `LIMIT 256` this once carried silently dropped rows off the tail of a large generation's report, and QueryMeta.Completeness is derived from what this returns on every answer, so the drop made every answer over-claim. The scan streams in one read transaction under the primary key's order, but the ANSWER is the whole report and so is the peak: every row is decoded into the returned slice and there is no page API here. That is deliberate rather than an omission -- the report is written pre-folded. The index publishes it through capabilityReport.finish, which collapses the rows to one per (provider, capability, scope) in foldCollapsed and bounds what survives in boundStates (internal/index/status.go), so a generation's stored report is bounded by the provider/capability/scope product and not by the repository. A page API would page a set whose size the writer already fixed.

func (*PinnedReader) ChangedFiles

func (r *PinnedReader) ChangedFiles(ctx context.Context, after model.FileID,
	statuses []model.FileStatus, limit int) ([]model.FileVersion, error)

ChangedFiles is Files with one predicate: only the snapshot rows whose status is one of statuses are returned, keyset-paged by file id exactly as Files is. It exists so a caller that wants the captured working-tree changes of a 250,000-file workspace does not have to read every file row to find them; the status column is part of the snapshot_files row the same query already scans, so this is a filter, not a second file listing.

An empty statuses is a caller asking for nothing and is answered with nothing, never with every row: a status filter that silently becomes "no filter" would turn the bounded read this exists to provide back into the full scan.

func (*PinnedReader) Close

func (r *PinnedReader) Close() error

Close releases the lease. It is safe to call more than once.

func (*PinnedReader) Containers

func (r *PinnedReader) Containers(ctx context.Context, kinds []model.NodeKind,
	after model.NodeID, limit int) ([]StoredNode, error)

Containers pages the visible CONTAINER nodes of the pinned generation by keyset on node_id, restricted to kinds. It is the repository map's enumeration: Nodes above demands exactly one of name, qualified name or qualified-name prefix, and a map has no such seed -- it asks for the containers themselves.

One row per node, chosen by nodePrecedence inside the grouped subquery exactly as NodesByID does, so the outer LIMIT counts NODES and not facts. That is load-bearing rather than tidiness: node_facts holds one row per (node, unit), and a container published by two units would otherwise make a full page come back short, which the caller reads as the end of the map -- a repository silently missing its remaining packages.

Visibility is the generation's: memberOf restricts every fact to a unit this generation selected, so a failed or unsealed unit's containers are invisible.

func (*PinnedReader) Continuable

func (r *PinnedReader) Continuable() bool

Continuable reports whether a query served through this reader may record a retention LEASE. A process that opened the store read-only cannot: it still hands back a continuation -- the spool is a filesystem write and the token is signed, not stored -- but that continuation names no lease and the state it names is reclaimed by the expiry its header carries instead.

func (*PinnedReader) DistinctNodesInFile

func (r *PinnedReader) DistinctNodesInFile(ctx context.Context, file model.FileID, afterStart int64, after model.NodeID, limit int) ([]StoredNode, error)

DistinctNodesInFile is NodesInFile restricted to ONE row per node: the row of the unit the Section 9.4 precedence order picks for that node, at that unit's offset. It exists because a retrieval tier that emits candidates by identity must not emit one node twice, while document-symbols keeps every declared offset; the two readings are separate methods so neither narrows the other.

node_facts is keyed (unit_id, node_id), so a node at two offsets in one file is always two UNITS disagreeing about where it is declared, never two declarations of one unit. Taking the precedence winner's row -- offset and attributes from the same unit -- is what Nodes and Containers already do for the same disagreement (query.go:252, query.go:297), so an identity read of a node reports the same byte range whichever of them serves it.

The order and the (start_byte, node_id) keyset are NodesInFile's, unchanged: each node now appears at exactly one offset, so the key stays total and exact across pages.

func (*PinnedReader) DocumentFrequency

func (r *PinnedReader) DocumentFrequency(ctx context.Context, terms []string) ([]int64, error)

DocumentFrequency returns, per term in order, how many visible documents contain it. It is EXACT: a segment every one of whose units the generation carries answers from its directory entry, and a segment holding documents the generation hides is counted by walking that term's posting list against the visible bitmap. An approximate frequency that counted hidden documents would make a score depend on the store's history, so two stores holding the same code would rank the same query differently.

It bounds the term count itself at nothing: a term is one binary search per segment, which reads one fixed-width entry and one term text per probe and frees both. How many terms one request may carry is the search tier's decision, under resources.max_query_terms.

func (*PinnedReader) Evidence

func (r *PinnedReader) Evidence(ctx context.Context, node model.NodeID, relation model.RelationID, after model.EvidenceID, limit int) ([]StoredEvidence, error)

Evidence pages the visible evidence for one node or one relation, keyset on evidence id. Exactly one subject must be given.

func (*PinnedReader) EvidenceBatch

func (r *PinnedReader) EvidenceBatch(ctx context.Context, relations []model.RelationID, perRelation int) (map[model.RelationID][]StoredEvidence, error)

EvidenceBatch returns the visible evidence backing each relation in one round trip, at most perRelation rows per relation in evidence-id order. The per- relation cap is applied inside SQL, so one heavily evidenced relation cannot consume the whole batch and starve the relations after it. perRelation is an internal hydration cap rather than a frozen request field, so zero takes the model.MaxPageItems default the rest of the reader uses.

func (*PinnedReader) File

File returns the pinned snapshot's manifest row for id.

func (*PinnedReader) FileByPath

func (r *PinnedReader) FileByPath(ctx context.Context, path string) (model.FileID, error)

FileByPath resolves a normalized path to its file identity; unknown is CTX_ARGUMENT_INVALID, not an empty result.

func (*PinnedReader) Files

func (r *PinnedReader) Files(ctx context.Context, after model.FileID, limit int) ([]model.FileVersion, error)

Files pages the pinned manifest by keyset on file_id. This is the one paginated source of truth for public file listings (Section 10.1).

func (*PinnedReader) FilesByID

func (r *PinnedReader) FilesByID(ctx context.Context, ids []model.FileID) ([]model.FileVersion, error)

FilesByID hydrates snapshot file metadata for a bounded batch of ids in one query (len(ids) <= model.MaxPageItems). Ids absent from the pinned snapshot are omitted rather than erroring, exactly like NodesByID (adjacency.go:230). It exists because the budget pass needs Size/Path/Status for a selected set, and Files (keyset paging, query.go:452) and File (one row, query.go:429) would make that an N+1.

Rows come back ordered by file_id, which is the snapshot's primary key order and NOT the order of ids: the statement is one indexed probe, so a caller that needs its own order indexes the result by model.FileVersion.ID.

func (*PinnedReader) Match

func (r *PinnedReader) Match(ctx context.Context, expression string, after int64, limit int) ([]int64, error)

Match returns visible rowids whose indexed columns match the FTS5 expression, keyset by rowid. The caller owns query encoding: pass an expression built by the literal-text encoder, never raw user text (Section 14.2).

func (*PinnedReader) Node

func (r *PinnedReader) Node(ctx context.Context, id model.NodeID) (StoredNode, error)

Node returns the preferred visible fact for id, or CTX_ARGUMENT_INVALID when the node is not visible in this generation: a public NodeID is valid only within its binding (Section 9.1).

func (*PinnedReader) Nodes

func (r *PinnedReader) Nodes(ctx context.Context, f NodeFilter, after model.NodeID, limit int) ([]StoredNode, error)

Nodes pages visible node facts matching f by keyset on node_id. One row per node: when several units publish the same node the precedence order picks one. limit is capped at model.MaxPageItems.

func (*PinnedReader) NodesByID

func (r *PinnedReader) NodesByID(ctx context.Context, ids []model.NodeID) ([]StoredNode, error)

NodesByID hydrates a batch of node ids into their preferred visible facts, ordered by node id. Ids that are not visible in the pinned generation are absent from the result rather than an error: a traversal frontier legitimately contains edge endpoints whose node facts this generation does not publish.

func (*PinnedReader) NodesInFile

func (r *PinnedReader) NodesInFile(ctx context.Context, file model.FileID, afterStart int64, after model.NodeID, limit int) ([]StoredNode, error)

NodesInFile pages visible node facts declared in file, keyset on (start_byte, node_id), through idx_nodes_file. Every declared offset of a node is returned; DistinctNodesInFile is the one-row-per-node reading.

func (*PinnedReader) OpenPostings

func (r *PinnedReader) OpenPostings(ctx context.Context) (*PostingSession, error)

OpenPostings begins a posting session for this generation.

func (*PinnedReader) Relations

func (r *PinnedReader) Relations(ctx context.Context, node model.NodeID, direction model.Direction, kinds []model.RelationKind,
	after model.RelationID, limit int) ([]model.Relation, error)

Relations pages visible edges touching node in direction, keyset on relation_id. Reverse traversal uses the indexed target column.

func (*PinnedReader) SearchStats

func (r *PinnedReader) SearchStats(ctx context.Context) (documents, tokens int64, err error)

SearchStats returns the visible document count and total token length from the generation's packed term statistics, where activation recorded them.

func (*PinnedReader) SearchUnit

func (r *PinnedReader) SearchUnit(ctx context.Context, rowid int64) (model.SearchUnit, error)

SearchUnit reads one visible document by doc id. A posting is shared along a carry chain, but generation_units admits one unit per (provider, scope) and CarryOver refuses a predecessor from another, so at most one row of a chain is visible here and the doc id names one document. Body is always empty: the database stores no source body (ADR-0003 §2.1), and a caller that needs the text reads it from the content store over the returned file identity and byte range.

func (*PinnedReader) SearchUnitRowIDs

func (r *PinnedReader) SearchUnitRowIDs(ctx context.Context, after int64, limit int) ([]int64, error)

SearchUnitRowIDs pages the visible search document ids by keyset. The id is search_units.doc_id -- the search_fts rowid the document's text lives at -- which is what Match returns and what the vocabulary joins on.

type PostingSession

type PostingSession struct {
	// contains filtered or unexported fields
}

PostingSession is one read transaction dedicated to a query's term streams. Every stream it opens reads the same snapshot, and that snapshot is held for the whole candidate walk instead of being re-taken per page. It runs on the posting pool, never the reader pool, so a query that holds a session can still issue the short reads (Match) the same walk needs. Close rolls the transaction back and is safe to call more than once; closing it also closes every stream still open on it.

func (*PostingSession) Close

func (p *PostingSession) Close() error

Close ends the session. Cancelling or timing out the context the streams were opened with makes their statements fail and the caller unwind to this Close, which is what releases the connection back to the posting pool.

func (*PostingSession) PackedDocuments

func (p *PostingSession) PackedDocuments(ctx context.Context, rowids []int64) ([]SearchDocument, error)

PackedDocuments hydrates a page of candidates from the segments' packed per-document attribute streams (ADR-0007 Decision 2). It issues NO statement per candidate: the fields are read out of the same parts the query is already reading, through the session's window, so a page of 200 hits costs the parts its documents lie in rather than 200 index descents into the document table.

The answer is in the requested order, and a rowid the generation does not make visible is omitted rather than zeroed -- a document an inherited segment still holds but whose unit left the generation is not a document of this generation at all.

func (*PostingSession) TermCounts

func (p *PostingSession) TermCounts(ctx context.Context, term string) (*PackedStream, error)

TermCounts opens a stream over one single-token term's packed posting lists: one TermOccurrence per (document, column), documents ascending across the whole generation and a document's columns in column-name order, carrying the counts the scorer sums and no offsets. A phrase needs offsets and uses TermOccurrences instead.

func (*PostingSession) TermOccurrences

func (p *PostingSession) TermOccurrences(ctx context.Context, term string) (*OccurrenceStream, error)

TermOccurrences opens a stream over every visible instance of term, one row per (document, column), documents ascending. The stream lives until Close or until the session ends.

type Replaced

type Replaced struct {
	// Files are the per-path evidence buckets the import replaced or dropped.
	// Each must be a valid FileID.
	Files iter.Seq[model.FileID]
	// Scopes are the alias scope keys the import replaced or dropped. Each
	// must be non-empty and within model.MaxScopeKeyBytes.
	Scopes iter.Seq[string]
	// Keys are the producer fact keys the import replaced or dropped.
	// Each key must be a lowercase hex digest (model.ValidHexID); anything
	// else is refused.
	Keys iter.Seq[string]
	// IndexLevel drops the previous unit's index-level bucket: the facts and
	// evidence that name no file. Providers that republish every unlocated
	// fact on every run set it; a provider that republishes them only for the
	// documents it reparsed must not, or edges carried from untouched
	// documents lose their endpoints and the unit cannot seal.
	IndexLevel bool
}

Replaced names everything of the previous unit that the current import has already re-emitted or that no longer exists. Everything else is carried. Naming the complement rather than the survivors is deliberate: for both importers the replaced set is a handful of entries against tens of thousands of survivors.

All three sets are streams, consumed once, and none is ever materialized here: the replaced set is small for the refreshes that motivate a delta, but a branch switch or a formatting pass names every document of the repository, and Section 6 forbids holding that list in the Go heap. A producer that cannot stream its set has a whole-repository list of its own, which is the same defect one level up.

A producer's stream may read the store — the dependence applier's Files is a merge join against Store.UnitInputs — and it is drained from inside CarryOver's ingestion call, on the single writer connection. Such a stream sees the store as of the last commit: it takes a connection from the reader pool (Options.ReadConnections) once per page of its own scan, and the ingestion group stays open across every one of those round-trips. What a stream reads is the predecessor unit, sealed and committed before this run began, so the last commit is the state it needs; a stream must never call a read that runs on the group's own connection, because the ingestion call draining it holds that connection and the two would wait on each other. WAL readers never wait on the writer, so the pool reads cannot deadlock; a saturated reader pool stalls the carry-over, and the open group blocks every other writer in the process for as long as the walk lasts. A producer whose stream reads the store must therefore keep those reads bounded and proportional to the unit it is replacing, and must not discover this nesting by measuring a stall.

type RetentionPolicy

type RetentionPolicy struct {
	RetainRefs       int
	MaxRetainedBytes int64
}

RetentionPolicy is the user-set retention window of Section 20.1. Both settings spell 0 as "unlimited", exactly as the `retain_refs` and `max_retained_bytes` configuration keys document it: 0 refs retains every ref the repository has ever activated, and 0 bytes leaves retention governed by RetainRefs alone. A nonzero MaxRetainedBytes evicts least-recently-used refs first and never the active generation's ref.

type RetentionReport

type RetentionReport struct {
	RefsRetained     int
	GenerationsSwept int
	UnitsDeleted     int
	BytesReclaimed   int64
	BytesReclaimable int64
	// GenerationsDeleted names the generations this pass actually deleted, so
	// a caller that keeps its own rows against a generation can delete them
	// with it. It holds exactly the swept generations, which is what this pass
	// already materialized as its candidates, and never a repository-sized
	// list.
	GenerationsDeleted []int64
}

RetentionReport is what one pass did and what a stricter limit could still reclaim. BytesReclaimable is measured even when MaxRetainedBytes is 0 (unlimited), so `status` can warn before a disk fills: it is the accounted size of the retained non-active generations this pass kept, including any it skipped because a live lease still holds them.

Both byte figures are this store's own record accounting — the fixed per-record overhead plus the stored text — over the fact, evidence, alias, search and delta-state rows of a generation's units, and both count only the units the generation holds EXCLUSIVELY, which are the ones deleting it would actually free; a unit another generation still selects reclaims nothing. The size max_retained_bytes is measured against is a different question and counts every unit a retained generation selects, shared or not (see keepByRef). Both exclude FTS index overhead, page rounding and the content blobs, which snapshots and the Section 10.4 grace protocol retain, not generations. The figures are an accounting estimate for a warning, not a page count.

type RunScope

type RunScope struct {
	ProviderID, ScopeKey string
}

RunScope names one scope of one provider: the pair a failure reason is about.

type SearchColumn

type SearchColumn string

SearchColumn names one indexed column of search_fts, in its declared order.

const (
	ColumnName          SearchColumn = "name"
	ColumnQualifiedName SearchColumn = "qualified_name"
	ColumnSignature     SearchColumn = "signature"
	ColumnPath          SearchColumn = "path"
	// ColumnBody is an indexed column of search_fts; it is not a stored
	// column of search_units, which keeps no copy of the source text.
	ColumnBody SearchColumn = "body"
)

type SearchDocument

type SearchDocument struct {
	RowID         int64
	ID            string
	NodeID        model.NodeID
	FileID        model.FileID
	Path          string
	Kind          model.NodeKind
	Name          string
	QualifiedName string
	Signature     string
	Bytes         model.ByteRange
	TokenCount    int64
}

SearchDocument is a visible lexical document without its body: Section 14.2 forbids source bodies in generic results, and the database stores no body to return (ADR-0003 §2.1). Ranking and hydration use it, and it is what one record of a segment's per-document attribute stream decodes to.

type SessionRecord

type SessionRecord struct {
	ID           model.SessionID
	ActorID      string
	Binding      model.Binding
	ManifestID   model.ManifestID
	Phase        model.Phase
	State        model.WorkflowState
	StateVersion int
	ScopeVersion int
	CreatedAt    time.Time
	ExpiresAt    time.Time
	ClosedAt     *time.Time
}

SessionRecord is one read_sessions row with its generation binding. Readiness is computed by the workflow service from this plus Coverage and observations.

type Stats

type Stats struct {
	Generations   int64
	Snapshots     int64
	Files         int64
	Blobs         int64
	Units         int64
	NodeFacts     int64
	RelationFacts int64
	Evidence      int64
	SearchUnits   int64
	Leases        int64
	Sessions      int64
	DatabaseBytes int64
	WALBytes      int64
}

Stats is the storage accounting status and doctor report (Section 22): bounded row counts plus database and WAL bytes on disk.

type Store

type Store struct {

	// Version is the embedded engine's sqlite_version() as observed at open.
	Version string
	// SourceID is sqlite_source_id() as observed at open.
	SourceID string
	// contains filtered or unexported fields
}

Store is one workspace database: a single writer connection, a short-read pool, a posting-stream pool and a private in-memory connection for query tokenization.

func Open

func Open(ctx context.Context, path string, opts Options) (*Store, error)

Open opens or initializes the database at path (its parent directory is created 0700). It verifies the embedded engine version, applies and checks the Section 12.1 pragmas on every pooled connection, and fails closed with CTX_SCHEMA_MISMATCH on a foreign schema. A rebuild is a different path chosen by the caller; this function never alters an incompatible database.

func (*Store) Abort

func (s *Store) Abort(ctx context.Context, gen model.GenerationID) error

Abort fails a staging generation: its status and health become failed, its still-running provider runs are marked failed, and the unsealed units those runs were building are deleted. Sealed units stay reusable and the active pointer is untouched.

func (*Store) AcknowledgeFile

func (s *Store) AcknowledgeFile(ctx context.Context, session model.SessionID, actor string, file model.FileID) error

AcknowledgeFile records the client's separate full-file statement. It fails unless the file is already fully served for this session and hash; an acknowledgment never substitutes for receipts (Section 16.2).

func (*Store) AcquireLease

func (s *Store) AcquireLease(ctx context.Context, lease model.Lease, ownerRef string) error

AcquireLease records a retention lease. The generation and snapshot it names must exist; leases on staging or failed generations are permitted so a build's own state stays retained.

ownerRef names the owner the lease is taken for -- a session id, say -- and makes the lease findable from its owner, so the transaction that ends the owner ends the lease with it (releaseOwnerLease, and AdvanceSession's close). At most one live lease exists per (owner kind, owner ref): a second acquire is refused with OwnerLeaseConflict rather than pinning the generation twice. An owner with no stable identity of its own passes "".

func (*Store) Activate

func (s *Store) Activate(ctx context.Context, gen, expectedActive model.GenerationID, health model.GenerationHealth,
	capabilities []model.CapabilityState, normalizationVersion string) (model.Binding, error)

Activate validates the frozen membership, computes the AnalysisKey and runs exactly the Section 12.3 pointer transaction. expectedActive is the generation the caller believes is published (zero: none); it is compared with active_generations inside the same immediate transaction and a mismatch is CTX_VERSION_CONFLICT, so a coordinator never publishes over a pointer it has not seen. Any failure rolls back every status and pointer change.

func (*Store) ActiveGeneration

func (s *Store) ActiveGeneration(ctx context.Context, repo model.RepositoryID) (model.GenerationID, error)

ActiveGeneration returns the published generation for repo, or CTX_NO_ACTIVE_GENERATION.

func (*Store) AdvanceSession

func (s *Store) AdvanceSession(ctx context.Context, req model.AdvanceRequest) (model.WorkflowStatus, error)

AdvanceSession performs one compare-and-swap workflow transition.

func (*Store) AttachCarried

func (s *Store) AttachCarried(ctx context.Context, gen model.GenerationID, unit model.UnitID, c Carry) error

AttachCarried makes a sealed unit a stale member of gen, recording how far behind it is. Unlike AttachUnit it does not require the unit's inputs to be the snapshot's version of those files — a carried unit differs from the snapshot by definition — but every input must still exist in the snapshot (CTX_SNAPSHOT_CHANGED otherwise), because Section 13.3 does not carry a unit whose files were deleted.

func (*Store) AttachUnit

func (s *Store) AttachUnit(ctx context.Context, gen model.GenerationID, unit model.UnitID) error

AttachUnit reuses a sealed unit in gen. Reuse is admitted only when every declared input is exactly the selected snapshot's version of that file (content hash and mode); otherwise CTX_SNAPSHOT_CHANGED. Membership of the unit's dependencies is validated at Activate.

func (*Store) BeginGeneration

func (s *Store) BeginGeneration(ctx context.Context, repo model.RepositoryID, snapshot model.SnapshotID, semanticConfigHash, ref string) (model.GenerationID, error)

BeginGeneration opens a staging generation over snapshot. Staging facts are invisible to every reader until Activate publishes the pointer.

ref is the ref this generation is built from and is what retention groups by (Section 12.4): keeping the last retain_refs DISTINCT refs is what makes switching A -> B -> C -> A find A's units still on disk. Storage never interprets it. The coordinator supplies a branch name, the HEAD object id when HEAD is detached, or the sentinel "(none)" when the workspace is not a Git repository; an empty ref is refused rather than silently grouping every non-Git generation under one nameless bucket.

func (*Store) BeginProviderRun

func (s *Store) BeginProviderRun(ctx context.Context, gen model.GenerationID, providerID, providerVersion string) (model.ProviderRunID, error)

BeginProviderRun records a running provider invocation for gen and returns its random operational ID.

func (*Store) BeginUnit

func (s *Store) BeginUnit(ctx context.Context, gen model.GenerationID, build model.UnitBuild,
	inputs func(yield func(model.UnitInput) error) error) (*UnitWriter, error)

BeginUnit opens an immutable unit for gen. It recomputes the unit identity, requires every declared dependency to be sealed, streams and hashes the inputs in bounded batches against gen's snapshot, and rejects the unit if the streamed inputs do not reproduce Spec.InputHash. inputs must yield in ascending FileID order. A sealed unit with the same key cannot be rebuilt; use AttachUnit to reuse it.

func (*Store) Blob

func (s *Store) Blob(ctx context.Context, hash string) (model.BlobRecord, error)

Blob reads a ready blob's integrity metadata: the record PutBlob persisted, with block digests in block order and line checkpoints in byte order. A blob that is absent, quarantined or in trash is CTX_SOURCE_INTEGRITY: no caller may serve bytes it cannot verify against stored digests.

func (*Store) Capsule

func (s *Store) Capsule(ctx context.Context, session model.SessionID, actor string) (model.Capsule, error)

Capsule reads a session's stored capsule for its actor.

func (*Store) CapsuleRows

func (s *Store) CapsuleRows(ctx context.Context, session model.SessionID, actor string,
	list model.CapsuleList, after string, limit int) ([]model.CapsuleRow, error)

CapsuleRows reads one keyset page of one list of a session's sealed capsule.

after is the cursor a previous page ended on -- a row's own key, not an offset -- and empty starts at the first record. A cursor naming no row in this list is refused as CTX_CURSOR_INVALID rather than silently restarting the list, which would make a continuation read as a complete answer.

The caller decides whether there is a next page from the capsule's count for this list and the last row's ordinal, so the last page is the last page: no extra round trip and no empty trailing page.

func (*Store) CarriedUnits

func (s *Store) CarriedUnits(ctx context.Context, gen model.GenerationID,
	afterProviderID, afterScopeKey string, limit int) ([]CarriedUnit, error)

CarriedUnits pages gen's stale members in provider and scope order, keyset on that pair (two empty strings start from the beginning); limit is capped at model.MaxPageItems. A generation with none returns an empty slice, not an error: carrying nothing is the ordinary case.

It is a page and not a listing because a caller must not assume it returns everything: a generation may carry more stale units than one response may hold, and refusing that with CTX_RESOURCE_LIMIT would take the capability report down with it. A short page — fewer rows than limit — is the end.

The fan-out is real and not hypothetical. The planner (internal/index/plan, which storage must not import) admits up to 513 dependence units per language family that subdivides — 512 nested project units plus the root — across five such families, plus one C/C++ unit: 2566 carriable units in one generation, well past any single page. Page until a short page arrives.

func (*Store) CarryRunFailures

func (s *Store) CarryRunFailures(ctx context.Context, gen model.GenerationID, sealed []RunScope, from ...model.GenerationID) error

CarryRunFailures copies the recorded failure reasons of every run of the source generations onto gen, which must still be staging and belong to the same repository, except the reasons about a scope in sealed.

A generation that publishes work other generations ran holds none of their run rows: a deferred publication extends the active generation with units a short-lived work generation built, and the scopes that failed along the way -- in the foreground pass the queue was planned over, in an earlier batch, or in this one -- are named failed by its capability rows while FailedRuns, which reads by generation, would find no reason for any of them. Copying the rows gives the published generation the reasons its own capability rows state, and the copies die with it exactly as the originals die with theirs. The sources are left as they are: a publication that loses its activation is aborted, and a moved row would take the active generation's reasons with it.

THE RULE: gen carries a reason only for a scope it does not seal. sealed is the set of scopes whose fresh unit gen publishes, and a reason about one of them is a failure that unit has since answered; carried on, it would list the scope as failed beside a capability row that reports it covered, in this generation and every one that extends it.

Each copy is a run row of its own under a fresh random id, so no two generations ever share one row. The copy is one statement per source and one per sealed scope, and holds nothing in memory, whatever number of reasons it moves; sealed is the caller's own batch, already in hand.

func (*Store) Check

func (s *Store) Check(ctx context.Context, deep bool) error

Check runs the integrity checks Section 12.1 reserves for initialization, doctor and recovery.

It is two different checks, because the expensive one is O(database bytes) and an ordinary `doctor` may not pay it: on a 1.9 GB index `quick_check` alone costs 30 s cold and ~4.5 s of CPU warm, which made the one command an operator runs on a sick workspace the slowest command in the product.

  • shallow (deep == false): the constant-cost header reads -- page size and page count, the schema fingerprint row, and that the journal is still the write-ahead log this store requires. These catch a database that is not this schema, was truncated below its own header, or lost its WAL mode; they do NOT walk a single page of content, and the caller must report the content walk as unverified rather than as passed.
  • deep (deep == true): everything shallow checks, then `quick_check`, the foreign key check and the full-text index walk -- the complete pass.

Both run on the reader pool (`s.read`), never `s.write`: an integrity walk reads, and taking the writer for it serialised `doctor` against any running indexer for the whole walk. The one exception is the FTS5 `integrity-check` command, which is spelled as an INSERT and is therefore refused by the readers' `query_only=ON`; it takes the writer for that statement alone, and only under deep.

search_fts is contentless (ADR-0003 §2.1), so its integrity-check proves the index is internally consistent, not that it agrees with a stored copy of the text -- there is no stored copy, and the text's own authority is the content store, which verifies every block digest on read. Document membership is still checked both ways: a posting is named by search_units.doc_id -- which a delta carry-over shares along a carry chain, so several rows may name one -- and the vocabulary scan in checkContent reports any indexed document no row names.

func (*Store) ClearWatchHeartbeat

func (s *Store) ClearWatchHeartbeat(ctx context.Context, repo model.RepositoryID, session string) error

ClearWatchHeartbeat removes this watch's own heartbeat and no other's. A watch that ends deliberately withdraws its claim at once rather than leaving a fresh row that reads as live coverage until it expires; expiry remains the answer for a process that died without getting here.

func (*Store) Close

func (s *Store) Close() error

Close commits any open ingestion group, stops the writer goroutine and releases every pool. It is safe to call once.

func (*Store) CollectBlobs

func (s *Store) CollectBlobs(ctx context.Context, deadline time.Time, limit int) (deleted []string, restored int64, err error)

CollectBlobs is the final phase: a trashed blob referenced again is restored, and one trashed at or before deadline that the recheck in this same transaction still finds unreferenced has its row deleted. The hashes of the deleted rows are returned so the caller removes their CAS objects -- the database row goes first, because a published object with no row is an orphan the next capture simply republishes over, while a row with no object reads as present and fails only on the first ReadRange.

func (*Store) CompleteProviderRun

func (s *Store) CompleteProviderRun(ctx context.Context, result model.ProviderResult, diagnosticCode string) error

CompleteProviderRun records a run's terminal state and counters.

func (*Store) ConfirmChunks

func (s *Store) ConfirmChunks(ctx context.Context, session model.SessionID, actor string, chunkIDs []string) error

ConfirmChunks marks issued chunks confirmed and merges their intervals into served_ranges in one transaction (Section 16.3). Duplicate confirmations are idempotent; an unknown, expired or foreign chunk fails the whole batch.

func (*Store) Coverage

func (s *Store) Coverage(ctx context.Context, session model.SessionID, actor string, after model.FileID, limit int) ([]model.FileCoverage, error)

Coverage pages the session's honest per-file coverage: pinned hash, size, confirmed bytes (the disjoint served union), requirement, state and waiver. A nonempty file is full_served only when the union is exactly [0,size); an empty file requires a confirmed zero-length EOF receipt.

func (*Store) CoverageSummary

func (s *Store) CoverageSummary(ctx context.Context, session model.SessionID, actor string) (CoverageCounts, error)

CoverageSummary answers CoverageCounts in a single round trip. Like Coverage and Session it reports honestly beside CTX_SESSION_EXPIRED rather than pretending the session has no scope.

func (*Store) DeleteGeneration

func (s *Store) DeleteGeneration(ctx context.Context, gen model.GenerationID) error

DeleteGeneration removes a failed or superseded generation's membership and metadata, then collects units no retained generation or dependent unit reaches, in reverse dependency order through the FTS-aware deletion procedure, then orphan identities and origin runs (Section 12.4). A live lease or a retained session referencing the generation blocks deletion with a retryable CTX_WORKSPACE_BUSY. The tail is the shared sweep; blob collection is a separate grace-protocol step and is not performed here.

func (*Store) DeltaState

func (s *Store) DeltaState(ctx context.Context, unit model.UnitID, kind string, dst io.Writer) error

DeltaState streams what a sealed unit stored under kind into dst, part by part, in one read transaction so the artifact is the one the unit sealed. A unit that stored none is CTX_ARGUMENT_INVALID with reason not_found, so a refresh can tell "no previous state, import in full" from a failure.

func (*Store) DependencyAliases

func (s *Store) DependencyAliases(ctx context.Context, units []model.UnitID) (*AliasTable, error)

DependencyAliases loads every alias row of units in one read transaction on the reader pool, against the last commit. Each list is the distinct identities of one scoped native key, ordered by canonical key and then node id. No units is an empty table and no read.

Every unit must be sealed in that snapshot. One that is not -- building, failed, absent, or sealed only in the uncommitted ingestion group -- is refused naming the unit, never answered as aliased to nothing: an empty answer would mint an identity other than the one the committed dependency names, and the run would still succeed.

func (*Store) EnsureRepository

func (s *Store) EnsureRepository(ctx context.Context, id model.RepositoryID, rootPath string) error

EnsureRepository records the installation-local repository identity and its current root path. The row is required by every snapshot and generation.

func (*Store) ExpireSessions

func (s *Store) ExpireSessions(ctx context.Context, now time.Time, limit int) (int64, error)

ExpireSessions closes open sessions whose TTL has passed, in one bounded batch, and reports how many it closed. Closing stops mutations; the audit artifacts stay until PruneSessions.

func (*Store) FailedRuns

func (s *Store) FailedRuns(ctx context.Context, gen model.GenerationID) ([]model.RunFailure, int64, error)

FailedRuns is one page of the typed reasons the runs of one generation failed, in provider order, oldest first within a provider, with the number of reasons that did not fit the page.

It is keyed by GENERATION and not by run id, which is what makes it answerable at all: the reasons are on the run rows, a status report holds no run ids, and a reader keyed by one could only be called by something that already knew which run to ask about. Every surface that reports a failure asks the same question -- why does this generation have no facts for these scopes -- and this is the one answer to it.

A generation that has been collected takes its runs with it, so an empty page is "no reason is kept for this generation" and not an error: the question was why a scope has no facts, and "the generation that failed is no longer retained" is an answer.

The page is bounded like every other result list, and what did not fit is counted rather than dropped in silence: the one surface whose job is to report failures must never lose one without saying so.

func (*Store) Flush

func (s *Store) Flush(ctx context.Context) error

Flush commits the open ingestion group, if any. The index build calls it at each provider boundary, so the next provider's reads on the reader pool see the units the previous one sealed; a caller that opens a connection of its own -- a test or a tool reading the database file beside the store -- calls it to see what the store has written so far. Close commits whatever a process leaves open.

func (*Store) GenerationCapturedAt

func (s *Store) GenerationCapturedAt(ctx context.Context, gen model.GenerationID) (time.Time, error)

GenerationCapturedAt reports when the capture a generation serves was taken: the creation time of its snapshot, not of the generation row. They differ whenever a generation is built from a snapshot an earlier run captured, and the snapshot's time is the one that answers "how old is the source this workspace is serving".

func (*Store) GenerationStatus

func (s *Store) GenerationStatus(ctx context.Context, gen model.GenerationID) (model.GenerationStatus, error)

GenerationStatus reads one generation's lifecycle status.

func (*Store) IncludeManifest

func (s *Store) IncludeManifest(ctx context.Context, req model.IncludeRequest, manifest model.ManifestID) (model.WorkflowStatus, error)

IncludeManifest points an open sweep/verify session at a recompiled manifest of the same generation and snapshot: history is appended, scope and state versions advance, and the new manifest's files join the session while same-hash coverage already confirmed is retained.

func (*Store) IssueChunk

func (s *Store) IssueChunk(ctx context.Context, c model.IssuedChunk) error

IssueChunk persists an issued, not yet confirmed, chunk for the session's actor. Like every session mutation it loads the session first, so a wrong actor, a closed or an expired session is refused before any row is written. The chunk must name a file and content hash in the session's pinned scope; the schema's foreign key to session_files makes another snapshot's bytes unrepresentable.

func (*Store) KnownBlobs

func (s *Store) KnownBlobs(ctx context.Context, hashes []string) (map[string]struct{}, error)

KnownBlobs reports which of hashes the index still holds a blobs row for, as a set: a hash present in the result is named by the store, and absence means no row exists. It is the oracle (*snapshot.CAS).SweepOrphans asks before removing a published object, so the direction of an error matters -- a partial answer must never reach the sweep, and this returns nothing but a complete set or an error.

The chunking is a statement-shape bound only: every chunk is read inside ONE read transaction, so the answer is a single consistent snapshot of blobs rather than one stitched from as many snapshots as it took chunks. A set assembled across snapshots could omit a hash a concurrent writer inserted after an earlier chunk and report the object an orphan.

No state filter: 'quarantined' and 'trash' rows are blobs mid-grace-protocol, whose objects that protocol deletes after its own reachability recheck. Only a file with no row at all is an orphan.

func (*Store) LeaseExpiry

func (s *Store) LeaseExpiry(ctx context.Context, id string) (time.Time, error)

LeaseExpiry reports when a live lease expires. A lease that was released or never existed is CTX_CURSOR_INVALID, so spools and cursors that consult it end with their lease rather than with the expiry they were created under.

func (*Store) Manifest

func (s *Store) Manifest(ctx context.Context, id model.ManifestID) (model.ContextManifest, error)

Manifest reads one manifest header with its binding, exactly as written.

func (*Store) ManifestEntries

func (s *Store) ManifestEntries(ctx context.Context, id model.ManifestID, afterOrdinal int, limit int) ([]model.ContextEntry, error)

ManifestEntries pages a manifest's entries by ordinal.

func (*Store) ManifestExcluded

func (s *Store) ManifestExcluded(ctx context.Context, id model.ManifestID, afterOrdinal int, limit int) ([]model.ExcludedContextEntry, error)

ManifestExcluded pages a manifest's exclusions by ordinal.

func (*Store) ManifestScopeNodes

func (s *Store) ManifestScopeNodes(ctx context.Context, id model.ManifestID,
	after model.NodeID, limit int) ([]model.NodeID, error)

ManifestScopeNodes pages the DISTINCT node ids a manifest's entries name, in node-id order, by keyset on the node id itself.

The capsule's scope list is exactly this set, and deriving it in SQL is what keeps the seal's memory a function of the page size: a Go-side walk of ManifestEntries would have to hold a dedup set and a sort buffer over every entry the manifest carries, and context.max_manifest_bytes is unlimited by default. Entries that name no node (a file pulled in whole) contribute nothing and are excluded here rather than skipped by the caller.

The rows carry node_ids.id surrogates, which are storage-internal and meaningless outside this database file, so the page is both ordered and keyset on the CANONICAL 32-byte id reached through node_ids: the caller (workflow's capsule scope collector) feeds the last id it received back as `after`, and a cursor must carry a canonical identity. node_id is not indexed on context_entries (the primary key is (manifest_id, ordinal)) and the ordering column lives in the joined dictionary, so SQLite satisfies the DISTINCT and the ORDER BY from a temporary b-tree over one manifest's entries, each row's canonical fetched by the node_ids integer primary key. That cost is the database's, not the process heap's, and it is bounded by the manifest.

func (*Store) ManifestSlices

func (s *Store) ManifestSlices(ctx context.Context, id model.ManifestID, afterIndex int, limit int) ([]model.ContextSlice, error)

ManifestSlices pages a manifest's slices by index, with their entry ordinals.

func (*Store) Observations

func (s *Store) Observations(ctx context.Context, session model.SessionID, actor string, kind model.ObservationKind, scopeVersion int,
	after model.ObservationID, limit int) ([]model.Observation, error)

Observations pages a session's observations by ID, optionally one kind and one scope version (zero means every version).

func (*Store) OpenSession

func (s *Store) OpenSession(ctx context.Context, open model.SessionOpen) (model.SessionID, error)

OpenSession creates an actor-specific session over a manifest and populates its file scope. With an idempotency key, a retry by the same actor with the same open-request hash returns the existing session; the same key with a different hash fails (Section 12.4). Identical requests from different actors always get separate sessions.

func (*Store) PinGeneration

func (s *Store) PinGeneration(ctx context.Context, repo model.RepositoryID, gen model.GenerationID, ttl time.Duration) (*PinnedReader, error)

PinGeneration resolves gen (zero selects the active generation) and acquires a query lease for ttl in the same transaction (Section 12.3). A staging or failed generation cannot be pinned.

func (*Store) ProviderRun

func (s *Store) ProviderRun(ctx context.Context, id model.ProviderRunID) (model.ProviderRun, error)

ProviderRun reads one run's provenance. GenerationID is zero once the creating generation has been deleted.

func (*Store) PruneSessions

func (s *Store) PruneSessions(ctx context.Context, now time.Time, retention time.Duration, limit int) (int64, error)

PruneSessions deletes closed or complete sessions whose closed_at is older than retention, in one bounded batch. Their chunks, ranges, observations and capsules cascade; their manifests become collectable with their generation.

func (*Store) PutBlob

func (s *Store) PutBlob(ctx context.Context, b model.BlobRecord) error

PutBlob records one retained CAS object with its block digests and line checkpoints (Section 10.3). It is idempotent for the same hash and size; the same hash with a different size is a source integrity failure. The caller has already flushed and published the object file.

func (*Store) PutCapsule

func (s *Store) PutCapsule(ctx context.Context, c model.Capsule, src model.CapsuleListSource) (model.Capsule, error)

PutCapsule stores the deterministic completion record once, for a session whose consolidate phase is open. A second call returns the first stored capsule unchanged (Section 17.3).

The capsule blob carries identity and counts; src streams the records themselves into context_capsule_rows inside this same write transaction, after the "a capsule already exists" check, so the seal stays write-once and a capsule is never committed without the rows its identity was derived from. A second call returns the stored capsule and writes no rows.

func (*Store) PutManifest

func (s *Store) PutManifest(ctx context.Context, m model.ContextManifest, requestJSON []byte,
	entries []model.ContextEntry, slices []model.ContextSlice, excluded ExcludedEntries) error

PutManifest stores an immutable compiled manifest: header, ordered entries, slices and exclusions in one transaction. Every entry's node must be visible in the manifest's generation and every file must be in its snapshot. Storing the same manifest ID again with the same canonical hash is a no-op; a different hash under the same ID is CTX_VERSION_CONFLICT.

Entries must arrive in the Section 15.3 reading order: ordinals 0..n-1, with requirement rank non-decreasing (so required_full is a prefix) and score descending within a rank. The workflow walks those ordinals directly, so an order this method accepted and never checked would be a defect visible only one task later.

Exclusions arrive as a SEQUENCE and not as a slice. An entry list is bounded by the compile's resolved budget -- the caller's own declared window -- but the exclusion list is bounded only by the repository, so materializing it here would put a repository-sized slice in heap for the length of the transaction. Rows are validated and inserted one at a time as the sequence yields them, against a prepared statement inside the manifest's single transaction, so peak heap is one row however many were excluded.

func (*Store) PutObservation

func (s *Store) PutObservation(ctx context.Context, o model.Observation) error

PutObservation appends an immutable observation. Its ID is derived from its content, so a resubmission is idempotent; an existing observation is never rewritten. The scope version must be the session's current one.

func (*Store) PutSnapshot

func (s *Store) PutSnapshot(ctx context.Context, snap model.Snapshot, files func(yield func(model.FileVersion) error) error) error

PutSnapshot stores a snapshot header and its manifest rows. files streams the manifest; rows are written in bounded batches and never collected. Every nondeleted row must name a blob already retained with PutBlob. The header's file count and byte total are validated against the rows actually written.

The call is idempotent for a complete existing snapshot. An incomplete one left by a crash is replaced only while no generation references it.

func (*Store) QuarantineBlobs

func (s *Store) QuarantineBlobs(ctx context.Context, now time.Time, limit int) (int64, error)

QuarantineBlobs demotes up to limit retained blobs that nothing references any more, stamping each with now, and reports how many it demoted. Nothing is deleted here: quarantine only starts the clock.

func (*Store) RangeConfirmed

func (s *Store) RangeConfirmed(ctx context.Context, session model.SessionID, actor string,
	file model.FileID, hash string, r model.ByteRange) (bool, error)

RangeConfirmed reports whether [r.Start,r.End) of one pinned file is already fully confirmed served to this actor, at this content hash (Sections 16.2, 17.2). It is the check no landed method answers: a scope review may cite source intervals, and a citation over bytes the actor never confirmed would let a note mark a file read without coverage.

It returns a bounded boolean and never an interval list. A file outside this session's pinned scope, or one at a hash this session never pinned, is simply not confirmed -- (false, nil) -- because a citation over it is a claim to refuse, not a storage failure. Unlike the reporting reads it does not swallow CTX_SESSION_EXPIRED: it exists to gate a write, and an expired session confirms nothing.

func (*Store) RecordRunFailure

func (s *Store) RecordRunFailure(ctx context.Context, id model.ProviderRunID, failure model.RunFailure) error

RecordRunFailure keeps the typed reason a run produced no unit on the run row. It is the only durable home for a failed unit's message and details: a unit that never sealed has no row of its own, and the capability row the fold publishes carries one exemplar scope per provider capability and excludes the raw tool output entirely.

It is written after CompleteProviderRun and therefore does not require the run to still be running; a run recorded as succeeded has no failure to write, which is why the empty reason is refused rather than stored.

func (*Store) RecordSuppliedIndex

func (s *Store) RecordSuppliedIndex(ctx context.Context, gen model.GenerationID, path string, resolved bool) error

RecordSuppliedIndex records one supplied index against a generation. It is called while the generation is still staging, so the row is removed with the generation by the foreign key if the run never activates.

The write is idempotent on (generation_id, path): a caller that supplies the same path twice records one row, and the later resolution wins rather than failing the publish over a duplicate.

func (*Store) RecordWatchHeartbeat

func (s *Store) RecordWatchHeartbeat(ctx context.Context, repo model.RepositoryID, hb WatchHeartbeat) error

RecordWatchHeartbeat publishes hb as this watch's live heartbeat, replacing whatever this same watch left. There is one row per watching process and no history: this is liveness, and a watch that has ended leaves nothing worth keeping.

The same write deletes the repository's rows whose deadline has elapsed, by the same rule and the same clock reading a reader judges them by, so what a reader is told is alive and what this sweep is willing to delete can never disagree. Without it the rows of processes that died without withdrawing would accumulate for ever. The sweep runs after the upsert, so the beat being published can never be its own victim.

func (*Store) RecordedBlob

func (s *Store) RecordedBlob(ctx context.Context, hash string) (model.BlobRecord, error)

RecordedBlob is Blob as the ingestion side sees it: through the open group, so a capture asking whether it has already recorded a blob sees the ones it recorded since the last commit.

func (*Store) Recover

func (s *Store) Recover(ctx context.Context, now time.Time) error

Recover is startup recovery for the indexing owner (Section 12.3). The caller must hold the workspace indexing lock: it marks every staging generation failed, deletes the unsealed units and running runs those generations left behind, then runs the same collection tail as DeleteGeneration (unreachable units, orphan runs, expired leases, unreferenced snapshots and files), so a crash between a deletion's steps leaves nothing a second Recover would treat differently. The active pointer is never touched.

func (*Store) ReleaseLease

func (s *Store) ReleaseLease(ctx context.Context, id string) error

ReleaseLease drops a lease; a lease already gone is not an error.

func (*Store) RenewLease

func (s *Store) RenewLease(ctx context.Context, id string, expiresAt time.Time) error

RenewLease extends a live lease. An expired or released lease is CTX_CURSOR_INVALID: continuation never silently repins.

func (*Store) RetainByRef

func (s *Store) RetainByRef(ctx context.Context, repo model.RepositoryID, p RetentionPolicy, now time.Time) (RetentionReport, error)

RetainByRef applies the policy to repo and reports what it did. It keeps the most recent generation of each of the last p.RetainRefs distinct refs (every ref, when the window is the unlimited 0), and when p.MaxRetainedBytes is set evicts the least recently used of those refs until the accounted retained size fits, never evicting the active generation's ref and never touching a staging generation (Recover owns those). Failed generations are swept unconditionally: a generation that never published is not a result the user indexed.

A generation a live lease or a retained session still holds is skipped rather than failing the pass; it is retained until its lease expires and counts toward BytesReclaimable.

func (*Store) RetainsLeases

func (s *Store) RetainsLeases() bool

RetainsLeases is Continuable for a caller that holds the lease store rather than a pinned reader -- the graph engine, whose continuations are minted far from the reader that pinned them. It satisfies pagination.LeaseRetainer, so pagination.Leases answers it for the whole process.

func (*Store) SampleBlobs

func (s *Store) SampleBlobs(ctx context.Context, limit int) ([]string, error)

SampleBlobs reports up to limit retained blob hashes. It is the hash source the Section 22 content-addressed-storage check needs: Blob verifies a hash the caller already holds, and nothing else lists them. The result is bounded by maxBlobSample however large limit is, so no caller can turn a diagnostic probe into a full table scan.

func (*Store) SealUnit

func (s *Store) SealUnit(ctx context.Context, w *UnitWriter) error

SealUnit validates the whole unit and makes it immutable and a member of the generation it was begun for (Section 12.2 invariants beyond DDL): evidence behind every fact, ranges inside their source, relation endpoints and alias targets visible through the unit/dependency closure, an acyclic dependency graph, and the exact FTS document count its transactions emitted.

func (*Store) SelectedUnit

func (s *Store) SelectedUnit(ctx context.Context, gen model.GenerationID, providerID, scopeKey string) (model.UnitID, error)

SelectedUnit reports the unit a generation selects for one provider and scope, which is how a refresh finds the predecessor to carry over from: the previous generation's unit for the same work. A generation that selects none is CTX_ARGUMENT_INVALID with reason not_found.

func (*Store) Session

func (s *Store) Session(ctx context.Context, id model.SessionID, actor string) (SessionRecord, error)

Session reads a session record for its actor. Unlike the mutating operations, a closed or expired session is still returned so status can report it honestly.

func (*Store) SessionFilePaths

func (s *Store) SessionFilePaths(ctx context.Context, session model.SessionID, actor string,
	ids []model.FileID) (map[model.FileID]string, error)

SessionFilePaths resolves a bounded batch of file identifiers to the paths they carry in this session's pinned snapshot. Coverage rows and manifest entries carry identifiers and no path, and naming a file the operator cannot locate is not an answer.

It is scoped by session_files, not by the snapshot alone: only files this session pinned are named, so a batch can never be used to enumerate paths outside the actor's scope. An identifier that is not in the pinned scope, or whose pinned hash is no longer in the snapshot, is absent from the map; the caller decides what its own absence means. Like Coverage and CoverageSummary it answers honestly beside CTX_SESSION_EXPIRED rather than pretending the session has no scope.

func (*Store) Snapshot

func (s *Store) Snapshot(ctx context.Context, id model.SnapshotID) (model.Snapshot, error)

Snapshot reads one stored header exactly as PutSnapshot wrote it.

func (*Store) SnapshotFile

func (s *Store) SnapshotFile(ctx context.Context, id model.SnapshotID, file model.FileID) (model.FileVersion, error)

SnapshotFile reads one manifest row of a snapshot.

func (*Store) SnapshotFiles

func (s *Store) SnapshotFiles(ctx context.Context, id model.SnapshotID, afterPath string, limit int) ([]model.FileVersion, error)

SnapshotFiles pages a snapshot's manifest in canonical path order, keyset on the path itself (the empty string starts from the beginning). Paths compare bytewise under the column's BINARY collation, which is the order the capture walk emits and the manifest hash folds, so a page boundary never reorders or repeats a row. limit is capped at model.MaxPageItems.

func (*Store) Stats

func (s *Store) Stats(ctx context.Context) (Stats, error)

Stats reads the row counts in one read transaction and sizes the files. A file size that cannot be read is an error, never reported as zero.

func (*Store) StoreSizes

func (s *Store) StoreSizes(ctx context.Context) (databaseBytes, walBytes int64, err error)

StoreSizes reports the on-disk database and write-ahead-log sizes without running a query. It is what a shallow `doctor` reads instead of Stats, whose row counts are O(rows) per table.

func (*Store) SuppliedIndexes

func (s *Store) SuppliedIndexes(ctx context.Context, gen model.GenerationID) ([]SuppliedIndex, error)

SuppliedIndexes reports the supplied indexes recorded against a generation, ordered by path so two calls produce the same report. A generation with no recorded path returns an empty list, which is the answer "this generation was built with no supplied index" -- a measurement now that the recording half exists, where before the same emptiness was merely the absence of a feature.

func (*Store) SynchronousMode

func (s *Store) SynchronousMode(ctx context.Context) (string, error)

SynchronousMode reads `PRAGMA synchronous` back from the WRITER connection and reports it in the configuration's own spelling. It is the writer's because the writer is the only connection whose mode is a choice: readers are pinned to FULL above, so asking a reader would report FULL for every workspace and say nothing about the durability the operator configured.

It exists so a diagnostic can report the LIVE mode rather than echo the configured one. The two cannot drift while a store is open -- every pooled connection has its pragma set read back before it joins the pool -- but a check that reports what the configuration says has verified nothing, and ADR-0004 Decision 1a asks an operator to be able to read this mode back.

A mode the mapping does not know is reported as the raw pragma value rather than guessed at or failed: this is a diagnostic read, and an unexpected number is itself the answer.

func (*Store) Tokenize

func (s *Store) Tokenize(ctx context.Context, text string) ([]string, error)

Tokenize splits text with the exact unicode61 tokenizer search_fts uses. Tokens are returned in document order, duplicates included, so the caller can count term and phrase occurrences without reimplementing Unicode tokenization.

func (*Store) TrashBlobs

func (s *Store) TrashBlobs(ctx context.Context, limit int) (trashed, restored int64, err error)

TrashBlobs is the second phase: a quarantined blob that is referenced again is restored to 'ready', and one that is still unreferenced moves to 'trash'. It takes no clock, because trashed_at marks the demotion out of 'ready' and must not be rewritten here: restamping it would restart the grace window on every pass and nothing would ever be collected.

func (*Store) UnconfirmedChunks

func (s *Store) UnconfirmedChunks(ctx context.Context, session model.SessionID) (int64, error)

UnconfirmedChunks counts a session's issued but unconfirmed, unexpired chunks so the coverage service can enforce its per-session cap.

func (*Store) UnitInputs

func (s *Store) UnitInputs(ctx context.Context, unit model.UnitID) iter.Seq2[model.UnitInput, error]

UnitInputs streams the sealed unit's declared inputs in ascending FileID order — the same order BeginUnit required when they were written — so a caller can merge-join them against a fresh input stream without holding either list. It refuses a unit that is not sealed: a building unit's unit_inputs rows are still arriving, and a delta that diffed against a partial set would name too few replaced buckets and carry a stale fact.

The first failure ends the sequence: it is yielded with a zero UnitInput and nothing follows it.

Each page is one read transaction on the reader pool, so the stream sees the last commit and not the ingestion group in progress: the unit it reads is a predecessor, sealed and committed before the run that reads it began. The dependence delta applier walks this from inside UnitWriter.CarryOver's ingestion call, which holds the group's connection, so those reads are nested inside the writer's and compete for reader connections with the rest of the process; see Replaced.

func (*Store) UnitOrigin

func (s *Store) UnitOrigin(ctx context.Context, unit model.UnitID) (model.ProviderRunID, error)

UnitOrigin reports the run that produced a retained unit. A reused unit keeps its original run; a rerun never rewrites it (Section 9.3).

func (*Store) UnitState

func (s *Store) UnitState(ctx context.Context, id model.UnitID) (state model.UnitState, exists bool, err error)

UnitState reports the lifecycle state of the unit keyed by id and whether the row exists at all, as of the last commit: a unit the open ingestion group has begun, sealed or failed is answered as the last commit left it. The coordinator consults it before BeginUnit: a sealed unit with a matching key is reused through AttachUnit, never rebuilt. Every unit it can meet sealed was sealed by an earlier run or tick, and each of those ends in a committing Activate or Abort.

func (*Store) WALBoundBytes

func (s *Store) WALBoundBytes() int64

WALBoundBytes is the largest write-ahead log an ingestion group leaves behind: the writer's page cache, which the group fills before it commits, plus one batch of spilled pages. The log is rewound in place at every reset and keeps its high-water length, so a log past this bound is one whose frames a group actually reached: a reader kept the old ones open across several groups, or the log was never checkpointed.

func (*Store) Waive

Waive records an audited exception for one scoped file. It changes no coverage and never grants strict readiness (Section 17.3).

func (*Store) WaiversAfter

func (s *Store) WaiversAfter(ctx context.Context, session model.SessionID, actor string,
	after model.FileID, limit int) ([]model.WaiverRecord, error)

WaiversAfter pages this session's recorded coverage exceptions by keyset on file_id, the key Coverage pages session_files on. Waive is a writer whose returned record echoes the request's reason, and FileCoverage carries only the Waived flag, so this is the only reader of the stored reasons -- the text a sealed capsule has to carry to be an honest durable artifact (Section 17.3).

coverage_waivers is append-only and has no actor column: the exception is a property of the session, so ActorID comes from the resolved session record exactly as Waive sets it. Like Coverage, Observations and ManifestScopeNodes each page is its own read transaction rather than one loop inside a single snapshot: coverage_waivers is only ever appended to, and a Waive that lands between a seal's counting pass and its digest pass is caught by CapsuleCanonicalHash's count-versus-stream refusal instead of being sealed. The caller therefore never holds a session-sized list, which is the property a whole-list read could not give.

Like Coverage and CoverageSummary it answers honestly beside CTX_SESSION_EXPIRED: a capsule is sealed from what the session recorded, and an expired session still recorded it.

The keyset is file_id alone. Both coverage_waivers and session_files are keyed (session_id, file_id, content_hash), so both carry the same latent boundary if one session ever pinned one file at two content hashes; nothing in the writer path produces that today, and answering it differently here would be the only place in the package that does.

func (*Store) WatchHeartbeats

func (s *Store) WatchHeartbeats(ctx context.Context, repo model.RepositoryID) ([]WatchHeartbeat, error)

WatchHeartbeats reads every heartbeat row this repository holds, freshest deadline first. An empty result is a workspace no watch has ever published in, which is a different answer from a row that has expired: nothing has run here, rather than something ran and stopped.

Expired rows are returned rather than filtered, because whether a row is live is the caller's judgement, made against ExpiresAt through Live -- and an expired row is the only evidence that a watch ran here and stopped, which is what the doctor must warn about. This method reports what was written and interprets nothing.

The order is by deadline and then by session, so two readers see one order. It is the deadline and not the beat time because two watches need not share a configuration, and under different windows the two orders diverge; the deadline is the stamp liveness is judged by, so it is the one that ranks.

func (*Store) WriterBusy

func (s *Store) WriterBusy() time.Duration

WriterBusy is the writer's busy time: how long the goroutine that owns the writer connection has spent running jobs, commits and checkpoints included, since the store opened. The index build reports it against a stage's wall (ADR-0012 decision 5): the share of the wall the one writer the log admits was busy says whether writing is what bounds the stage. A read-only store has no writer and reports zero.

type StoredAlias

type StoredAlias struct {
	NodeID       model.NodeID
	Kind         model.NodeKind
	CanonicalKey string
}

StoredAlias is one canonical identity a scoped native key resolves to. The canonical key is what the resolver orders and tie-breaks on (Section 9.4: never by completion order), and what a provider must copy into the node facts it publishes under that identity.

type StoredEvidence

type StoredEvidence struct {
	Evidence model.Evidence
	Bytes    *model.ByteRange
}

StoredEvidence is one visible evidence row, with the byte interval separate for the same reason as StoredNode.

type StoredNode

type StoredNode struct {
	Node          model.Node
	Bytes         *model.ByteRange
	UnitID        model.UnitID
	SourceBinding model.SourceBinding
}

StoredNode is one visible node fact. Ranges are stored as bytes only; the consumer derives line/column positions from the source's line checkpoints, so the model.Node here carries no Range and Bytes holds the interval.

type SuppliedIndex

type SuppliedIndex struct {
	Path     string
	Resolved bool
}

SuppliedIndex is one `--scip-index` path recorded against a generation, with whether the generation ended up holding a unit built from it.

Path is recorded even when nothing resolved: that is the whole point of the record. A path that matched no file plans no unit, so a reader looking only at units cannot tell a typo from a run that supplied no index at all.

type TermOccurrence

type TermOccurrence struct {
	RowID     int64
	Column    SearchColumn
	Count     int64
	Offsets   []int64
	Truncated bool
}

TermOccurrence is one term's instances in one column of one visible document. Offsets ascend, truncated at MaxTermOffsets; a phrase count from truncated offsets is a lower bound, which Truncated says.

type UnitWriter

type UnitWriter struct {
	// contains filtered or unexported fields
}

UnitWriter receives one unit's facts in bounded batches while the unit is building. Only the store hands one out; providers reach it through the Task 6 sink and never see SQL.

func (*UnitWriter) Abandon

func (w *UnitWriter) Abandon(ctx context.Context) error

Abandon gives up a building unit without deleting the rows it wrote: the unit is marked failed, which is all that is needed to keep it invisible (every read joins generation_units, and an abandoned unit is a member of no generation) and to keep a rebuild of the same scope from colliding with it. Its rows are reclaimed by the next Recover and by retention, through collectUnreachableUnits.

This is the cancel path and only the cancel path. Fail's cascade walks every fact, identity and search document the unit wrote, which for a large unit is seconds to minutes of uncancellable work; running it when the caller has already asked the process to stop turns Ctrl-C into a long wait for a rollback whose result is discarded either way.

func (*UnitWriter) CarryOver

func (w *UnitWriter) CarryOver(ctx context.Context, prev model.UnitID, replaced Replaced) (CarryOverStats, error)

CarryOver inherits the sealed unit prev's facts into the unit being built, minus everything Replaced names. It must run after the provider's own batches and before SealUnit: every insert it issues yields to a row already present, so the fresh import always wins over its predecessor.

The whole body is one ingestion call on the single writer connection, and draining Replaced's three streams happens inside it: a producer stream that reads the store runs nested in this call and on the reader pool, seeing the last commit (see Replaced).

Carrying a row asserts that its source has not changed, so the whole call is refused unless every file the previous unit located facts in, and that Replaced does not name, is declared by this unit with the same content hash. A file that was edited but not named, or deleted from the snapshot, is therefore a typed refusal and never a stale fact.

func (*UnitWriter) EvidenceClipped

func (w *UnitWriter) EvidenceClipped() int64

EvidenceClipped is the number of occurrence rows SealUnit dropped to hold the Section 11.1 bound on evidence per fact. It is zero until the unit is sealed. A nonzero count is not a failure; it is what keeps the truncation from being silent, and SealUnit itself logs one bounded warning naming the unit and the count, so the drop is on the record even for a caller that never reads this.

func (*UnitWriter) Fail

func (w *UnitWriter) Fail(ctx context.Context) error

Fail discards a building unit and every row it wrote. Unsealed output is deleted, never quarantined into visibility.

func (*UnitWriter) PutAliases

func (w *UnitWriter) PutAliases(ctx context.Context, aliases []model.NativeAlias) error

PutAliases stores scoped native aliases. The target must be a registered identity now and a visible node in the unit/dependency closure at seal.

func (*UnitWriter) PutDeltaState

func (w *UnitWriter) PutDeltaState(ctx context.Context, kind string, src io.Reader) error

PutDeltaState stores one provider-owned incremental-refresh artifact with the unit it describes: the SCIP DocumentManifest, the dependence KeySet, or whatever a later provider needs to diff its next run without recomputing the previous one. The payload is opaque to storage and read from src in parts, so an artifact is never held whole; there is no bound on its size. It shares the unit's lifetime exactly, so a retired unit takes its artifact with it and no refresh can diff against state whose facts were collected. A second call under the same kind replaces the first.

func (*UnitWriter) PutKeyedNodes

func (w *UnitWriter) PutKeyedNodes(ctx context.Context, facts []model.NodeFact, keys [][]string) error

PutKeyedNodes stores node facts with their evidence in one transaction. Each fact's identity is recomputed from the repository, its kind and its canonical key (Section 9.1) and registered in the node_ids dictionary before the fact row references it; a fact whose ID does not derive, or whose kind disagrees with the identity already registered under that ID, is malformed provider output.

keys is either empty or parallel to facts: keys[i] is every producer key backing facts[i] — the keys its next incremental run will classify as changed, unchanged or removed (Section 11.4) — sorted, without duplicates, each a lowercase hex digest. A fact is backed by more than one key whenever the producer's keys are finer than the identities they resolve to, which is ordinary: a canonical edge is published once and derived from N occurrences. Storage never derives or interprets a key; it records every one so CarryOver can drop the fact when ANY of them is replaced, which is the same condition the producer re-emits the whole fact on.

A repeated node identity within one unit is not an error while it is the same fact: the same declaration can be observed by more than one worker, by two parts of a subdivided unit or, under a delta, by both the fresh import and the carried predecessor, and its keys accumulate. A repeat whose stored columns DIVERGE from the row already written is refused (CTX_PROVIDER_OUTPUT_INVALID) naming the node and the first differing column: the row would otherwise keep whichever description arrived first, and a delta-built unit would keep a different one from a full re-import of the same source. Nothing of a refused fact is stored, its evidence included.

func (*UnitWriter) PutKeyedRelations

func (w *UnitWriter) PutKeyedRelations(ctx context.Context, facts []model.RelationFact, keys [][]string) error

PutKeyedRelations stores canonical edges with their occurrence evidence. Relation identity is recomputed; both endpoints must already be registered identities. keys carries every producer key backing each fact, exactly as PutKeyedNodes documents. A relation_facts row stores nothing but its identity, so a repeat cannot diverge the way a node fact can; it adds its keys to the ones already recorded and is otherwise a no-op.

func (*UnitWriter) PutNodes

func (w *UnitWriter) PutNodes(ctx context.Context, facts []model.NodeFact) error

PutNodes stores node facts with their evidence, recording no delta keys.

func (*UnitWriter) PutRelations

func (w *UnitWriter) PutRelations(ctx context.Context, facts []model.RelationFact) error

PutRelations stores canonical edges with their occurrence evidence, recording no delta keys.

func (*UnitWriter) PutSearchUnits

func (w *UnitWriter) PutSearchUnits(ctx context.Context, docs []model.SearchUnit) error

PutSearchUnits stores lexical documents and their FTS index rows in the same transaction. The FTS insert is explicit: search_fts is contentless (ADR-0003 §2.1), so nothing maintains it by cascade or by row counts and the body text reaches the index from this document and nowhere else -- the database keeps no second copy of it (Section 12.2). Each posting's rowid is recorded as the row's doc_id, which is what every read resolves a document by and what a delta carry-over inherits instead of re-indexing.

token_count is computed here with the index's own unicode61 tokenizer over exactly the columns search_fts indexes, so the Section 12.4 length statistics agree with the index. SearchUnit.TokenCount is informational on input and ignored; on read it is the stored, authoritative value.

func (*UnitWriter) UnitID

func (w *UnitWriter) UnitID() model.UnitID

UnitID is the immutable key of the unit being written.

type WatchHeartbeat

type WatchHeartbeat struct {
	SessionID     string
	WriterPID     int
	BeatAt        time.Time
	LastPassAt    *time.Time
	PendingEvents *int64
	ExpiresAt     time.Time
}

WatchHeartbeat is what one running watch publishes about itself so a second process can read it: which watch it is, which process owns it, when it last published, when its last pass completed, how many notification events are still pending, and the deadline the writer promised to refresh before.

SessionID is the watch's own identity, minted once per watch. It, and not the pid, is what tells two watching processes apart: a pid is reused by the operating system, so a row keyed by one could be claimed by an unrelated process that inherited the number.

ExpiresAt is the writer's own deadline and the only liveness signal. A reader compares it to the clock and never recomputes it from configuration: the two processes need not share a configuration, and a reader that derived the window would grant or deny liveness the writer never promised.

BeatAt is when the writer last published the row. It is reported beside the watch, never judged: deriving liveness from it would be the reader reconstructing the writer's window, which is what ExpiresAt exists to prevent.

LastPassAt and PendingEvents are pointers because absent and zero are different answers. A watch driven only by periodic reconciliation has no notification queue to count, and a watch that has not completed a pass yet has no pass time and publishes no pending count either; reporting either as zero would state that the watch is caught up. A row with no pass time is therefore a watch that is running and covering nothing -- waiting for whichever process holds the workspace -- which is a third answer beside a covering watch and no row at all.

func (WatchHeartbeat) Live

func (hb WatchHeartbeat) Live(now time.Time) bool

Live reports whether the process that published this row is still refreshing it, which is the one rule this product answers "is that process still there" by: the writer's own unelapsed deadline, compared against the reader's clock. It is the rule the run ledger judges a run by, stated once here so that every surface reading a heartbeat -- the status listing, the doctor's check, the resource block -- and the sweep below cannot come to different conclusions about the same row. No pid is probed: probing one is neither portable nor race-free, and the number may since have been reused.

Jump to

Keyboard shortcuts

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