Parent: #612
Depends on: #613, #614
背景
現行CCIPはLanceDBの media_ccip にmedia単位で768次元vectorを保存している。crop対応とmodel更新を見据えると、media 直カラムでは1 media対複数領域・複数embedding versionを表現できない。既存の ICcipVectorStore 境界を維持しつつ、PostgreSQL/pgvectorを正本にする。
設計方針
- 全体画像とcropを
media_regions で統一する
- 全体画像は
kind=full かつbboxなし、cropはbbox付きregionとする
ccip_embeddings はregion、model、embedding version、vector(768)、入力revision、抽出時刻を保持する
- 最初はexact cosine検索で現行意味論を維持し、既存Rust CCIP metricで再rankingする
- HNSW/IVFFlatは移行後のbenchmark・recall検証まで非スコープとする
スコープ
- domain/Zod schema、Drizzle schema、migration、明示的repository mapper
PostgresCcipVectorStore の実装とDI切替点
- get/upsert/delete/list/search、source filter、stale判定
- FK cascadeによるmedia/region削除時の整合性
- PostgreSQL 18とPGlite双方のCRUD・cosine検索テスト
非スコープ
- LanceDB既存データのbackfill
- crop保存API/UI
- ANN indexの本番tuning
受け入れ条件
主な参照
packages/application/src/ports/ccip-vector-store.ts:1
packages/application/src/services/ccip-vector-service.ts:168
apps/server/src/infrastructure/ai/lancedb-ccip-vector-store.ts:11
apps/server/src/infrastructure/ai/lancedb-ccip-vector-store.ts:266
packages/db/src/schema.ts:1
評価反映(2026-07-18)
以下を追加の必須条件とする。既存記述と競合する場合は本節を優先する。
DB制約
media_regions / ccip_embeddings の不変条件はapplication validationだけでなくDB制約でも保証する。
kind=full はmediaごとに1件だけとし、UNIQUE (media_id) WHERE kind = 'full' 相当のpartial unique indexを設ける
kind=full はbboxをすべてNULLにする
kind=person / manual 等のcrop regionはbboxをすべて必須とし、次をCHECKする
x >= 0、y >= 0
width > 0、height > 0
x + width <= 1、y + height <= 1
- 元画像のwidth/heightは正数とする
- detector scoreはNULLまたは定義済み範囲内とする
- embeddingは
vector(768)に加え、NaN、Infinity、zero normをruntime境界で拒否する
(region_id, model, embedding_version) の一意性をDBで保証する
Port・検索契約
現行のmedia ID中心のICcipVectorStoreでは、複数region・model・versionを安全に扱えないため、次を明示した契約へ更新する。
- get/upsert/deleteはregion ID、model、embedding versionを指定できる
- searchはregion kind、source、model、embedding versionを明示する
- 異なるmodel/versionのvectorを同じ検索空間へ混在させない
- source・region kind・model/versionのfilterは候補
LIMITより前に適用する
- 初期runtimeは
kind=fullだけを抽出・検索対象とする。person/manual regionのembeddingは#618まで作成しない
- DB行からdomain型への変換は明示的mapperへ集約する
Stale判定
fresh/staleの正本はextracted_atとmedia更新日時の大小比較ではなく、永続化したinput_revisionの一致判定とする。input_revisionには少なくとも元画像revision、model、embedding version、前処理条件を含め、service・batch・再処理判定で同じ生成規則を共有する。
Backend切替
本Issue完了時点ではLanceDBをdefault backendのまま維持する。Postgres adapterは設定flagまたはshadow verification経由で有効化し、read/writeの本切替は#616のbackfill・parity検証・rollback準備完了後に行う。
追加受け入れ条件
Parent: #612
Depends on: #613, #614
背景
現行CCIPはLanceDBの
media_ccipにmedia単位で768次元vectorを保存している。crop対応とmodel更新を見据えると、media直カラムでは1 media対複数領域・複数embedding versionを表現できない。既存のICcipVectorStore境界を維持しつつ、PostgreSQL/pgvectorを正本にする。設計方針
media_regionsで統一するkind=fullかつbboxなし、cropはbbox付きregionとするccip_embeddingsはregion、model、embedding version、vector(768)、入力revision、抽出時刻を保持するスコープ
PostgresCcipVectorStoreの実装とDI切替点非スコープ
受け入れ条件
主な参照
packages/application/src/ports/ccip-vector-store.ts:1packages/application/src/services/ccip-vector-service.ts:168apps/server/src/infrastructure/ai/lancedb-ccip-vector-store.ts:11apps/server/src/infrastructure/ai/lancedb-ccip-vector-store.ts:266packages/db/src/schema.ts:1評価反映(2026-07-18)
以下を追加の必須条件とする。既存記述と競合する場合は本節を優先する。
DB制約
media_regions/ccip_embeddingsの不変条件はapplication validationだけでなくDB制約でも保証する。kind=fullはmediaごとに1件だけとし、UNIQUE (media_id) WHERE kind = 'full'相当のpartial unique indexを設けるkind=fullはbboxをすべてNULLにするkind=person/manual等のcrop regionはbboxをすべて必須とし、次をCHECKするx >= 0、y >= 0width > 0、height > 0x + width <= 1、y + height <= 1vector(768)に加え、NaN、Infinity、zero normをruntime境界で拒否する(region_id, model, embedding_version)の一意性をDBで保証するPort・検索契約
現行のmedia ID中心の
ICcipVectorStoreでは、複数region・model・versionを安全に扱えないため、次を明示した契約へ更新する。LIMITより前に適用するkind=fullだけを抽出・検索対象とする。person/manual regionのembeddingは#618まで作成しないStale判定
fresh/staleの正本は
extracted_atとmedia更新日時の大小比較ではなく、永続化したinput_revisionの一致判定とする。input_revisionには少なくとも元画像revision、model、embedding version、前処理条件を含め、service・batch・再処理判定で同じ生成規則を共有する。Backend切替
本Issue完了時点ではLanceDBをdefault backendのまま維持する。Postgres adapterは設定flagまたはshadow verification経由で有効化し、read/writeの本切替は#616のbackfill・parity検証・rollback準備完了後に行う。
追加受け入れ条件
input_revision生成・stale判定を使用する