Two follow-up fixes on the same branch:
1. **Purge sqlite-vec embeddings during project delete** (Codex P2)
sqlite-vec stores vectors in a vec0 virtual table keyed by chunk rowid
with no cascade. The previous purge removed search_vector_chunks but
left the embeddings behind; `_run_vector_query` then keeps returning
stale vectors that crowd live results.
ProjectRepository.delete now deletes embeddings first (using the same
rowid-IN-chunks pattern as SQLiteSearchRepository.delete_project_vector_rows),
then the chunk rows. Both deletes are skipped if the underlying table
is absent on a given install. New test test_remove_project_purges_vector_embeddings
covers the happy path and skips cleanly when the embeddings table
isn't initialized.
2. **Fix search_all_projects=True on local installs**
`_search_all_projects` recurses into search_notes with both project=
and project_id= set. project_id (external UUID) routes through the
cloud v2 API path, which 401s on local installs because there's no
JWT to present — so the inner calls silently failed and the merged
result list stayed empty.
The fan-out now mirrors get_project_client's cloud_available composite
(factory mode OR explicit --cloud OR has_cloud_credentials). When that
composite is false we forward project= only and take the name-routed
local-ASGI path. Cloud disambiguation still works because the project
name in project_ref is already the workspace/project qualified_name.
The existing cloud-style fan-out tests now go through a cloud_routing
fixture that pins the three signals; a new local_routing test confirms
project_id is dropped when no cloud route is available.
Signed-off-by: phernandez <paul@basicmachines.co>
search_index is created lazily by SearchRepository.init_search_index and
search_vector_chunks only materializes once semantic search initializes,
so either table may be absent on minimal test DBs. The previous version
of ProjectRepository.delete unconditionally issued DELETEs against both
and crashed CLI integration tests on Postgres and SQLite where
search_vector_chunks hadn't been created yet:
relation "search_vector_chunks" does not exist
no such table: search_vector_chunks
Inspect the connection's tables once per call and only delete from
whichever derived tables are present. Idempotent on every backend.
Signed-off-by: phernandez <paul@basicmachines.co>
SQLite stores search_index as an FTS5 virtual table, which can't carry a
foreign key, so the ON DELETE CASCADE from search_index.project_id to
project.id only applies on Postgres. On SQLite, deleting a project left
its FTS rows behind — and when auto-increment handed the same id to a
new project, the leftover rows masqueraded as the new tenant's data and
leaked into searches scoped to that project.
- ProjectRepository.delete now explicitly purges search_index and
search_vector_chunks for the project id in the same session before
the ORM delete. Idempotent on Postgres (the cascade FK still runs).
- One-time cleanup migration sweeps two leftover shapes: rows whose
project_id is gone, and rows whose entity_id is gone (the larger
class from id reuse). Guarded by table-existence checks so fresh
SQLite installs — where search_index is created at runtime by
init_search_index, not by Alembic — don't fail the upgrade.
- Regression test seeds both derived tables, calls remove_project,
and asserts both come out clean. Verified red on pre-fix code.
Signed-off-by: phernandez <paul@basicmachines.co>