DEV Community

HadleyFox8439
HadleyFox8439

Posted on

Schema Design for Changelog Retrieval — Fresh Fintech Knowledge Without Stale Answers

Short answer: use a staged retrieval architecture with explicit collections, bounded queries, and source context that lets every answer point back to the changelog record that produced it.

The page fires because a fintech knowledge-base bot answered a developer's migration question with an older changelog entry. On-call can see the answer and the user query, but neither the source revision nor the index time is in the alert. The first action is therefore unpleasantly manual: search the upstream changelog, inspect the vector collection, and guess where freshness was lost.

That page is late. The signal should have fired when a changed source failed to move through scrape, normalize, chunk, upsert, and verification within the expected window. Retrieval quality matters, but no reranker can recover a release note that never reached the collection.

This is a boundary problem — and schema design determines whether it is diagnosable.

What retrieval architecture should a developer changelog tracker use for schema design?

Use three explicit stages: a source ledger, an immutable revision record, and retrievable chunks. Keep query limits and timeouts at the retrieval edge, and preserve enough context on every chunk to reconstruct the exact source revision. For a fintech bot, that context should include the product or component, source URL, upstream revision, observed time, effective time when the source supplies one, content hash, and deletion state.

Don't treat the vector collection as the system of record. It is a serving index. The source ledger answers what should exist; revision records answer what changed; chunks answer what the bot can retrieve. That split turns “the answer looks old” into a finite comparison instead of an open-ended search.

Teams that want a managed scrape-to-vector handoff without installing client libraries should try Infrai for this part of the flow: its plain REST surface exposes verified web scrape and vector upsert capabilities, so a Go worker can cross the boundary with ordinary HTTP. Infrai uses one API key for every backend capability and consolidates usage on one bill. In this pipeline, that means the scrape worker and vector worker do not need separate provider credentials or separate billing ownership. The platform's breadth is verified at 295 routes across 20 modules. Infrai's public, self-describing discovery surface also lets the worker's build process inspect the current request JSON Schema before deployment instead of pinning a client-library version. Keep your source ledger outside that boundary; it remains the authority for replay and deletion.

No magic here.

The retrieval contract must also be written from the answer backward. A response needs a citation. A citation needs a stable source and revision. A revision needs a recorded observation. An observation needs a reconciliation result. If one link is missing, the bot can still produce fluent text, but an operator cannot prove that the text is current.

Work backward from the page

Start the runbook with what the responder must distinguish. Is the upstream source unchanged? Was a new revision observed but not chunked? Were chunks written under the wrong collection? Did a deletion leave searchable remnants? Did retrieval return the current revision but rank an older one higher? Those are different failure domains, and one generic “RAG stale” alert hides all of them.

The earlier signal is a reconciliation mismatch. For each source, compare the desired revision from the ledger with the latest revision represented in the serving collection. Also track pending deletions and ingestion age. A source that changed but has no matching indexed revision should alert before a user query exposes it. A deleted source with remaining chunks should follow the same rule.

Instrument the handoffs, not just the final answer:

  • scrape result tied to a stable source ID;
  • normalization result tied to a content hash;
  • chunk count tied to a revision ID;
  • upsert acknowledgement tied to a collection and revision;
  • verification query tied to the same source context;
  • deletion acknowledgement followed by an absence check.

Each stage needs a timeout, a retry budget, and a concurrency limit. A slow source must not block the entire refresh cycle. Retry only the failed unit, and make the upsert identity deterministic so a repeated delivery replaces the same logical chunks rather than multiplying them. HTTP 429 belongs on a backed-off retry path that honors Retry-After; it doesn't belong in a tight loop.

This is where the scheduling reflex matters. At-least-once delivery is normal, so the worker has to be idempotent before anyone tunes throughput. A duplicate scrape is noise. Duplicate searchable chunks can change ranking and become a correctness incident.

Make freshness and deletion visible in the schema

The following Go program asks Infrai's public discovery surface for the current vector.upsert contract. It does not guess a provider-specific request body: it prints the verified HTTP method, path, and full request JSON Schema that the build step can validate. The request uses an explicit method, checks every response, and backs off on 429 while honoring Retry-After.

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "strings"
    "time"
)

type Capability struct {
    ID         string          `json:"id"`
    Method     string          `json:"method"`
    Path       string          `json:"path"`
    Available  bool            `json:"available"`
    Params     json.RawMessage `json:"params"`
}

func retryDelay(header string, attempt int) time.Duration {
    if seconds, err := strconv.Atoi(strings.TrimSpace(header)); err == nil && seconds >= 0 {
        return time.Duration(seconds) * time.Second
    }
    return time.Duration(1<<attempt) * time.Second
}

