# description
Per-retrieval-citation attestation envelope binding {query, embedding-model, retrieved-chunks, generation-output} to a cryptographic chain. Closes SYNTHESIS-acquisition Round 20 P5 [[ai-retrieval-and-rag]] Q1 ship-first action. FOURTEENTH JSON Schema in IntelligencePro's catalog. Closes TWO dangling cross-references simultaneously: cycle-316 ip.review.attestation.v1 AND cycle-320 ip.support.resolution.attestation.v1 both already cite 'ip.retrieval.citation.attestation.v1' in their citedEvidenceShaArray.kind enums — this commit ships the cited substrate. The load-bearing anti-hallucination anchor: a generation's claim that 'the agent retrieved source X' is now cryptographically verifiable — recompute sourceContentSha256 against the published source and prove the citation matches the source bytes that actually existed at retrievedAt. Designed for: LangChain + LangGraph + LangSmith retrieval-eval surfaces + LlamaIndex (LlamaParse + LlamaCloud) + Pinecone + Weaviate + Chroma + Qdrant + Milvus + Vespa + pgvector + Elasticsearch retrieval + OpenSearch + Algolia + Vectara + Cohere Embed v4 (first multimodal) + OpenAI text-embedding-3-large/3-small + Voyage AI (MongoDB-acquired Feb-2025) + NVIDIA Llama-Embed-Nemotron-8B (leads multilingual MMTEB) + Gemini Embedding (leads English MTEB v2) + Cohere Rerank v3 + Jina AI embeddings + BGE (BAAI). Composes UPSTREAM with ip.attestation.event.v1 wire row. Composes DOWNSTREAM with ip.review.attestation.v1 (cycle 316) + ip.support.resolution.attestation.v1 (cycle 320) + ip.legal.citation.attestation.v1 (cycle 312) + ip.bi.cited_number.attestation.v1 (cycle 313) via citedEvidenceShaArray[] — every cited-evidence consumer can now cryptographically prove a retrieval actually returned the cited chunks.

# $comment
Discovery: https://ip.tekton.cc/.well-known/ip-knowledge.json (full descriptor) | Badge: https://ip.tekton.cc/api/badge/v1/attestation/retrieval.citation.attestation.v1/<subjectSha256> (shields.io endpoint) | Free dry-run: POST https://ip.tekton.cc/api/credentials/dry-run with body {kind:"retrieval.citation.attestation.v1", body:<candidate>} (no auth required, validates against this schema) | Verification recipe: https://ip.tekton.cc/docs/verification-recipe.md (3-layer trust model + curl trail) | Revocation: https://ip.tekton.cc/credentials/status/v1 (W3C BitstringStatusList v1.0; check bit at credentialStatus.statusListIndex)

# properties.schemaVersion.description
Semver of this schema. Bumps to 2.0.0 land at /credentials/retrieval-citation/v2 — v1 stays immutable (see /docs/schema-versioning.md for the cycle-726 canonical bump-mechanics + soft-change-detection contract).

# properties.citationId.description
Unique RFC 4122 UUID v4 or v7 — this retrieval-citation attestation's canonical identifier. Lets a consumer dedupe across retries + replays.

# properties.queryHashSha256.description
Sha256 of the canonical query bytes (the text submitted to the retrieval system, NOT the model-prompt that generated the text). For privacy-sensitive deployments (medical, legal, financial) the queryText may be omitted but queryHashSha256 still pins WHICH query — a downstream auditor with the original query can recompute and verify.

# properties.queryText.description
Optional plaintext query. When present, sha256(canonical-utf8(queryText)) MUST equal queryHashSha256. Recipe pin (cycle 1181): canonical-utf8(<text>) = sha256 of the UTF-8 byte encoding of <text> AS-PROVIDED, with NO Unicode normalization — equivalent to sha256(<text>.encode('utf-8')). The word 'canonical' refers to the canonical UTF-8 encoding (no BOM, no JSON quoting), NOT to Unicode-NFC normalization. Issuers wanting NFC-stable hashing should pre-normalize before inlining. Optional because some deployments hash queries to protect end-user privacy while still attesting that A query happened.

# properties.embeddingModelVersionSha256.description
Optional sha256 of the canonical embedding-model artifact at retrieval time. For closed-API embeddings (OpenAI / Cohere / Gemini): vendor-published version manifest sha. For OSS embeddings (BGE / Jina / Llama-Embed-Nemotron): sha256 of the safetensors. Pins WHICH WEIGHTS produced the query embedding. Defeats the silent-embedding-update class — OpenAI silently rotated text-embedding-ada-002 weights at least once in 2023, invalidating prior similarity scores; this anchor closes that gap.

# properties.retrievalSystemVersionSha256.description
Optional sha256 of the canonical retrieval-system version artifact at retrieval time. Pinning the system version helps with reproducibility when retrieval algorithms (HNSW parameters, BM25 weights, hybrid-search fusion) evolve.

