Context
Parent: #14029 (atomic vector-write invariant). During PR #14077 a V-B-A of the actual Memory Core write paths surfaced that #14029 is two distinct mechanisms, not one:
- Explicit-embedding paths — the caller supplies the vector (
collection.add/upsert({embeddings: rows.map(r => r.embedding)}), e.g. DatabaseService non-reEmbed import). A caller-side validity gate fits: reject a row whose explicit embedding is missing/empty/wrong-dimension before persist.
- Auto-embed paths — the caller passes no embeddings and relies on Chroma's server-side embedding function (
DatabaseService reEmbed mode strips embeddings; SessionService artifact-upserts). ChromaManager sets an embeddingFunction, so these are not inherently vectorless; the invariant there is a different mechanism (verify the server-side embedding landed — read-back / fail-loud-on-embed-failure), coupled to the #14027 culprit forensics.
This sub-ticket is the first leaf: the pure, reusable gate-core (the validity logic + fail-loud partition) for the explicit-embedding paths. It is the substrate the explicit-path write-wiring (a separate sub) consumes, mirroring how #14056's producer-core shipped ahead of its #14061 consumer.
The Fix
A pure, I/O-free helper module ai/services/memory-core/helpers/vectorWriteInvariant.mjs that partitions write-path rows into persist-safe rows (valid same-dimension vector) and rejected rows (with a stable reason), so the caller persists only the valid set and routes the rest fail-loud — never half-writing a metadata-only row. Strictly additive safety: valid rows pass through unchanged.
Acceptance Criteria
Out of Scope
- The write-path wiring that consumes this gate-core (
DatabaseService explicit-import sites, conditional on !reEmbed) — a separate #14029 sub.
- The auto-embed verification mechanism (reEmbed + live
SessionService paths) — coupled to #14027.
Related
- #14029 (parent — the full invariant), #14027 (the auto-embed-failure forensics this defers to), #13999 (the incident shape), #14056 (the producer-core-before-consumer precedent).
Authored-by: @neo-opus-vega (Vega, Claude Opus 4.8)
Context
Parent: #14029 (atomic vector-write invariant). During PR #14077 a V-B-A of the actual Memory Core write paths surfaced that #14029 is two distinct mechanisms, not one:
collection.add/upsert({embeddings: rows.map(r => r.embedding)}), e.g.DatabaseServicenon-reEmbed import). A caller-side validity gate fits: reject a row whose explicit embedding is missing/empty/wrong-dimension before persist.DatabaseServicereEmbed mode strips embeddings;SessionServiceartifact-upserts).ChromaManagersets anembeddingFunction, so these are not inherently vectorless; the invariant there is a different mechanism (verify the server-side embedding landed — read-back / fail-loud-on-embed-failure), coupled to the #14027 culprit forensics.This sub-ticket is the first leaf: the pure, reusable gate-core (the validity logic + fail-loud partition) for the explicit-embedding paths. It is the substrate the explicit-path write-wiring (a separate sub) consumes, mirroring how #14056's producer-core shipped ahead of its #14061 consumer.
The Fix
A pure, I/O-free helper module
ai/services/memory-core/helpers/vectorWriteInvariant.mjsthat partitions write-path rows into persist-safe rows (valid same-dimension vector) and rejected rows (with a stable reason), so the caller persists only the valid set and routes the rest fail-loud — never half-writing a metadata-only row. Strictly additive safety: valid rows pass through unchanged.Acceptance Criteria
partitionRowsByVectorValidity({rows, expectedDimension})→{valid, rejected}rejects a row whose explicit embedding is missing / empty / wrong-dimension / non-finite (input order preserved forvalid).classifyRowVector(row, expectedDimension)returns the first/most-fundamental failure reason ornull.summarizeVectorRejections(rejected)→{count, byReason}for fail-loud logging.VECTOR_REJECTION_REASONSfrozen enum (missing-embedding | empty-embedding | wrong-dimension | non-finite-values).TypeError.Out of Scope
DatabaseServiceexplicit-import sites, conditional on!reEmbed) — a separate #14029 sub.SessionServicepaths) — coupled to #14027.Related
Authored-by: @neo-opus-vega (Vega, Claude Opus 4.8)