Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions docs/retrieval-evaluation.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,39 @@ signed-index round trips. Profile chunk schema field order also matches its
constructor, preserving the existing JSON-derived integrity fingerprint on
reload instead of invalidating every nonempty advisory index.

## Gold body line endings and index compatibility

Gold chunk construction uses the existing authorization canonicalization: CRLF
and lone CR in a curated body become LF before computing its chunk ID, BM25 data,
and citation body hash. Review and evidence profiles inherit this same Gold body.
No source file is rewritten, no JSON value is double-escaped, and receipt v1
canonical bytes, digests, signing payloads, and signatures are unchanged.
Bronze keeps its separate CRLF-only normalization: lone CR remains part of its
body, body hash, and line-range citations.

For curated bodies containing lone CR, cache provenance additionally records
`source_body_sha256`, the hash of the body after CRLF-only corpus normalization.
Gold stores it on the chunk; review/evidence store it in authorization provenance.
This optional index-v2 field is not a signed receipt field or admission authority.
It keeps live fingerprints sensitive to lone-CR representation changes even
though served text and chunk IDs are canonical. Such a post-build mutation still
refuses search and citation reads as stale until a legitimate rebuild; a
canonical-equivalent receipt does not bypass that refusal.

**After this update, rebuild all three indexes if any indexed Gold body contains
lone CR**, using `node dist\src\cli\main.js build --root YOUR_VAULT`. Old snapshots
for those pages fail live verification. Their Gold chunk IDs (including Gold in
review/evidence), citation body hashes, and corpus fingerprints change; issue new
citations after rebuilding. Existing LF/CRLF-only Gold chunks and fingerprints
are unchanged by this fix. The index format remains v2 and does not require new
receipts or restaging Silver. Older binaries reject the new optional provenance
field when present, so rolling back also requires rebuilding affected indexes.

The dedicated `test/gold-body-normalization.test.ts` regression uses disposable
signing keys and real temporary vaults to cover LF, CRLF, lone CR, mixed endings,
unchanged source bytes, all three profiles, live staleness/rebuild, and unchanged
Bronze hashes and lossless citations.

## Authorization evidence and limits

The opt-in runner uses test-only Ed25519 keys and real files. `runBuild` produces
Expand Down
12 changes: 12 additions & 0 deletions site/src/content/docs/concepts/tiers.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,18 @@ indexes are unsupported and must be restaged or rebuilt. This does not refer to
model-facing `RefinementDraft` v1 or external authorization receipt v1; neither changes
the stored v2 formats.

Gold bodies are served with CRLF and lone CR normalized to LF, matching the
existing signed canonical representation. This applies to Gold in all three
indexes and never rewrites knowledge or Bronze files. Bronze still normalizes
CRLF only, preserving lone CR in its hashes and citations.

After updating, run `ziggurat build` for any vault whose indexed Gold bodies
contain lone CR. Their chunk IDs and fingerprints change, so obtain new
citations after rebuilding. LF/CRLF-only Gold chunks are unchanged. Live
verification still refuses a post-build lone-CR representation change until
rebuild; receipts and their signed bytes do not change. See the
[index compatibility details](https://github.com/patschmittdev/Ziggurat/blob/main/docs/retrieval-evaluation.md#gold-body-line-endings-and-index-compatibility).

:::caution[Gold is not a truth label]
Every retrieved chunk carries `content_role: reference` and
`instruction_authority: none`; see [Gold's limits](./provenance-and-authority.md).
Expand Down
2 changes: 2 additions & 0 deletions src/contracts/gold-index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const GoldChunkSchema = z.object({
path: z.string().min(1),
heading: z.string().min(1),
body: z.string().min(1),
source_body_sha256: Sha256Schema.optional(),
bronze_lineage: z.array(z.object({
path: z.string().min(1),
sha256: Sha256Schema,
Expand Down Expand Up @@ -53,6 +54,7 @@ const ProfileProvenanceSchema = z.discriminatedUnion('kind', [
path: z.string().min(1),
sha256: Sha256Schema,
}).strict()),
source_body_sha256: Sha256Schema.optional(),
}).strict(),
]);

Expand Down
11 changes: 8 additions & 3 deletions src/retrieval/chunks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { sha256Text } from '../bronze/canonical.js';
import { normalizeText } from '../authorization/canonical.js';
import { canonicalBronzeBody, sha256Text } from '../bronze/canonical.js';
import type { CuratedPage } from '../contracts/index.js';
import type {
GoldChunk,
Expand All @@ -17,11 +18,15 @@ export function makeGoldChunk(
bronzeLineage: Array<{ path: string; sha256: string }>,
authorization: VerifiedAuthorization,
): GoldChunk {
const sourceBody = canonicalBronzeBody(pageBody);
const body = normalizeText(sourceBody);
return {
id: chunkId('gold', path, pageBody),
id: chunkId('gold', path, body),
path,
heading: page.title,
body: pageBody,
body,
// Preserve lone-CR mutation detection without changing ordinary LF/CRLF chunks.
...(sourceBody === body ? {} : { source_body_sha256: sha256Text(sourceBody) }),
bronze_lineage: bronzeLineage,
profile: 'gold',
tier: 'gold',
Expand Down
2 changes: 2 additions & 0 deletions src/retrieval/profile-index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ function goldProfileChunk(
reviewer_id: chunk.authorization.reviewer_id,
key_id: chunk.authorization.key_id,
bronze_lineage: chunk.bronze_lineage,
...(chunk.source_body_sha256 === undefined
? {} : { source_body_sha256: chunk.source_body_sha256 }),
},
);
}
Expand Down
Loading