-- Cortex Phase 4.C (foundation): encrypted-ready memory embeddings side table.
--
-- This migration adds the durable substrate for vector embeddings produced by
-- pluggable [`Embedder`] backends (see `cortex-retrieval::embedding`). It is a
-- pure additive side table: no existing column or trigger is touched, and the
-- default lexical + salience retrieval path (Phase 4.B baseline) keeps its
-- byte-for-byte invariants. A real backend implementation lands in a sibling
-- agent (D2-B); the composer integration lands in another (D2-C). This
-- migration is the storage half BOTH downstream pieces depend on.
--
-- Design notes:
--
--  - One row per `(memory_id, backend_id)` pair. A memory may carry
--    embeddings from multiple backends simultaneously (e.g. the deterministic
--    stub plus a real model during a migration window); the composite
--    primary key keeps each backend's vector independently addressable.
--  - `vector_blob` is the raw little-endian f32 byte encoding of the vector
--    produced by [`Embedder::embed`]. The encoding is deliberately self-
--    describing through `dim`: callers MUST verify that
--    `vector_blob.len() == dim * 4`. Picking a serializer-free encoding
--    avoids pulling bincode or serde_cbor into the workspace dependency set
--    for foundation slice D2-A; the encoding is stable for the
--    `(backend_id, dim)` contract a backend advertises.
--  - `encryption_kind` defaults to `'none'`. The column exists as a
--    forward-only hook for a future at-rest encryption slice: when an
--    encrypted backend lands, the reader inspects this column to dispatch
--    decryption, and rows written by the unencrypted foundation continue to
--    deserialize through the `'none'` branch. The `encryption_key_id`
--    column is nullable for the same reason.
--  - `dim INTEGER CHECK (dim > 0)` is a structural guard: a zero-dimensional
--    vector is a representation bug, not a legal embedding shape, and the
--    constraint fails closed on it.
--  - The foreign key onto `memories(id)` keeps the side table consistent
--    with the authoritative memory row. We deliberately do NOT cascade
--    deletes here: memory deletion is itself a policy-bearing surface
--    (ADR 0026), and the side-table cleanup is performed explicitly by the
--    [`EmbeddingRepo::delete`] caller alongside any other policy work.
--
-- Idempotency: `IF NOT EXISTS` makes this migration safe to re-apply, and
-- `_migrations` tracking in `apply_pending` is the hard guard against
-- duplicate application in production paths.

CREATE TABLE IF NOT EXISTS memory_embeddings (
    memory_id TEXT NOT NULL,
    backend_id TEXT NOT NULL,
    dim INTEGER NOT NULL CHECK (dim > 0),
    vector_blob BLOB NOT NULL,
    encryption_kind TEXT NOT NULL DEFAULT 'none',
    encryption_key_id TEXT,
    computed_at TEXT NOT NULL,
    PRIMARY KEY (memory_id, backend_id),
    FOREIGN KEY (memory_id) REFERENCES memories(id)
);

CREATE INDEX IF NOT EXISTS idx_memory_embeddings_backend
    ON memory_embeddings(backend_id);
