Skip to content

fix(catalog): index physicalDescription, personality, role, motivations, significance in search_tsv#546

Merged
atomantic merged 2 commits into
mainfrom
fix/catalog-fts-character-fields
May 30, 2026
Merged

fix(catalog): index physicalDescription, personality, role, motivations, significance in search_tsv#546
atomantic merged 2 commits into
mainfrom
fix/catalog-fts-character-fields

Conversation

@atomantic

Copy link
Copy Markdown
Owner

Summary

Catalog full-text search (catalog_ingredients.search_tsv) currently misses bible-promoted characters' main narrative text. The STORED generated tsvector only indexes name (weight A) and payload->>description|notes|background|summary (weight B), so a search for "tall" or "introverted" against a character whose data lives in payload.physicalDescription and payload.personality returns no hits.

This PR expands the indexed expression to cover the character canon fields plus the type-specific role/motivations/significance fields used by place/object/concept payloads:

  • payload->>'description'
  • payload->>'physicalDescription' (new)
  • payload->>'personality' (new)
  • payload->>'background'
  • payload->>'summary'
  • payload->>'notes'
  • payload->>'role' (new)
  • payload->>'motivations' (new)
  • payload->>'significance' (new)

Schema migration

Postgres can't ALTER the expression of a STORED generated column, so the column is DROPped and re-ADDed. Done inside the existing catalogDDL block in server/lib/db.js, which already runs on every boot (PR #542 made it unconditional). The whole block is idempotent: a fresh install's DROP IF EXISTS is a no-op (column didn't exist) and ADD IF NOT EXISTS creates it; an existing v1 install's DROP IF EXISTS removes the narrow expression and ADD IF NOT EXISTS installs the expanded one. The expanded idx_catalog_ing_fts GIN index rebuilds automatically against the re-added column.

Mirrored in server/scripts/init-db.sql so fresh-install schemas land in the same end state as ensureSchema would produce (matches the [catalog-ddl-drift-test] PLAN invariant).

Wire-protocol version bump

PORTOS_SCHEMA_VERSIONS.catalog bumped from 12. The existing per-category gate in server/services/catalogSync.js applyRemoteChanges enforces the contract:

  • A v1 peer pushing into a v2 receiver is sender-behind on catalog (not ahead) → accepted. The v2 receiver re-derives search_tsv locally via the STORED expression.
  • A v2 peer pushing into a v1 receiver is sender-ahead → 412 CATALOG_SCHEMA_VERSION_AHEAD, preventing pre-expansion-shape rows from mismatching the indexed expression on the older install.

PLAN entry: [catalog-fts-character-fields].

Test plan

  • cd server && npm test — 8204 pass / 7 skip / 0 fail
  • No test pinned PORTOS_SCHEMA_VERSIONS.catalog === 1 — the live registry isn't asserted at a specific catalog value, so no test update needed
  • server/lib/db.js catalogDDL stays idempotent (DROP IF EXISTS + ADD IF NOT EXISTS)
  • server/scripts/init-db.sql mirrors ensureSchema (no drift)

atomantic added 2 commits May 29, 2026 21:35
…ns, significance in search_tsv

Catalog full-text search currently misses bible-promoted characters' main
narrative text — search_tsv only included description/notes/background/summary
and bypassed the character canon fields physicalDescription + personality
(and the type-specific role/motivations/significance fields).

Postgres can't ALTER the expression of a STORED generated column, so the
fix is DROP + re-ADD inside the idempotent ensureSchema DDL block. The block
already runs on every boot (PR #542 made it unconditional), so the ALTERs
apply to existing installs automatically and are no-ops on fresh installs
because the new CREATE TABLE no longer declares search_tsv inline.

Mirrored in server/scripts/init-db.sql so fresh-install schemas match what
ensureSchema produces. PORTOS_SCHEMA_VERSIONS.catalog bumped to 2 — older
peers still on 1 get a 412 from /api/catalog/sync/apply against newer peers,
preventing them from pushing pre-expansion-shape rows that would mismatch
the indexed expression.
…sion detected

Previously ensureSchema() unconditionally DROP+ADDed catalog_ingredients.search_tsv
on every boot to swap the generation expression. Postgres treats that as a
table rewrite under an AccessExclusive lock plus a GIN index rebuild — fine
the first time but pure waste on every subsequent boot of an already-v2
install (and ensureSchema fires once from memoryBackend init and once from
the catalog backfill chain).

Gate the DROP on a pg_attrdef inspection: only drop when the existing
generation expression is missing 'physicalDescription' (a v2-only token).
Fresh installs skip the drop because the column doesn't exist yet; already-v2
installs skip it because the expression already matches; only an upgrading
v1 install pays the rewrite. ADD COLUMN IF NOT EXISTS still does the right
thing in all three cases. Mirrored byte-for-byte into init-db.sql.
@atomantic atomantic merged commit 2c11536 into main May 30, 2026
2 checks passed
@atomantic atomantic deleted the fix/catalog-fts-character-fields branch May 30, 2026 04:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant