Skip to main content

Catalog module

AI-powered product information management, semantic search, configurable taxonomy, supplier shortlisting. Absorbs the Stella Catalog feature set — taxonomy is configurable per tenant (UNSPSC, ETIM, NATO FSC, CPV, or custom).

  • Source: apps/catalog/
  • Schema: catalog
  • Project Tracker prefix: CAT-*
  • Hosting: sub-zone behind Directory; production basePath is /catalog/* (rewritten from the root zone).

Current architecture

The Catalog that ships today is a single Next.js App Router application at apps/catalog/, following the same three-layer split as every other Constellation module:

API Route (src/app/api/) → Tool (src/server/tools/) → Service (src/server/services/) → Repository (src/server/repositories/)

Routes are thin adapters that call tools; tools express business capabilities and call services and policies; services hold the domain logic (including the AI orchestration for description generation) and call repositories; repositories are persistence-only over the catalog Postgres schema (Prisma, plus raw SQL for ltree / pgvector operations). Automation — event handlers and the lazy embedding-fill workflow — goes through tools or services, never repositories directly. The full picture is in §2 Component diagram and §7 Layers + call direction.

This is not the standalone NestJS, tri-layer service described in the Catalog architecture (v3) design page — that was a greenfield design for Stella Catalog that was never built. Whenever the two disagree, this page (the shipped Next.js app) is authoritative.

1. Purpose

Catalog is where suppliers describe what they offer and buyers find it. Suppliers publish products, services, and capabilities; buyers search by meaning rather than exact keywords, browse a familiar industry classification, and turn results into a curated shortlist that feeds the delivery work in Project Tracker.

Key capabilities

  • Semantic search — queries match by meaning (vector embeddings), not just keywords
  • Configurable industry taxonomy per tenant: UNSPSC, ETIM, NATO FSC, CPV, or custom
  • Shortlists: curated, lifecycle-managed selections of catalogue entries
  • AI-generated entry descriptions

Who uses it

  • Suppliers publishing and maintaining their catalogue entries
  • Agency buyers and procurement officers searching and shortlisting
  • The planned Procurement module, which will build RFQ flows on top of shortlists

For product context, see What is Constellation?. The rest of this page is the technical reference.

Catalog is the platform's product knowledge layer. It owns the catalogue entries (products, services, capabilities), the configurable industry taxonomy that classifies them, and the shortlisting / supplier-matching primitives that downstream procurement modules rely on. Two technical pillars set Catalog apart: pgvector for semantic-search embeddings and ltree for hierarchical taxonomy paths. AI-generated descriptions (via @constellation-platform/ai-core) are an explicit feature of the module rather than a layer concern.

2. Component diagram (C4 L3)

Call direction is strict: API Route → Tool → Service → Repository. Routes never call services or repositories directly; workflows and event handlers go through tools or services. See route-wrapping.

3. Schema

catalog — owned by Catalog. Postgres extensions required: pgvector (embeddings on catalog_entries), ltree (path-based taxonomy hierarchies), pg_trgm (fuzzy text matching). RLS is enforced on every tenant-scoped table via app.tenant_id. Cross-module reads of identity.* are read-only via raw SQL.

4. Entities

AggregatePurpose
catalog_entriesProducts / services / capabilities. Lifecycle: DRAFT → PUBLISHED → ARCHIVED. Carries pgvector embeddings and AI-generated descriptions.
catalog_taxonomyConfigurable taxonomy — LTREE path enables sub-tree queries. Per-tenant choice of UNSPSC / ETIM / NATO FSC / CPV / custom.
shortlistsCurated subsets of catalogue entries — feeds Procurement RFQ flows. Lifecycle: ACTIVE → ARCHIVED.
shortlist_entriesMembership join between a shortlist and its catalogue entries.

5. Domain events

Published from src/server/events/catalog.events.ts via the transactional outbox under the catalog.* namespace. Full payload schemas are in @constellation/contracts and indexed in the Domain events index.

catalog.entry.created, catalog.entry.updated, catalog.entry.deleted, catalog.entry.status_changed, catalog.taxonomy.created, catalog.taxonomy.updated, catalog.taxonomy.moved, catalog.taxonomy.deleted, catalog.shortlist.created, catalog.shortlist.updated, catalog.shortlist.deleted, catalog.shortlist.archived, catalog.shortlist.entry_added, catalog.shortlist.entry_removed.

6. Public API

The Catalog API reference is not yet wired into this site — adding Zod→OpenAPI to apps/catalog/ is tracked under INF-26. Illustrative routes:

  • POST /api/catalog-entries — create a draft catalogue entry; AI description and embedding are generated asynchronously.
  • POST /api/search — semantic search across published entries (uses pgvector ANN over the embeddings column).
  • POST /api/shortlists — create a shortlist from search results or a manual selection.

7. Layers + call direction

LayerPathMay import from
API Routessrc/app/api/Tools only
Toolssrc/server/tools/Services, Policies, Events
Servicessrc/server/services/Repositories, Policies, @constellation-platform/{db,ai-core,ai-embeddings}
Repositoriessrc/server/repositories/@constellation-platform/db (Prisma + raw SQL for ltree / vector ops)
Policiessrc/server/policies/@constellation-platform/auth-core
Workflowssrc/server/workflows/Tools or Services (never repositories)
Eventssrc/server/events/@constellation-platform/events publish() + outbox

Enforced at PR time by scripts/check-route-wrapping.ts — invoked via npm run check:routes, which scans apps/catalog/src/app/api and apps/directory/src/app/api. Every withAuth must be paired with withTenantAuth. Plus ESLint import boundaries.

8. Code entry points

9. Known exceptions / pitfalls

  • pgvector and ltree are required. Local dev brings them up via scripts/db-init.sql — verify with \dx in psql before running migrations.
  • AI calls always go through @constellation-platform/ai-core. Don't import provider SDKs (@anthropic-ai/sdk, openai) directly anywhere outside the platform packages — that's how the budget and audit interceptors stay in the request path. See provider-abstraction.
  • Embeddings are generated lazily. A new entry doesn't get an embedding inside the create transaction — there's a workflow that picks up entries with embedding IS NULL and fills them in. Search for an entry that's seconds-old returns no result; that's expected.
  • Optional clearance / classification gating. Tenants that need defence-style access control enable per-entry classification + per-user clearance. Off by default; switching it on changes the search filter shape — see the catalog spec.
  • Cross-module reads are read-only via raw SQL. Don't define Prisma cross-schema relations to identity.* — use @constellation-platform/db raw queries.

See also