Contracts¶
The data model. Every model here is a frozen Pydantic v2 model with
extra="forbid" — an unknown key is a construction-time error, not a field
that gets quietly dropped (SPEC.md §2, fail closed).
Chunk carries character offsets into its source document, and Citation
carries document ID + chunk ID + offsets. Together they are what makes
"citations are verifiable" a checkable property rather than a claim:
verify_citation
re-reads the source file and confirms the span still says what the citation
says it says.
RetrievalResult carries source_class and extractor (ADR-0016), which
route citation verification to the right re-derivation path. Their defaults
— "text" and None — exist for backward compatibility with a published
contract, not as a value any construction site should take implicitly: an
AST-level test asserts that every RetrievalResult(...) in src/groundkit/
passes both fields explicitly. evals/ is exempt, and deliberately — the two
sites there build synthetic fixtures whose provenance is invented for the
harness rather than carried through from a store record, so there is nothing
upstream of them for a default to silently replace.
groundkit.contracts ¶
Core data model: Document, Chunk, RetrievalResult, Citation, SearchResponse, EmbeddingIdentity, CollectionManifest.
Ported from ARP's agentic_v2/rag/contracts.py per ADR-0001, with two
deliberate changes: chunks carry character offsets into their source
document (the substrate citation resolution depends on — SPEC.md §5.2), and
the high-confidence threshold is a named constant instead of an inline
literal.
All models are frozen and reject unknown fields. Functions that transform these models return new objects; nothing here is mutated.
Chunk ¶
Bases: BaseModel
A chunk of text extracted from a document, addressable by offsets.
content must be the exact substring
document.content[start_offset:end_offset] of its parent document —
chunker tests enforce the substring property; this model enforces the
length arithmetic so a drifting chunker cannot construct a valid Chunk.
Attributes:
| Name | Type | Description |
|---|---|---|
chunk_id |
str
|
Unique identifier for this chunk. |
document_id |
str
|
ID of the parent document. |
chunk_index |
int
|
Position of this chunk within the document. |
content |
str
|
The chunk text. |
start_offset |
int
|
Character offset of |
end_offset |
int
|
One past the last character of |
metadata |
dict[str, Any]
|
Inherited + chunk-specific metadata. |
content_hash
property
¶
SHA-256 hash of the chunk content for deduplication.
Recomputed on every access, and deliberately not a
functools.cached_property (GK-028). Staleness is not the
objection -- the model is frozen and the hash is a pure function of
content, so a cache could never disagree with its input. Unfreezing
is: pydantic's __setattr__ special-cases cached_property
before it consults the frozen config
(pydantic/main.py::BaseModel._setattr_handler, checked against
the pinned 2.13 in this tree), so chunk.content_hash = "..."
would stop raising and start writing straight into __dict__.
Every later reader -- BM25's and dense's tie-breaks, the value
index/metadata.py persists -- would then use a string that is not
the hash of content, on a model whose entire contract is that it
cannot be changed after validation. Trading that for an unmeasured
saving on a bounded string is the wrong way round; a caller that
wants the hash once caches it itself at build time
(index/bm25.py). tests/test_contracts.py pins the assignment
refusal so the trade-off is enforced rather than remembered.
Citation ¶
Bases: BaseModel
A verifiable pointer from a retrieved passage back to its source.
Attributes:
| Name | Type | Description |
|---|---|---|
document_id |
str
|
ID of the source document. |
chunk_id |
str
|
ID of the cited chunk. |
source |
str
|
The document's source identifier (path/URL). |
source_class |
SourceClass
|
How to resolve |
extractor |
str | None
|
Extractor identity for an |
start_offset |
int
|
Character offset where the cited span starts. |
end_offset |
int
|
One past the last character of the cited span. |
CollectionManifest ¶
Bases: BaseModel
The persisted embedding identity a collection was built with (ADR-0004).
Identity is the triple (provider, model_name, dimensions), checked
as a triple — never dimensions alone. Distinct embedding models can
share a vector width (nomic-embed-text and all-mpnet-base-v2 are
both 768-dimensional), so a width-only check would admit exactly the
model swap this manifest exists to reject. Written once, by
SQLiteMetadataStore.write_manifest on a collection's first dense
write, and immutable for the collection's lifetime thereafter.
Attributes:
| Name | Type | Description |
|---|---|---|
provider |
str
|
Embedding provider the collection was built with. |
model_name |
str
|
Model identifier the collection was built with. |
dimensions |
int
|
Embedding vector width the collection was built with. |
created_at |
str
|
ISO-8601 UTC timestamp of the manifest's creation. |
Document ¶
Bases: BaseModel
A source document before chunking.
Attributes:
| Name | Type | Description |
|---|---|---|
document_id |
str
|
Unique identifier (auto-generated UUID if omitted). |
source |
str
|
File path, URL, or other source identifier. |
content |
str
|
Raw text content of the document. |
source_class |
SourceClass
|
How |
extractor |
str | None
|
Identity of the extractor that produced |
metadata |
dict[str, Any]
|
Arbitrary key-value metadata (author, date, etc.). |
EmbeddingIdentity ¶
Bases: BaseModel
The embedding identity triple a collection is bound to (ADR-0004 decision 2).
The input to a manifest write or verify, and the exact set of fields
ADR-0004 defines identity as — no more. Distinct from
:class:CollectionManifest, which is what comes back out of the store
and additionally carries the created_at stamp the store assigns.
Deliberately not an EmbeddingConfig: that model also carries
operational settings (base_url, batch_size, timeout_seconds)
that have nothing to do with which semantic space a collection lives in,
and its provider is a closed Literal over the providers this repo
ships, which a third-party
:class:~groundkit.providers.protocols.EmbeddingProtocol implementation
could not satisfy. Taking the narrow triple lets the identity be read
straight off the embedder that actually produces the vectors.
Attributes:
| Name | Type | Description |
|---|---|---|
provider |
str
|
Embedding provider identity (e.g. |
model_name |
str
|
Model identifier used to embed. |
dimensions |
int
|
Embedding vector width. |
RetrievalResult ¶
Bases: BaseModel
A single retrieval result, always citation-bearing.
Producers must normalize scores into >= 0.0 before construction —
feeding raw model logits into this contract is the ARP defect ADR-0001
hazard 2 records; it is a producer bug, not a contract relaxation.
Attributes:
| Name | Type | Description |
|---|---|---|
content |
str
|
The matched chunk text. |
score |
float
|
Relevance score (>= 0.0, higher is better). |
document_id |
str
|
ID of the source document. |
chunk_id |
str
|
ID of the matched chunk. |
source |
str
|
The document's source identifier (path/URL). |
start_offset |
int
|
Character offset of the match in its document. |
end_offset |
int
|
One past the last character of the match. |
metadata |
dict[str, Any]
|
Chunk metadata. |
SearchResponse ¶
Bases: BaseModel
Response from a search query.
Attributes:
| Name | Type | Description |
|---|---|---|
query |
str
|
The original query text. |
results |
list[RetrievalResult]
|
Ordered list of retrieval results. |
total_results |
int
|
Total number of results found. |
metadata |
dict[str, Any]
|
Pipeline metadata (latency, stages, etc.). |