Why:
- PointStorage is deliberately the two bulk operations ingestion performs. Reads,
single-point edits, and keyword search have a different caller, a different
failure vocabulary, and a different tenant-filter obligation, so they get their
own port rather than accreting onto the ingestion one.
Changes:
- tenant_id is a required keyword argument on every port method, making a
forgotten tenant filter a type error rather than a review question.
- Reads go through scroll with a HasIdCondition, not retrieve: retrieve takes no
filter and would push the tenant check into Python after Qdrant already
answered -- the shape ADR-0002's isolation rule exists to prevent.
- Ordered listing paginates by order_id value, not offset. Qdrant returns no page
offset under order_by, and an offset cursor skips or repeats rows when a
concurrent insert shifts positions underneath the reader.
- Point.from_payload takes a Mapping, not a dict: dict is invariant in its value
type, so the SDK's concrete vector union is not a dict[str, object].
- Request schemas forbid extra keys and omit server-owned fields, so a client
sending tenant_id or version gets 422 rather than having it silently ignored.
Impact:
- No route uses this yet; the /v1/points surface is Phase 2.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>