Regulated audit
The failure this prevents: an answer cites a document nobody agreed was in scope. It has a real document ID, a real page and a verbatim quote. At the point of use it is indistinguishable from a correct citation — and the cite-or-abstain guarantee still holds, because that guarantee is scoped to the index, while the auditor’s question is scoped to the corpus.
Every other freshness mechanism is change-driven: it reacts to an ingest, an update, a revoke. None of them notice an artifact that arrived by a path the change log never saw — a crashed ingest, a restored snapshot, a shared bucket prefix, a run against the wrong config.
-
Declare the agreed corpus. The manifest is authored by you, not derived from the index — a manifest generated from the index could never disagree with it, which is the entire point.
from citenexus.reconcile import CorpusManifest, CorpusEntrymanifest = CorpusManifest(manifest_version="2026-08-16",entries=(CorpusEntry(document_id="policy-handbook", sha256="3c58d7e6…",version="4.1", current=True,effective_date="2026-01-01"),CorpusEntry(document_id="pension-schedule", sha256="9a01bb2f…",version="2.0", current=True),),) -
Reconcile. It reads; it changes nothing.
report = rag.reconcile(manifest)report.orphans # indexed, NOT in the manifest -> the ghost documentsreport.missing # in the manifest, NOT indexed -> a failed ingestreport.drifted # in both, content hash differs -> the source changedThe three sets are disjoint, and running it twice on an unchanged index produces an identical report. A clean corpus reports empty.
-
Remediate explicitly, never as a side effect.
if report.orphans:removal = rag.remediate(report) # routes through the same revoke pathassert not rag.reconcile(manifest).orphans
reconcile() and remediate() do not exist in this port — and neither does
the thing they audit. Go holds no index, no manifest and no store: the corpus is
the []answer.Doc slice the caller builds for each Ask, so there is no gap
between “the agreed corpus” and “what is indexed” for a reconciliation to find.
That is not the same as the problem going away. It moves the whole audit into your code: whatever assembles that slice is now the thing an auditor has to trust, and it is the thing that must be able to prove which documents it put in. If you need the diagnostic itself — orphans, missing, drifted, and the append-only audit line — the index has to be Python’s.
reconcile() and remediate() do not exist in this port, because there is no
index to reconcile: JavaScript takes the corpus as an argument to ask and stores
nothing between calls.
The audit obligation does not disappear, it relocates — the code that builds the corpus array becomes the thing an auditor checks, and it must be able to say which documents it included and why. For the orphan / missing / drifted report and its append-only log, use the Python index.
Why remediation is a separate call
Section titled “Why remediation is a separate call”A diagnostic that deletes is a diagnostic nobody dares run. Auto-remediation on a
shared bucket is a data-loss footgun, so reconcile() is read-only by
construction and remediate() is the deliberate second step. Removal goes
through the existing revoke path — one deletion mechanism, not two.
Version supersession
Section titled “Version supersession”When the manifest designates one version of a document as current, earlier
versions of the same document_id are reported as drifted, not as orphans.
They are a known, declared state rather than something that snuck in.
In-scope is not the same as authoritative
Section titled “In-scope is not the same as authoritative”Reconciliation proves a cited document belongs to the agreed corpus. It does not rank the documents inside it, so an in-scope but non-binding source can still win the citation. Attach each document’s standing at ingest and make strict mode refuse below a floor: see Authority — grounding is not standing.
The audit artifact
Section titled “The audit artifact”Each run appends to an append-only stream, so “the index matched the agreed
corpus at time T” becomes an evidence artifact rather than a verbal assurance.
It is a plain JSONL object in the store, one line per run, under the partition’s
eval/ prefix — so reading it needs no API at all:
import jsonfrom pathlib import Path
# eval/<partition>/reconcile_log.jsonl — here, the default workspace=default.log = Path("./citenexus-data/eval/workspace=default/reconcile_log.jsonl")
for line in log.read_text(encoding="utf-8").splitlines(): print(json.loads(line)) # timestamp, manifest_version, counts per setNothing in this port writes the stream, because nothing in it reconciles. The
artifact is plain JSONL in the store, though, so reading one that a Python
run produced needs no CiteNexus at all — bufio.Scanner plus encoding/json
over the same file or S3 key. That portability is the point of the format.
Nothing in this port writes the stream, because nothing in it reconciles. Reading
a stream a Python run produced needs no CiteNexus — the file is plain JSONL, so
readFile + split("\n") + JSON.parse is the whole reader. That is deliberate:
an auditor should not have to install a library to read the evidence.
That the artifact is readable with json and nothing else is the point: an
auditor does not have to trust — or install — this library to read it. On S3 it
is the same key under the bucket. There is no public accessor for the stream
today; citenexus.reconcile.read_audit exists but takes the storage backend,
which the client does not expose.
Pass audit=False for a strictly zero-write run when you need to prove the
diagnostic touched nothing at all.