# description
Per-embedding-response attestation envelope binding {input-bytes, embedding-model, embedding-model-version, output-vector-shape, normalization} to a cryptographic chain. Closes SYNTHESIS-acquisition Round 19 P1 [[ai-embedding-model-providers]] E1 ship-first action. FIFTEENTH JSON Schema in IntelligencePro's catalog. The per-call substrate that cycle-321 ip.retrieval.citation.attestation.v1 already references via embeddingModelId + embeddingModelVersionSha256 — this commit ships the per-response attestation primitive that retrievals can chain to. Anchors the silent-embedding-rotation class: OpenAI silently rotated text-embedding-ada-002 weights at least once in 2023, invalidating prior similarity scores. With per-response attestation, downstream consumers can recompute the embedding on the canonical inputBytesSha256 + embeddingModelVersionSha256 and prove the vector wasn't tampered with between emit and consumption. Designed for: OpenAI text-embedding-3-large + 3-small + ada-002 (legacy) + Cohere Embed v4 (first multimodal Mar-2025) + Cohere Embed v3 multilingual + Voyage AI voyage-3-large + voyage-3-lite (MongoDB-acquired Feb-2025 at $220M) + Gemini Embedding gemini-embedding-001 (leads English MTEB v2) + NVIDIA Llama-Embed-Nemotron-8B (leads multilingual MMTEB Apr-2026) + Mistral mistral-embed + BGE BAAI/bge-large-en-v1.5 + bge-multilingual-gemma2 + Jina AI jina-embeddings-v3 + jina-embeddings-v3-multilingual + Together AI embedding endpoints + Fireworks AI + Anyscale + Modal + Replicate embedding endpoints + on-prem sentence-transformers + Snowflake snowflake-arctic-embed-l-v2.0. Composes UPSTREAM as a primitive for retrieval-citation chains. Composes DOWNSTREAM with ip.retrieval.citation.attestation.v1 (cycle 321) via embeddingModelVersionSha256 + ip.eval.run.attestation.v1 (cycle 308) — embedding-eval benchmarks (MTEB v2, MMTEB, BEIR, MIRACL) consume these attestations to prove leaderboard submissions actually used the claimed model version.

# $comment
Discovery: https://ip.tekton.cc/.well-known/ip-knowledge.json (full descriptor) | Badge: https://ip.tekton.cc/api/badge/v1/attestation/embedding.response.v1/<subjectSha256> (shields.io endpoint) | Free dry-run: POST https://ip.tekton.cc/api/credentials/dry-run with body {kind:"embedding.response.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/embedding-response/v2 — v1 stays immutable (see /docs/schema-versioning.md for the cycle-726 canonical bump-mechanics + soft-change-detection contract).

# properties.responseId.description
Unique RFC 4122 UUID v4 or v7 — this embedding-response attestation's canonical identifier.

# properties.inputBytesSha256.description
Sha256 of the canonical UTF-8 bytes of the input(s) that were embedded. For single-input calls: sha256 of the input text bytes. For batch calls: sha256 of the canonical-JCS encoding of the input array. THIS is the load-bearing anchor — a downstream verifier can recompute the embedding against this exact input + the pinned embeddingModelVersionSha256 and prove the vector emitted matches the model's actual deterministic output.

# properties.inputText.description
Optional plaintext input. When present, sha256(canonical-utf8(inputText)) MUST equal inputBytesSha256. 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 privacy-sensitive deployments (medical, legal, financial, PII) may hash and omit the plaintext.

# properties.embeddingModelVersionSha256.description
Optional sha256 of the canonical embedding-model artifact. For closed-API (OpenAI / Cohere / Gemini / Voyage): vendor-published version manifest sha. For OSS (BGE / Jina / Llama-Embed-Nemotron / Snowflake Arctic): sha256 of the safetensors. Pinning this defeats the silent-weight-rotation class (OpenAI ada-002 2023 silent rotation). Strongly recommended for any audit-relevant attestation.

# properties.outputDimension.description
Number of dimensions in the output vector. Standard examples: text-embedding-3-small=1536 (truncatable), text-embedding-3-large=3072 (truncatable to 256/1024), cohere-embed-v4=1024 or 256 or 512 or 1536 (Matryoshka), voyage-3-large=1024 (configurable to 256/512/2048), gemini-embedding-001=3072 (truncatable via Matryoshka to 768/1536), llama-embed-nemotron-8b=4096, bge-large-en-v1.5=1024, mistral-embed=1024, snowflake-arctic-embed-l-v2.0=1024. The Matryoshka truncation pattern is critical — pinning outputDimension defeats the 'we changed truncation' class of silent behavior changes.

