Files

3.6 KiB

Search and indexing

Goals

Search must find mixed Dutch, English and technical content quickly without leaking inaccessible titles, snippets, facets or counts.

The first release uses PostgreSQL full-text search and pg_trgm. Embeddings and external search services are out of scope.

Indexed sources

Page document

Weighted fields:

  • A: title
  • B: tags, document type, breadcrumb labels
  • C: stripped Markdown headings and body
  • D: selected metadata such as course/module and attachment descriptions

Project document

  • name and summary;
  • current state;
  • status and technologies;
  • project updates;
  • linked decision summaries;
  • linked document titles.

Attachment metadata

Only safe metadata is indexed:

  • original filename;
  • description;
  • caption;
  • detected media type.

Attachment body extraction and OCR are deferred.

Text normalization

  • Store original content unchanged.
  • Build a normalized search projection.
  • Use PostgreSQL simple configuration initially to avoid damaging code, commands, product names and bilingual terms through aggressive stemming.
  • Normalize Unicode and case for fuzzy title matching.
  • Strip Markdown syntax without concatenating unrelated tokens.
  • Keep code identifiers searchable.
  • Never index hidden secrets or operator-only source paths.

Query behavior

  1. Parse and bound the query.
  2. Apply workspace, visibility, trash and permission filters.
  3. Generate workspace- and permission-filtered full-text candidates.
  4. If exact candidates exist, rank those first and skip workspace-wide fuzzy work; otherwise use the indexed pg_trgm title predicate at the measured 0.2 threshold for typo-only searches.
  5. Rank with title/exact phrase/recency weights.
  6. Generate snippets only from permitted candidates.
  7. Return deterministic (rank, updatedAt, id) cursor ordering.

Exact title and exact quoted phrase matches outrank fuzzy matches.

This exact-first fallback is intentional: it keeps common-term latency bounded while preserving typo tolerance. The threshold is applied with PostgreSQL SET LOCAL in the request transaction; see ADR-020 and the 2026-08-01 production audit.

Filters

  • document type;
  • document status;
  • collection/root;
  • tag;
  • project;
  • author/editor;
  • changed date range;
  • content kind;
  • stale/review state.

Facets are calculated from the permitted candidate population.

Index consistency

Publishing a page updates its current search projection transactionally where practical. Background repair handles:

  • parser/version changes;
  • project context rollups;
  • bulk import;
  • failed projection updates;
  • full reindex.

Every projection stores a source version/hash. Administrators can see lag and repair it.

Permission safety

Search authorization is part of SQL selection. It is not acceptable to:

  1. retrieve the top global results;
  2. filter inaccessible rows in application memory;
  3. return fewer results or leaked facets.

Tests must include private pages whose titles and rare terms would reveal their existence.

Scale verification

Synthetic data must include:

  • 100,000 current pages;
  • mixed Dutch/English prose;
  • code and command blocks;
  • 1,000,000 revisions not all included in current-content search;
  • tags and project links;
  • deliberately inaccessible content;
  • typo and phrase queries.

Capture:

  • p50/p95/p99 latency;
  • query plans;
  • index sizes;
  • result correctness;
  • no-leak assertions.

Search API safeguards

  • query length bound;
  • result limit cap;
  • cursor expiry/versioning;
  • rate limit;
  • cancellation/timeout;
  • safe highlight tags generated by server or client—not arbitrary HTML;
  • generic not-found/forbidden behavior where existence is sensitive.