Troubleshooting¶
Common issues and solutions when working with VectorPin.
Verification Errors¶
VECTOR_TAMPERED¶
Problem: The vector hash does not match vec_hash.
Cause: The stored vector differs from the one originally pinned. This is the steganography kill shot — it is what VectorPin exists to catch. Treat as a security incident until proven otherwise.
Triage steps:
- Confirm you're passing the stored vector (not a re-computed one) to
verify(). - Check for
f32vsf64dtype mismatch between pin time and verify time. The pin commits to a specificvec_dtype; converting before hashing breaks the hash. - Check for FTZ/DAZ floating-point modes. Disable them, or normalize subnormals to zero before hashing.
- If 1–3 are clean, treat as a real integrity failure. Pull backups, investigate write paths, and rotate any keys whose
valid_untilwindow covers the affected pins.
SOURCE_MISMATCH¶
Problem: The source-text hash does not match source_hash.
Common causes:
- Encoding drift — Source text was UTF-16 / Latin-1 at one site, UTF-8 at the other.
- Whitespace drift — Trailing newline added or stripped; line endings normalized.
- Unicode normalization — Text was not NFC-normalized before pinning. (VectorPin always NFC-normalizes; the failure means the input was different.)
- Wrong source column — On Symbiont,
contentholds the text the embedding was produced from;sourceholds a URL. Using the wrong column breaks the hash. See Adapters > Symbiont schema.
Solution: Match the exact bytes the embedding was produced from. The pin commits to NFC-normalized UTF-8 of the source string passed to signer.pin.
MODEL_MISMATCH¶
Problem: The pin's model field differs from the caller's expected model.
Cause: The pin was produced with a different embedding model than the verifier expects.
Solution: Either the ingestion pipeline used the wrong model, or the verifier was configured with the wrong expectation. Confirm which is correct, then either re-pin or update the verifier config.
This check only fires when the caller passes an expected_model argument. If you don't supply one, model mismatch is silently accepted (the source/vector hashes still have to match, so this is only a confidentiality leak about which model the producer used — not an integrity failure).
UNKNOWN_KEY¶
Problem: The pin's kid is not in the verifier's registry.
Common causes:
- Misconfiguration — Verifier wasn't given the public key for this
kid. - Cross-environment leak — A pin from staging reached production (or vice versa).
- Forged
kid— Less likely, sincekidis in the signed canonical bytes (§4.2). Ifkidwas tampered with, you'd seeSIGNATURE_INVALIDfirst.
Solution: Add the missing public key to the registry, after confirming the key fingerprint out of band:
verifier = Verifier({
"prod-2026-05": load_public_bytes("./keys/prod-2026-05.pub"),
# add the missing one:
"prod-2025-11": load_public_bytes("./keys/prod-2025-11.pub"),
})
Do not silently auto-register unknown keys (TOFU). That makes the system a checksum, not a signature.
KEY_EXPIRED¶
Problem: The pin's ts falls outside the registered key's (valid_from, valid_until) window.
Cause: Either the key was rotated (and the new key is what should have signed this pin) or the key was revoked due to compromise.
Solution:
- If the pin's
tsis recent and the producer should still be signing with this key — the registry'svalid_untilis wrong. Fix the window. - If
valid_untilis correct and the pin is supposed to be there — the pin is suspect. It was signed after the key was retired, which means either clock skew on the producer or a real compromise. Investigate. - If the key was deliberately revoked due to compromise, this is the system working as designed.
See Pin Protocol > Key Rotation and Revocation.
SIGNATURE_INVALID¶
Problem: The Ed25519 signature does not validate against the registered public key.
Triage steps:
- Confirm the public key bytes match the private key that produced the pin. (Public-key fingerprint mismatch = wrong key in the registry.)
- Confirm both implementations are on the same protocol version. A v1 pin presented to a strict v2 verifier produces
UNSUPPORTED_VERSION, notSIGNATURE_INVALID— but a v2 pin whose canonical bytes were reconstructed wrongly will fail signature. - Check for double-decoding of the pin JSON (e.g., the JSON was JSON-decoded twice and is now a string-of-a-string).
If 1–3 are clean and the failure is consistent, treat as a security incident — someone produced a pin with a key that isn't yours.
SHAPE_MISMATCH¶
Problem: The caller's vector has a different dimensionality than vec_dim.
Cause: Wrong embedding model on either side, or wrong vector field plucked from the DB record.
Solution: Confirm the vector being passed to verify() is the one from the same record as the pin, and that both sides agree on the embedding model.
UNSUPPORTED_VERSION¶
Problem: The pin's v is not in the verifier's supported set.
Cause: A v1 pin from before the May 2026 wire-format break. v1 is not backwards-compatible with v2.
Solution: Re-pin the affected records using a v2 signer. If you need to verify v1 pins for a migration, use LegacyV1Verifier (opt-in only). See Specification §12 for the v1 → v2 change list.
PARSE_ERROR¶
Problem: The pin JSON is malformed, oversized, or contains disallowed content.
FAIL [parse_error] unknown top-level field: "foo"
FAIL [parse_error] string field "model" is not NFC-normalized
FAIL [parse_error] pin exceeds 64 KiB size limit
Common causes:
- Unknown top-level fields — v2 verifiers MUST reject them. This is a downgrade-attack defense.
- Non-string
extravalues —extrais strictlymap<string, string>. - Non-NFC strings — every string field must be NFC-normalized.
- Control characters or bidi overrides in strings — always rejected.
- Oversized pin — exceeds the 64 KiB limit, or
extraexceeds 32 entries. - Malformed timestamp —
tsmust match exactly^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$. No fractional seconds, no timezone offsets, no lowercaset/z.
Solution: Inspect the failing pin. Most PARSE_ERROR cases are buggy producers — fix the producer, re-pin. Hostile pins go to your security log.
RECORD_MISMATCH / COLLECTION_MISMATCH / TENANT_MISMATCH¶
Problem: Replay-protection check failed.
Cause: A pin from record A was attached to record B, then audited with the expected record id for B. This is exactly what the reserved vectorpin.record_id (etc.) keys are designed to catch — see Security > Replay Protection.
Solution: Investigate as a possible pin-shuffle attack. If it's a legitimate copy / re-keying operation, re-pin under the new record id.
Pinning Errors¶
vector contains NaN / vector contains Inf¶
Problem: The vector contains NaN, +Inf, or -Inf.
Cause: Numerical issue upstream of pinning — e.g., a model that produced NaN for an empty or degenerate input.
Solution: Reject the embedding upstream, don't try to pin it. Pinning NaN/Inf is forbidden by the protocol because their canonical byte representation isn't unique enough across implementations.
string field is not NFC-normalized¶
Problem: A string field (source, model, kid, an extra key/value) is not NFC.
Solution: Normalize before passing to signer.pin. The signer also normalizes, but it rejects strings containing control characters or bidi overrides outright.
Vector dtype confusion¶
If you sign with f32 but verify with the same array cast to f64, the hashes differ. The pin's vec_dtype is authoritative — verifiers cast the supplied vector to that dtype before hashing. Make sure your storage and your signing path agree on the dtype.
Adapter Errors¶
LanceDB: column 'text' not found¶
Problem: --source-column text (or the programmatic equivalent) names a column that doesn't exist on the table.
Solution: Check the table schema. On Symbiont, the source text lives in content, not text. Pass --source-column content.
Chroma: audit-chroma requires either --path or --host¶
Solution: Pick one. --path /path/to/db for PersistentClient, --host chroma.host --port 8000 for HTTP.
Qdrant: pin not visible in payload¶
Problem: Pins were attached, but audit-qdrant reports them as unpinned.
Cause: The pin is stored under payload["vectorpin"]. If your write path uses set_payload with a partial dict, ensure the dict actually includes that key.
See Also¶
- Pin Protocol > Failure-Mode Reference — One-line summary of every error
- Security — When to escalate, when to fix config
- Getting Started — Sanity-check end-to-end walkthrough