# properties.outputVector.description
Optional: the embedding vector itself. When present, the vector's length MUST equal outputDimension and the L2 norm MUST match outputNormalization. ⚠ NOT dry-run-enforced (schema-prober cycle 1086): these are self-contained issuer invariants the validator does NOT check — a length-mismatched vector (e.g. length 2 with outputDimension:1536) still dry-run-validates valid:true. JSON Schema can't express array-length-equals-a-sibling-field without a custom keyword, so a strict consumer MUST re-check outputVector.length === outputDimension (and the L2 norm) itself. (Contrast eval-run's resultsHash, which IS recompute-enforced via a custom keyword.) Optional for two reasons: (1) vectors can be large (3072 floats × 8 bytes = 24KB per call); (2) the inputBytesSha256 + embeddingModelVersionSha256 anchors are sufficient to recompute the vector deterministically. Privacy warning: embedding vectors are approximately invertible against a known model — do NOT include for inputs containing PII.

# properties.outputVectorSha256.description
Optional sha256 of the canonical bytes of the output vector (float64 little-endian, JCS-encoded array). When the outputVector field is omitted (common for privacy), this sha pins the vector cryptographically — letting a verifier confirm the consumer received the SAME vector that was attested.

# properties.outputNormalization.description
Normalization applied to the output vector. 'l2-unit' = vector L2 norm = 1.0 ± floating-point epsilon (OpenAI default, Cohere default). 'none' = raw model output, no normalization. 'matryoshka-truncated-l2-unit' = vector was Matryoshka-truncated to outputDimension and then re-L2-normalized (Cohere Embed v4 / Voyage / Gemini Embedding pattern). Pinning normalization defeats the 'I divided by norm' / 'I forgot to divide by norm' class of subtle bugs.

# properties.inputType.description
Hint about how the embedding will be used. Cohere + Voyage + Gemini Embedding emit DIFFERENT embeddings for the same text depending on input_type ('search_document' vs 'search_query' have asymmetric optimal spaces). Pinning inputType defeats the 'we mixed query and document embeddings' class of asymmetric-retrieval bugs that quietly halve retrieval quality.

# properties.modality.description
Optional: the input modality. 'text+image' or 'multimodal' specifically routes to Cohere Embed v4 (first widely-deployed multimodal embedding, Mar-2025), Voyage multimodal, Gemini multimodal embedding. Pinning modality routes downstream verifiers to the right reference model.

# properties.batchIndex.description
Optional 0-indexed position in a batch request. When a single API call embeds N inputs, each gets its own ip.embedding.response.v1 with the shared batchId + ordered batchIndex. Lets a downstream verifier confirm batch-order preservation (some providers shuffle batches under load).

# properties.batchId.description
Optional UUID linking attestations within a single batch API call. Shared across all ip.embedding.response.v1 rows emitted from one POST.

# properties.tokenCount.description
Optional: token count consumed by the embedding call. Useful for cost attribution + rate-limit accounting. Different tokenizers count differently (OpenAI cl100k_base vs Cohere bytepair vs Voyage SentencePiece) — pinning tokenCount + embeddingModelId together is unambiguous.

# properties.respondedAt.description
Epoch milliseconds when the embedding model emitted the response.

# properties.runnerDid.description
DID of the entity that ran the embedding call. did:web for institutional embedding-API providers (did:web:openai.com, did:web:cohere.com, did:web:voyageai.com, did:web:ai.google.dev); did:key for self-hosted / individual deployments. The DID resolves to a JWKS that verifies the signature.

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

# properties.chain.properties.parentSha256.description
Sha256 of the canonical-JSON bytes of the parent attestation. For an embedding.response the parent is the ip.experiment.receipt.v1 of the embedding model's training run — the upstream receipt that minted the embedder's weights (applies to API-vendor, self-hosted OSS, and bespoke fine-tuned deployments alike). ⚠ Cycle 493 honest-disclosure: platform does NOT verify parent-existence at issuance; consumers walk the chain against their own registry per /docs/verification-recipe.md. Critical for ip-verified-retrieval: a retrieval.citation parent-walk (cycle 780 added parentKind=embedding.response.v1 there) bottoms out at THIS schema's experiment.receipt parent.

# properties.chain.properties.parentKind.description
Kind identifier of the parent attestation. embedding.response anchors to the experiment-receipt of the embedding model used.

# 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 783 (sc-772-P0-1 partial close, batch B/3): uniform upward chain link per the cycle-484 substrate. Pre-cycle-783 embedding.response had no chain.parentSha256 — breaking the ip-verified-retrieval traversal contract two hops up from a retrieval.citation. Cycle-780's retrieval.citation → embedding.response link only resolves to a leaf if embedding.response also chains up; this cycle closes the second link. The block is OPTIONAL (not in required[]) so back-compat is preserved.

# properties.signature.description
Ed25519 signature (128 hex = 64 raw bytes) over the canonical-JSON bytes of (responseId ⊕ inputBytesSha256 ⊕ embeddingModelId ⊕ embeddingModelVersionSha256? ⊕ outputDimension ⊕ outputVectorSha256? ⊕ outputNormalization? ⊕ inputType? ⊕ modality? ⊕ batchIndex? ⊕ batchId? ⊕ tokenCount? ⊕ respondedAt ⊕ runnerDid ⊕ ipAttestationEventId?). Verify against the publicKey published in the runnerDid's JWKS. RFC 8037 + JCS (RFC 8785).

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