app.services.search

Read-only catalog search.

Takes a CatalogStore (already inside a unit of work opened by the caller) plus plain values, and returns plain dataclasses; nothing here imports FastAPI or a source adapter. Routers convert the results with SomeSchema.model_validate(value) (the schemas declare from_attributes=True).

Derived status on search results. Search list items do not carry the relationship-derived display status: computing it would require building the full relationship index (app.services.record_relationships) on every search request. It is only available on the detail endpoint. Documented as an open item for the frontend and for phase 09.

Record type. RecordTypeFilter selects decisions (the default), errata, or both; it is pushed into SQL like every other filter and combines with them (including needs_approval).

Sort order. resolve_sort() owns the default: relevance when a non-blank query is set, otherwise title; an explicit relevance without a query falls back to title (there is no rank to order by). recent orders by record.updated_at, which moves only when the content digest changes (last_seen_at is the poll heartbeat), newest first. Every order ends with title and id, so pages are stable.

Erratum target titles. An erratum hit carries the title of the decision it corrects (erratum_for_title). The targets of all erratum hits of the page are read with one query (list_published_by_uuid()) and resolved with the same rules as the detail page’s erratum_for (RelationshipIndex: published rows, same project first, ambiguous -> no title). The lookup is total: the corrects link is selected by type, an unresolved or self-targeting link simply yields no title. Targets are not restricted to decisions, so an erratum of an erratum shows the corrected erratum’s title, as its detail page does (where erratum_target_is_erratum flags it).

Public API: RecordTypeFilter, IssueCounts, CatalogSearchItem, CatalogSearchPage, resolve_sort(), search_records().

class app.services.search.CatalogSearchItem(*, id, title, source_id, project, path, ref, kind, record_kind, approval_state, approvable, issue_counts, erratum_for_title)

Bases: object

One search result row.

Parameters:
  • id (UUID) – Record id.

  • title (str) – Display title.

  • source_id (str) – Registration id.

  • project (str) – Project path.

  • path (str) – File path inside the project.

  • ref (str) – Branch/ref of the content.

  • kind (RecordState) – Published or candidate.

  • record_kind (RecordKind) – Decision or erratum.

  • approval_state (ApprovalStateCode) – Approval state of the current digest.

  • approvable (bool) – Whether the content can be decided at all (same rule as the record detail, app.services.approval_view.is_approvable()).

  • issue_counts (IssueCounts) – Stored validation issue counts.

  • erratum_for_title (str | None) – For an erratum, the title of the decision it corrects, when that resolves.

id: UUID
title: str
source_id: str
project: str
path: str
ref: str
kind: RecordState
record_kind: RecordKind
approval_state: ApprovalStateCode
approvable: bool
issue_counts: IssueCounts
erratum_for_title: str | None
class app.services.search.CatalogSearchPage(*, items, total, page, page_size)

Bases: object

One page of search results.

Parameters:
  • items (tuple[CatalogSearchItem, ...]) – Hits on this page.

  • total (int) – Number of hits over all pages.

  • page (int) – 1-based page number.

  • page_size (int) – Requested page size.

items: tuple[CatalogSearchItem, ...]
total: int
page: int
page_size: int
class app.services.search.IssueCounts(*, error, warning)

Bases: object

Stored-issue counts of one record, by severity.

Parameters:
  • error (int) – Number of error-severity issues.

  • warning (int) – Number of warning-severity issues.

error: int
warning: int
class app.services.search.RecordTypeFilter(*values)

Bases: StrEnum

Which record kinds a search returns.

DECISION = 'decision'
ERRATUM = 'erratum'
ALL = 'all'
app.services.search.resolve_sort(sort, query)

Apply the default sort and the no-query fallback.

Parameters:
  • sort (SearchSort | None) – Requested order, or None for the default.

  • query (str | None) – Free text, or None/blank.

Return type:

SearchSort

Returns:

relevance or title by default (with / without a non-blank query); title for relevance without a query; otherwise sort itself.

app.services.search.search_records(store, *, query, source_id, project, kind, page, page_size, policy, needs_approval=False, record_type=RecordTypeFilter.DECISION, sort=None)

Search the catalog with the Phase 1 filters.

Parameters:
  • store (CatalogStore) – Store of the caller’s unit of work.

  • query (str | None) – Free text, or None/blank for all records.

  • source_id (str | None) – Restrict to one registration, or None.

  • project (str | None) – Restrict to one project, or None.

  • kind (RecordState | None) – Restrict to published or candidate, or None.

  • page (int) – 1-based page number.

  • page_size (int) – Page size, 1 to MAX_PAGE_SIZE.

  • policy (Policy | None) – The configured policy, or None.

  • needs_approval (bool) – Only records whose current content does not satisfy the policy (pending, rejected, changed since acceptance, policy missing); evaluated in SQL by the store.

  • record_type (RecordTypeFilter) – Decisions (default), errata, or both.

  • sort (SearchSort | None) – Result order, or None for the default (see resolve_sort()).

Return type:

CatalogSearchPage

Returns:

The page.

Raises:

ValueError – If page or page_size is out of bounds.