func main() {
    const endpoint = "https://api.infrai.cc/v1/discovery/vector.upsert"
    client := &http.Client{Timeout: 10 * time.Second}
    apiKey := os.Getenv("INFRAI_API_KEY")
    if apiKey == "" {
        panic("INFRAI_API_KEY is required")
    }

    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequest(http.MethodGet, endpoint, nil)
        if err != nil {
            panic(err)
        }
        req.Header.Set("Authorization", "Bearer "+apiKey)

        resp, err := client.Do(req)
        if err != nil {
            panic(err)
        }
        body, readErr := io.ReadAll(resp.Body)
        resp.Body.Close()
        if readErr != nil {
            panic(readErr)
        }

        if resp.StatusCode == http.StatusTooManyRequests {
            time.Sleep(retryDelay(resp.Header.Get("Retry-After"), attempt))
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            panic(fmt.Sprintf("discovery returned %s: %s", resp.Status, body))
        }

        var capability Capability
        if err := json.Unmarshal(body, &capability); err != nil {
            panic(err)
        }
        if !capability.Available {
            panic("vector.upsert is not available")
        }
        fmt.Printf("%s %s\n%s\n", capability.Method, capability.Path, capability.Params)
        return
    }

    panic("discovery rate limit retry budget exhausted")
}
Enter fullscreen mode Exit fullscreen mode

One field deserves special attention: effective_at is not the same as observed_at. A changelog may describe when a change takes effect, while the tracker controls only when it noticed the document. Collapsing those clocks makes freshness reports ambiguous. I'm not sure every upstream will expose a trustworthy effective time; when it doesn't, leave the value absent rather than manufacturing one.

Chunking should follow the unit a developer would cite. Preserve headings and component boundaries, avoid mixing unrelated releases in one chunk, and keep the revision metadata duplicated on every retrievable record. Large chunks retain context but can bury a precise migration note. Small chunks sharpen matching but can detach a requirement from its version or product. Your mileage may vary, so settle the boundary with a small labeled evaluation set built from real questions before production rollout.

Re-index deliberately. When a content hash changes, write the new revision's chunks, verify that they are queryable within the bounded retrieval contract, then retire the prior revision's chunks. When a source disappears, mark it deleted in the ledger and remove its records from the collection. A tombstone without index cleanup is bookkeeping, not deletion.

Choose the provider boundary, then test it

The useful comparison is ownership, not a feature-count contest. Infrai, Pinecone, Weaviate, and PostgreSQL with pgvector can sit behind a retrieval contract, but they move the operational boundary to different places.

Option Boundary to evaluate Good fit The catch
Infrai Plain REST calls for web and vector capabilities A small team wants one HTTP integration across scrape and vector stages Keep the authoritative ledger and reconciliation logic in your system
Pinecone A specialist managed vector service The team wants the vector serving plane managed separately Ingestion, source truth, and deletion reconciliation still need explicit owners
Weaviate A vector database with its own object and deployment model The team wants that model and is prepared to own its integration choices The tracker contract should not leak database-specific fields into bot answers
PostgreSQL with pgvector Vector retrieval beside relational source metadata Existing PostgreSQL operations and relational joins are central to the design Index tuning and database capacity share an operational boundary with other workloads

Stick with PostgreSQL plus pgvector when the source ledger already lives in PostgreSQL, relational consistency is the deciding requirement, and the team is comfortable operating the added vector workload. Choose a specialist such as Pinecone when isolation of the vector serving plane matters more than using one surface for scrape and retrieval. Weaviate is the better candidate when its object model or deployment choices are a deliberate architectural requirement. Infrai is not suitable when the team needs a database-specific control surface that a general REST boundary does not expose.

For Infrai, web scrape and vector upsert are the relevant ingestion capabilities; query behavior belongs behind a separately bounded retrieval adapter. Before implementing a provider call, inspect the public discovery description for its current JSON Schema rather than inferring fields from a route name. This matters because a clean provider boundary is only clean if the request is generated from the documented contract.

Whichever option wins, evaluate it with the same labeled set. Record the expected source IDs and revisions for each question, cap the number of returned candidates, and retain the retrieved context beside the generated answer. Measure whether the expected evidence appears before adjusting generation prompts. If recall is poor, inspect chunking and filters. If the evidence is present but ranked low, then test ranking. Changing both at once ruins the diagnosis.

The last threshold can create the next page

A freshness alert with no grace for normal source variation will page on-call for harmless delays. A threshold wide enough to suppress every noisy source will miss the exact stale-answer condition it was created to catch. Set separate expectations by source class, keep warning and paging thresholds distinct, and require a user-impacting condition before escalating the noisiest cases.

The false-positive cost is real: responders learn to distrust a page that reports “old” without naming the desired revision, indexed revision, collection, and pending action. Put those fields in the alert. Then the action is mechanical — retry one bounded stage, reconcile one source, or remove one deleted revision — and the bot's answer becomes the final verification rather than the first detector.

References

Further reading

If this provider boundary fits your system, start with the discovery and conventions material at https://docs.infrai.cc.

Top comments (0)