feat(search): add shared PUBLIC_FIELDS table

Create the shared field-definition table consumed by the schema builder
(_schema.py) and the whoosh-compat field registry (_registry.py). This
eliminates drift between what the index exposes and what queries can address.

- Create PublicField frozen dataclass with field metadata
- Define PUBLIC_FIELDS tuple with 16 searchable fields
- Add comprehensive test suite covering field properties

The whoosh-compat pyproject.toml dependency addition is added in a
follow-up commit, with the correct [tantivy] extra, source comment, and a
matching uv.lock update.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Trenton HolmesandClaude Sonnet 5 authored and stumpylog committed 2026-08-19 13:33:23 -07:00
1 parent a1705ff185
commit 38c82a674f
2 files changed
+94

No files matched your search

+49
View File
@@ -0,0 +1,49 @@
from whoosh_compat import FieldKind
from documents.search._fields import PUBLIC_FIELDS
class TestPublicFields:
def test_every_field_has_a_whoosh_compat_kind(self) -> None:
for field in PUBLIC_FIELDS:
assert isinstance(field.kind, FieldKind)
def test_names_are_unique(self) -> None:
names = [f.name for f in PUBLIC_FIELDS]
assert len(names) == len(set(names))
def test_json_fields_have_subpaths(self) -> None:
for field in PUBLIC_FIELDS:
if field.kind is FieldKind.JSON:
assert field.subpaths, f"{field.name} is JSON but has no subpaths"
def test_non_json_fields_have_no_subpaths(self) -> None:
for field in PUBLIC_FIELDS:
if field.kind is not FieldKind.JSON:
assert field.subpaths == ()
def test_document_type_alias_is_type(self) -> None:
field = next(f for f in PUBLIC_FIELDS if f.name == "document_type")
assert field.aliases == ("type",)
def test_storage_path_alias_is_path(self) -> None:
field = next(f for f in PUBLIC_FIELDS if f.name == "storage_path")
assert field.aliases == ("path",)
def test_tag_allows_comma_values(self) -> None:
field = next(f for f in PUBLIC_FIELDS if f.name == "tag")
assert field.comma_values is True
def test_notes_subpaths(self) -> None:
field = next(f for f in PUBLIC_FIELDS if f.name == "notes")
assert field.subpaths == ("user", "note")
def test_custom_fields_subpaths(self) -> None:
field = next(f for f in PUBLIC_FIELDS if f.name == "custom_fields")
assert field.subpaths == ("name", "value")
def test_no_internal_id_fields_present(self) -> None:
# tag_id/owner_id/viewer_id/etc. are permission-filter-only fields,
# never user-query-addressable (see design spec, "Field surface").
names = {f.name for f in PUBLIC_FIELDS}
assert not any(name.endswith("_id") for name in names)