# properties.retrievedChunks.items.properties.sourceUri.description
URI of the source document the chunk came from. For internal KBs: 'kb://workspace/article/<id>@<version>'. For web: 'https://...'. For S3: 's3://bucket/key'. For HF Datasets: 'hf://datasets/<owner>/<name>@<commit>'. Optional but strongly recommended — the URI lets a downstream auditor cross-reference; sourceContentSha256 is the cryptographic anchor.

# properties.retrievedChunks.items.properties.sourceContentSha256.description
Sha256 of the canonical bytes of the SOURCE DOCUMENT (not the chunk) at the time of retrieval. THIS is the load-bearing anti-hallucination anchor: a downstream verifier recomputes this against the document at sourceUri and proves the citation matches the source bytes that existed at retrievedAt. If a wiki/KB article is silently edited after the retrieval, the sha mismatch surfaces the staleness.

# properties.retrievedChunks.items.properties.chunkSpan.properties.startByte.description
Byte offset within sourceContent where this chunk begins.

# properties.retrievedChunks.items.properties.chunkSpan.properties.endByte.description
Byte offset within sourceContent where this chunk ends (exclusive). When startByte is also present, endByte MUST be >= startByte (the span is [startByte, endByte) half-open: endByte == startByte is an empty chunk, endByte < startByte is impossible). The dry-run validator enforces this per retrievedChunks[] element (cycle 1201, keyword chunkSpanReversed) — Ajv range-checks each offset independently, so a reversed span would otherwise validate clean and break the chunkText = sourceContent.slice(startByte, endByte) contract.

# properties.retrievedChunks.items.properties.chunkSpan.properties.page.description
Optional 1-indexed page number for PDF / paginated sources.

# properties.retrievedChunks.items.properties.chunkSpan.properties.sectionPath.description
Optional human-readable section path (e.g., '§3.2 Refund Policy / Eligibility').

# properties.retrievedChunks.items.properties.chunkSpan.description
Pins WHICH BYTES of the source were the chunk. Lets a downstream auditor narrow citation verification to the relevant span.

# properties.retrievedChunks.items.properties.chunkText.description
Optional plaintext of the chunk. When present, MUST match sourceContent.slice(startByte, endByte). Useful for in-line audit display.

# properties.retrievedChunks.items.properties.rank.description
0-indexed rank in the retrieval result list (0 = top hit). The LLM may consume only top-K; the rank lets downstream analysis identify whether a hallucinated answer correlates with low-ranked chunks (rank > 5 often means the chunk was retrieved but not consumed).

# properties.retrievedChunks.items.properties.relevanceScore.description
Retrieval-system-emitted relevance score. Format is system-conventional: cosine similarity (0..1) for dense retrieval, BM25 score (unbounded positive) for lexical, fused score for hybrid. Used by downstream analysis to identify low-confidence citations (e.g., relevanceScore < 0.5 with a confident-sounding generation = hallucination risk).

# properties.retrievedChunks.items.properties.reranker.properties.rerankerScore.description
Reranker-emitted score after the initial retrieval. Hybrid pipelines first retrieve k=50 with dense+lexical, then rerank with a cross-encoder to top-k=10.

# properties.retrievedChunks.items.properties.reranker.description
Optional reranker pass details. Pins WHICH CROSS-ENCODER reranked the chunks (Cohere Rerank v3, Jina Reranker, BGE Reranker).

# properties.retrievedChunks.description
Ordered array of chunks the retrieval system returned. Empty array is VALID: 'retrieval returned no hits' is itself an attestable fact — proves the generation that followed had NO grounding (hallucination evidence).

# properties.topK.description
Optional: the K requested from the retrieval system (retrievedChunks.length MAY be less than K when fewer matches exist). Pins the request shape.

# properties.retrievalStrategy.description
Retrieval strategy. 'dense' = cosine-similarity over embeddings. 'lexical' = BM25 / TF-IDF / Elasticsearch. 'hybrid-rrf' = Reciprocal Rank Fusion. 'hybrid-linear' = weighted sum. 'multi-vector-colbert' = late-interaction (ColBERTv2). 'graph-rag' = knowledge-graph-augmented (Microsoft GraphRAG, Neo4j). 'agentic-rag' = LLM-orchestrated multi-step retrieval (LangGraph). 'hyde' = Hypothetical Document Embeddings. 'self-query' = LLM rewrites query before retrieval. Different strategies have different determinism + reproducibility properties.

# properties.generationModelId.description
Optional: identifier of the generation model that consumed the retrieved chunks. When present, this attestation chains the retrieval to a specific generation — letting downstream verifiers walk: review.citedEvidenceShaArray → retrieval.citation → generationModelId + generationSha256 → exact answer that was emitted.

# properties.generationSha256.description
Optional sha256 of the canonical bytes of the generated text that consumed the retrieved chunks. Pins WHICH ANSWER was produced from these specific retrievals. Required if a downstream review/support/legal attestation needs to prove the answer was actually grounded in the cited chunks.

# properties.retrievedAt.description
Epoch milliseconds when the retrieval was performed. Required because source documents are mutable — a citation against today's source content may not validate against last-week's. sourceContentSha256 anchors the bytes; retrievedAt anchors the time.

