0097: Rerank document echo — object-shape contract (general §6 rule; Jina §8.2 realization)¶
- Status: Accepted
- Author: Chris Colinsky
- Created: 2026-07-11
- Accepted: 2026-07-11
- Targets: spec/retrieval-provider/spec.md §6 ScoredDocument — generalize the
document-echo contract to object-shaped echoes (echoed text ornull, verbatim object onraw), amending the "surface verbatim" MUST (§6documentrow) and the per-result null-dichotomy invariant (§6 "some results but not others"); and §8.2 Jina — realize that rule for Jina'sdocument: anyOf[string, TextDoc, ImageDoc, null], with a non-object value (number / array / boolean) mapped toprovider_invalid_response(§7). Conformance (at Accept): fixture 019 gains TextDoc, ImageDoc→null, and mixed-shape cases (+ arawassertion). - Related: 0078 (Jina wire mapping — introduced the §8.2
document → documentmapping this refines), 0060 (rerank protocol — §6ScoredDocument.documentcontract), 0096 (rawverbatim JSON — the echo object is preserved nested in the verbatim response there) - Supersedes:
Summary¶
ScoredDocument.document (§6) is the echoed document text (string | null). But some providers echo it
as an object, not a bare string. Jina's rerank result document is anyOf[string, TextDoc, ImageDoc,
null] — verified against the live Jina OpenAPI (https://api.jina.ai/openapi.json, 2026-07-11: TextDoc is
{"text": str}, ImageDoc is {"image": str}) — and the text reranker typically returns the TextDoc
object (a bare string is also legal). §6's "surface the provider's echo verbatim" MUST and §8.2's
document → document direct mapping only fit the bare-string case: on the real wire an implementation either
type-violates ScoredDocument.document with an object, or (the common impl) reads document only when it is
a string and silently drops the echo to null — which fixture 019 (mocking only a bare string) cannot
catch.
This generalizes the §6 echo contract to object shapes — an object echo surfaces its text content (a
string-valued text key) or null, with the verbatim object preserved on RerankResponse.raw — and pins
the §8.2 Jina realization (TextDoc → text, ImageDoc/text-less object → null, a non-object echo → provider_invalid_response).
Motivation¶
§6 types document as the echoed text (string | null) and requires implementations to "surface the
provider's echo verbatim when present." For a TextDoc {"text": "berlin"} the echoed text is "berlin", so
the intent is already text — but the current MUST wording ("verbatim") and §8.2's document → document
mapping assume the echo is a string, so neither says how to handle the object shape.
The defect is silent: a mapping that reads document only when it is a string passes fixture 019 (which
mocks a bare string) but returns null against the live Jina text reranker, which echoes a TextDoc. Because
conformance can't distinguish a correct mapping from a bare-string-only one, the gap is a fixture-fidelity +
contract-specification problem, not a §6 / §2 disagreement — surfaced by a Jina rerank implementation against
the live endpoint. The object-echo case is not Jina-specific: any rerank vendor may echo a richer shape, so
the contract belongs in §6, with §8.2 the vendor realization.
Proposed change¶
retrieval-provider §6 — generalize the document-echo contract¶
Amend the ScoredDocument.document row and the §6 per-result echo invariant. document remains the echoed
document text (string | null); the echo rule generalizes to object shapes:
- a string echo → surfaced verbatim. An empty string
""is present and MUST be surfaced as""(not folded tonull). - an object echo → its text content: an object with a string-valued
textkey → that string; any other object (no stringtext, or a non-text media shape such as an image wrapper) →null. - an absent /
nullecho →null. - the verbatim echo — whatever its shape — is preserved on
RerankResponse.raw(0096), nested atresults[].documentwithin the verbatim provider response, so no echo information is lost. A caller that needs a non-text echo (e.g. an image) reads it there.
This amends the "surface verbatim" MUST to the text-or-null rule above (extracting the text from an
object echo is how "surface the echo" applies to a non-string shape). It reconciles the per-result
invariant ("when the provider returns document for some results but not others"): document = null now
covers both an omitted echo and an echo whose shape carries no text (e.g. an image), per result,
while a string / text-bearing echo populates. The "MUST NOT fabricate the echo from the input documents
list" rule is unchanged.
retrieval-provider §8.2 — Jina realization¶
Jina's rerank result document is anyOf[string, TextDoc, ImageDoc, null] (Jina OpenAPI: TextDoc =
{"text": str}, ImageDoc = {"image": str}). Apply the §6 rule; this replaces §8.2's prior
document → document direct mapping:
- a
string→ itself; - a
TextDoc(object with a string-valuedtextkey) → itstext; - an
ImageDoc({"image": str}), or any object without a stringtext→null; - absent /
null→null.
A TextDoc / ImageDoc echo is a documented Jina shape, so the mapping MUST NOT treat it as malformed,
and an object echo that is neither (no string text) still surfaces null per §6 rather than raising (its
verbatim shape stays on raw). A document echo that is not a string, object, or null — a number,
array, boolean — is a malformed response and maps to provider_invalid_response (§7); the null fallback
covers text-less object echoes, not non-object wire corruption.
Conformance test impact¶
At Accept (this is a Draft — the spec edits + fixtures land with the accept PR, not here), fixture
019 (019-rerank-jina-return-documents) gains three cases alongside the existing bare-string case B,
plus a raw assertion (019 asserts none today; the harness supports asserting raw per 0096's
retrieval-raw fixtures):
- TextDoc echo —
return_documents: true; mocked response echoesdocument: {"text": "doc about berlin"}per result. AssertScoredDocument.documentis the extracted string ("doc about berlin", not the object), andRerankResponse.rawcarries the verbatim{"text": …}object nested inresults[]. - ImageDoc echo →
null— mocked response echoesdocument: {"image": "https://…"}. AssertScoredDocument.documentisnull(no text), with the verbatim{"image": …}object onraw(proving a non-text echo maps tonullrather than raising, and stays recoverable onraw). - Mixed-shape (per-result variance) — one response echoing a string, a
TextDoc, anImageDoc, and an absentdocumentacross four results →["…", "…", null, null]per result, exercising the §6 per-result invariant so a "read the first result's shape, apply to all" implementation fails.
A non-object document (e.g. a number) → provider_invalid_response rides the existing §7 error-mapping
coverage; a dedicated malformed-echo case MAY be added at Accept. Numbers / case-names assigned at Accept.
Versioning¶
MINOR bump (pre-1.0). A behavioral change to the §6 document-echo contract (generalized to object
shapes) and the §8.2 Jina mapping. §6's string | null type is unchanged and the bare-string case is
unchanged; an implementation that only handled bare-string echoes becomes non-conforming for object echoes
(it was already silently dropping the echo on the live Jina wire). Depends on 0096 for the "verbatim echo on
raw" clause (RerankResponse.raw carries the verbatim response). Ships as spec v0.92.0.
Alternatives considered¶
- Reject non-string echoes as
provider_invalid_response. Reject — aTextDoc/ImageDocis a documented, expected Jina response shape (per the OpenAPIanyOf), not malformed; §6 typesdocumentas text with anullfallback, so a non-text echo maps cleanly tonull(object onraw), not an error. (A non-object value — number, array — DOES raise; that is the documented-shape-vs-corruption line.) - Widen
ScoredDocument.documentto carry the echo object. Reject — §6 deliberately types the field as the echoed text (string | null) for cross-vendor uniformity; the verbatim object already has a home onRerankResponse.raw(0096). Widening the field would fork its type per vendor and duplicateraw. - Keep the rule Jina-specific in §8.2 (no §6 change). Reject — the object-echo case recurs for any
rerank vendor with a rich
documentfield; §6 is the reusable home (amend once), §8.2 the vendor realization. A §8.2-only special case would leave the §6 MUST contradicting the mapping and force every future vendor to re-derive it. - Leave §8.2 as
document → document. Reject — it under-models the real Jina wire; a conforming impl either type-violatesScoredDocument.documentwith an object or silently drops the echo, and conformance can't catch either. This is the exact defect the proposal fixes.
Out of scope¶
- Image documents as rerank input — this proposal covers
ImageDoconly as an echo shape (→nulldocument); reranking image documents is a separate future concern. - Other vendors' current mappings — Cohere
/v2/rerankhas noreturn_documents(§8.4, silent no-op) and TEI usestext(§8.1); their mappings are unaffected, though the generalized §6 rule now governs them if they ever echo an object shape.