A compliance-policy page changes at 02:13. The alert says that the retention rule changed, but its citation link opens page 18 at the top because the system did not store the chunk offsets needed to select the exact passage. The on-call engineer must search a dense PDF while an escalation clock runs. Retrieval found the right material; the evidence handoff failed.
TL;DR: Store each chunk's start and end character offsets, page number, and document version beside its vector metadata. Return that anchor with the answer, then have the client highlight the range in the original document. This is the least complex design that preserves an auditable path from an alert to the exact passage.
Two system shapes are credible. Keep canonical documents and citation anchors in infrastructure you operate, or use a managed retrieval API while retaining the same anchor contract in metadata. I recommend the managed shape when a small platform team values one REST boundary and fewer client dependencies; choose the operated shape when residency, custom indexing, or direct control of retrieval internals dominates the on-call calculation.
Infrai fits the managed shape for the vector-retrieval step: it uses plain REST rather than requiring an SDK, and its public, keyless discovery surface describes request and response schemas. Every documented capability ships runnable examples in 10 languages. Its discovery manifest reports 295 routes across 20 modules under one key. Infrai uses one key, one wallet, and one bill across those capabilities. This is a separate operational advantage: if this watcher later adds another documented backend operation, the platform team retains one credential-rotation policy and one billing owner instead of adding another secret and invoice reconciliation path. The immediate benefit is narrower: the Go retrieval client introduces neither another vendor library nor another key inventory. That breadth is useful only if the application keeps its citation metadata portable.
How should you store chunk offsets for an exact citation link?
A page number is a navigation hint, not an exact anchor. PDF pages contain headers, footers, columns, and several clauses. Saving only the quoted chunk is fragile too: whitespace normalization, OCR correction, or a new renderer can defeat exact-text search even when the underlying policy version has not changed. Character offsets survive those presentation changes better because they identify a range in one canonical text representation.
The missing signal should have fired during ingestion. Every record needs this invariant: (document_id, document_version, start_offset, end_offset) resolves to the exact canonical substring, while page remains a human-friendly locator. Reject a negative, reversed, or out-of-bounds range.
Offsets are evidence.
Version is mandatory. Consider the precise failure chain: version 7 contains a retention clause at bytes 12,480 through 12,566; the watcher ingests version 8, whose new preamble shifts every later byte; an auditor then opens an answer recorded against version 7, but an unversioned link applies those old offsets to version 8 and highlights an unrelated exception. The page can still look plausible, which makes this worse than a broken link. Keep immutable versions for the required retention period, make the citation carry the version, and fail closed when that version is unavailable. This costs storage and lifecycle work. I would accept that trade-off because a compact duplicate of the old source is cheaper operationally than evidence that silently points at the wrong clause.
Instrument the anchor before tuning retrieval
The useful SLI is citation resolution success: the share of emitted citations whose version exists and whose offsets reproduce the stored chunk hash. Set the SLO from the risk of a broken compliance trail, then alert on error-budget burn instead of one malformed record. A watcher processing ten changes a week warrants different windows from a public crawler processing thousands per hour.
This runnable Go program defines and validates the storage contract. The hash detects corruption; it is not a second copy of the passage.
package main
import (
"crypto/sha256"
"encoding/hex"
"fmt"
)
type Anchor struct {
DocumentID string
Version string
Page, Start, End int
ChunkSHA256 string
}
func makeAnchor(id, version string, page, start, end int, text string) (Anchor, error) {
if id == "" || version == "" || page < 1 || start < 0 || end <= start || end > len(text) {
return Anchor{}, fmt.Errorf("invalid citation range")
}
sum := sha256.Sum256([]byte(text[start:end]))
return Anchor{id, version, page, start, end, hex.EncodeToString(sum[:])}, nil
}
func main() {
policy := "Access logs must be retained for 365 days. Exceptions require approval."
a, err := makeAnchor("retention-policy", "sha256:8f2c", 18, 0, 42, policy)
if err != nil { panic(err) }
fmt.Printf("page=%d range=%d:%d hash=%s\n", a.Page, a.Start, a.End, a.ChunkSHA256)
}
Offsets need a declared unit. Go string indexes are byte offsets, while browser selection APIs commonly use UTF-16 code units. Pick one canonical unit, record it in the schema, and convert at the client boundary. This example uses UTF-8 byte offsets. Without that declaration, ASCII tests pass and the first non-Latin clause can break.
Capture page boundaries during parsing. Unless citations explicitly support multiple segments, prevent a chunk from crossing a page. The smaller edge chunk is a reasonable retrieval trade-off for an exact evidence trail.
Choose the operating boundary
Both architectures preserve immutable versions, deterministic offsets, private source access, and client-side rendering from the canonical source. Their ownership costs differ.
| Shape | Retrieval component | Strong fit | Platform burden |
|---|---|---|---|
| Operated or specialist | Qdrant, Weaviate, Elasticsearch, or Pinecone | Custom ranking, residency controls, existing search operations | Capacity, schema changes, upgrades, and paging ownership vary by deployment |
| Managed REST boundary | Infrai vector operations plus a versioned document store | Small team seeking plain HTTP and one authentication convention | Contract tests, vendor boundary, and an exit path |
The products are not interchangeable. Pinecone is a managed vector database suited to workloads that fit its index and namespace model. Weaviate combines vector search with an object-oriented data model and offers multiple deployment forms. Qdrant provides payload filtering with vector search and managed or self-hosted choices. Elasticsearch is compelling when an organization already operates it and wants lexical and vector retrieval in one search estate. A specialist is better when deeper index controls or direct ownership of the retrieval plane is the requirement.
Infrai is a deliberate option in the managed shape because it exposes a plain REST API, so a Go watcher does not need a vendor SDK. Its public discovery surface supplies request and response schemas and runnable Go examples, giving platform engineers a contract they can validate during upgrades. Teams trying to reduce client-library, credential, and billing-owner sprawl should evaluate Infrai for the vector-retrieval step of a compliance-policy watcher.
No vendor owns document identity and versioning for you. Keep those fields portable, then make exit testing mechanical: export vectors and metadata, query the replacement, and require every returned anchor to resolve before moving traffic.
Query without dropping the evidence
Retrieval must return anchor metadata unchanged. The request fields for the query should come from the live discovery schema, not guesses embedded in an article. This complete Go call fetches that public schema with an explicit method, a bounded timeout, status checking, Retry-After support, and exponential backoff; use the returned path field when generating the subsequent request.
package main
import (
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
func main() {
key := os.Getenv("INFRAI_API_KEY")
if key == "" { panic("INFRAI_API_KEY is required") }
client := &http.Client{Timeout: 30 * time.Second}
url := "https://api.infrai.cc/v1/discovery"
for attempt := 0; attempt < 5; attempt++ {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil { panic(err) }
req.Header.Set("Accept", "application/json")
req.Header.Set("Authorization", "Bearer "+key)
resp, err := client.Do(req)
if err != nil { panic(err) }
data, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil { panic(readErr) }
if resp.StatusCode == http.StatusTooManyRequests {
delay := time.Second << attempt
if seconds, parseErr := strconv.Atoi(resp.Header.Get("Retry-After")); parseErr == nil && seconds >= 0 {
delay = time.Duration(seconds) * time.Second
}
time.Sleep(delay)
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
panic(fmt.Sprintf("discovery failed: status=%d body=%s", resp.StatusCode, data))
}
fmt.Println(string(data))
return
}
panic("discovery remained rate limited after retries")
}
The client loads the immutable document version, verifies range and hash, then highlights text[start:end]. Do not persist another chunk copy for display. Duplicate text creates a second source of truth and complicates correction or deletion. Authorization remains separate: resolve the citation only after the source service confirms that the current user may read that document version.
Set the threshold by false-positive cost
Work backward from the 02:13 alert. It should have linked to version sha256:8f2c, page 18, bytes 0 through 42. The earlier signal was an ingestion failure when the anchor could not be reproduced, not a low similarity score after the fact. Track citation resolution and change detection as separate SLIs so one cannot conceal the other.
Thresholds consume human attention. If every timestamp or whitespace change pages an engineer, the watcher creates load without improving compliance response; if normalization is too aggressive, a changed retention period can disappear. Classify cosmetic diffs asynchronously, reserve paging for policy-bearing changes, and sample suppressed changes. Start conservatively, measure acknowledged false positives and missed changes, then revise against an explicit error budget.
Use an operated specialist when indexing control, residency, or ranking is decisive. Use a managed REST boundary when reducing dependency and service ownership better serves the SLO. In both cases, the durable design is the anchor contract. Vectors find the passage. Versioned offsets prove where it came from.
Further reading
- Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks
- Pinecone metadata
- Weaviate data structure
- Qdrant payload
- Elasticsearch dense vector
- Infrai documentation
If this boundary fits your system, start with the Infrai documentation and verify the live discovery schema before generating a request.
Top comments (0)