# properties.runnerDid.description
DID of the entity that performed the retrieval. did:web for institutional retrieval-system operators (did:web:pinecone.io, did:web:weaviate.io); did:key for self-hosted / individual deployments. The DID resolves to a JWKS that verifies the signature.

# properties.anonymizedQueryEmbedding.description
Optional: the query embedding vector itself (for analytics surfaces that need to compute drift / clustering across attestations without storing the original query). Privacy-conscious deployments MUST NOT include this for queries containing PII — the vector is approximately invertible against a known embedding model.

# properties.ipAttestationEventId.description
Optional: UUID of the ip.attestation.event.v1 wire row that envelopes this attestation for transport.

# properties.signature.description
Ed25519 signature (128 hex = 64 raw bytes) over the canonical-JSON bytes of (citationId ⊕ queryHashSha256 ⊕ embeddingModelId ⊕ embeddingModelVersionSha256? ⊕ retrievalSystemId? ⊕ retrievalSystemVersionSha256? ⊕ retrievedChunks ⊕ topK? ⊕ retrievalStrategy? ⊕ generationModelId? ⊕ generationSha256? ⊕ retrievedAt ⊕ runnerDid ⊕ ipAttestationEventId?). Verify against the publicKey published in the runnerDid's JWKS. RFC 8037 + JCS (RFC 8785). ⚠ INTEGRITY GAP (aa-824-P0-1, cycle 848 honest disclosure): `chain.*` and `extra` are presently OUTSIDE this signature pre-image. A wire-path or post-hoc rewrite of `chain.parentSha256` / `chain.parentKind` / `chain.parentAttestationId` does NOT invalidate this `signature`. Offline verifiers using `signature`+`publicKey` (the documented embedded-verification path) MUST treat `chain.*` as ADVISORY pointer-shape, NOT cryptographically bound to this attestation. For full-integrity verification including chain pointers, fetch the W3C DataIntegrityProof envelope from /credentials/<sha>/v1 and verify against the proof.cryptosuite=eddsa-jcs-2022 signature (which signs the FULL canonical-JSON body including chain). Cycle-484 added the chain block; cycles 778-784 swept it; the signature pre-image was not re-derived. Migration target: v2 schema slug ($id pin per cycle-462). See cycles 825-829 (5/5 wedge-tier ring) + cycle 830 (verification-recipe.md dual-path teaching) for the canonical write-up; cycle 848 fans this disclosure to the 12 remaining chain-bearing leaf schemas in one batch.

# properties.publicKey.description
Ed25519 public key (32 raw bytes = 64 hex) corresponding to the signature.

# properties.chain.properties.parentSha256.description
Sha256 of the canonical-JSON bytes of the parent attestation. For a retrieval.citation.attestation the typical parent is the ip.pipeline.facet.v1 attesting WHERE the corpus came from (dominant path — anti-hallucination requires anchoring the retrieved chunks to a signed dataset provenance), OR the ip.experiment.receipt.v1 that minted the embedding-model checkpoint when verification targets the embedding model rather than the corpus. ⚠ Cycle 493 honest-disclosure (adversarial-auditor AA-485-P2-4): the platform does NOT verify parent-existence at issuance. Consumers walking the chain MUST resolve parentSha256 against their own attestation registry; see /docs/verification-recipe.md for the canonical walk. A dangling parentSha256 is an UNVERIFIED chain link, NOT a verified absence.

# properties.chain.properties.parentKind.description
Kind identifier of the parent attestation. retrieval.citation has THREE legitimate upstream paths: (a) pipeline-facet — the upstream dataset / corpus provenance (the dominant path for ip-verified-retrieval; the cycle-769 killer-feature contract is 'bind retrieval to source provenance'); (b) experiment-receipt — the embedding-model checkpoint when verification scope is the model rather than the data; (c) embedding.response — when the retrieval is part of a larger multi-step pipeline and the same retrieval cites an embedding.response that itself chains to its training-run experiment-receipt (cycle 814 sp-813-P1-1 close: pre-cycle-814 the enum only carried (a) + (b), but the embedding.response.v1 schema's own chain description prose at parentSha256.description claims 'a retrieval.citation parent-walk added parentKind=embedding.response.v1 there' — the prose was making a cross-reference the enum didn't honor). Adding embedding.response to the enum resolves the cross-schema parity drift.

# properties.chain.properties.parentAttestationId.description
Optional UUID of the parent attestation — convenience for consumers that index by id rather than by sha256.

# properties.chain.description
Cycle 780 (sc-772-P0-1 partial close, third of N): uniform upward chain link per the cycle-484 substrate. Pre-cycle-780 ip.retrieval.citation.attestation.v1 had no chain.parentSha256, so a LangChain / LlamaIndex callback handler emitting this attestation per-retrieved-source could not bind the citation to upstream dataset provenance — the entire ip-verified-retrieval anti-hallucination claim (cycle 769 wedge.md L66) requires the verify-walk to terminate at a pipeline-facet attesting where the data came from. Block is OPTIONAL (not in required[]) so back-compat preserved.