Why:
- The failure modes worth testing are races, and they are cheap to force
against the fake and expensive to observe anywhere else.
Changes:
- Unit tests for both boundaries, the repeat delete, a missing neighbour, the
traversal property after several deletes, and — via a repository that bumps a
rival's version before each apply — both the partial-apply repair and the
unconvergent 409.
- One new shared contract scenario (a multi-point batch applies every patch,
including nulling a pointer) so it runs against the fake and real Qdrant.
- HTTP tests against real Postgres and Qdrant for relinking, soft-not-hard
delete, cross-tenant 404, and scope enforcement on both routes.
- create_source_file factory for tests addressing a file without uploading.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Why:
- Domain values are denormalized into every Qdrant point payload. Without
validation, an unregistered or typo'd domain (e.g. "fier" for "fire")
silently creates a new partition that retrieval never queries — the file
ends up invisible rather than rejected. Tenants also need independently
sized domain sets (one may run 14 insurance lines, another 6), which rules
out an enum.
Changes:
- tenant_domains table (migration 41335d162de8) + repository, unique on
(tenant_id, domain).
- src/application/domains/: ensure_domain_allowed() is the strict-allowlist
check now run inside upload_source_file()'s first transaction, before any
MinIO object, job row, or Qdrant point is written.
- /v1/domains (list/create/patch/disable/enable) gated on its own
domains:read/domains:write scopes, deliberately separate from files:write
so an upload key cannot create partitions. domain itself is immutable
(denormalized into every point payload); only display_name is editable.
Disable blocks new uploads without touching already-indexed points.
Impact:
- BREAKING: POST /v1/files now rejects any domain without an active
tenant_domains row (400, unknown_domain). A domain must be created via
POST /v1/domains before the first upload to it.
Why:
- Code under test (auth resolution, the two-phase upload) opens more than
one session per operation; the existing fixture only exposed one
rolled-back session.
Changes:
- Add a db_sessionmaker fixture sharing one outer transaction.
- Pin loop_scope="session" -- without it, a second async test against the
session-scoped Postgres container fails with "Event loop is closed."