java.lang.Object
com.darkcollective.relix.semantic.internal.CatalogBuilder

public final class CatalogBuilder extends Object
Builds the read-only system catalog relations — the relix.* namespace that lets a script query the engine's own knowledge about itself (the information_schema analogue; see ADR-0007).

Strategy (ADR-0007, Decision 3 — Strategy A)

Catalogs are registered as SystemRelationSymbol — a dedicated, first-class read-only symbol kind in the reserved relix namespace. This promotes the v1 Strategy-B approach (reusing InlineRelationSymbol) to the Strategy-A escape hatch described in the issue, giving callers a structural signal that these relations are engine-computed and not user-defined inline data. The IR now labels them SYS instead of INL. relix.dependencies is the exception: it is a QueryRelationSymbol derived from relix.plan, so it has no Java-computed extent at all — it is planned and executed lazily like any other view (see buildDependencies()).

Two-step registration (the ordering constraint)

relix.relations has a collection-time extent, so registerInto(com.darkcollective.relix.symbol.table.SymbolTable, java.util.Map<java.lang.String, com.darkcollective.relix.lang.ast.ConnectionDeclaration>, java.util.Map<java.lang.String, com.darkcollective.relix.symbol.RelationStatistics>, java.util.List<com.darkcollective.relix.events.QueryEvent>, com.darkcollective.relix.function.FunctionCatalog, com.darkcollective.relix.semantic.internal.ComponentInventory, com.darkcollective.relix.semantic.internal.RelationBoundedness) computes and registers it in full before schema inference. relix.columns and relix.plan are different: their rows need the inferred output schemas of views (which only resolve during inference), yet the symbol must already exist before inference for a query to reference it. Both are therefore handled with the same placeholder-then-fill pattern the engine already uses for views:
  1. registerInto(com.darkcollective.relix.symbol.table.SymbolTable, java.util.Map<java.lang.String, com.darkcollective.relix.lang.ast.ConnectionDeclaration>, java.util.Map<java.lang.String, com.darkcollective.relix.symbol.RelationStatistics>, java.util.List<com.darkcollective.relix.events.QueryEvent>, com.darkcollective.relix.function.FunctionCatalog, com.darkcollective.relix.semantic.internal.ComponentInventory, com.darkcollective.relix.semantic.internal.RelationBoundedness) registers them with their (fixed, known) schema but an empty extent, so references resolve and infer correctly.
  2. fillColumns / fillPlan re-register them after inference with the real extent, read off the now-resolved symbol schemas (columns) and per-node schema annotations (plan).

Reservation

Catalog symbols live in the relix namespace with Provenance.BUILTIN; a user := (always in a user namespace) can never name one. The single-registration catalogs use ShadowPolicy.FORBIDDEN; relix.columns uses ShadowPolicy.PERMITTED so its post-inference fill can overwrite the placeholder.

Self-exclusion (ADR-0007, Decision 4)

Every extent is computed from the relation symbols outside the relix namespace, so the catalog never describes itself or its siblings; the extents are stable and acyclic.

relix.catalog is the one deliberate exception, and it is opt-in by being a relation of its own: naming it is how you ask. It changes no other extent — relix.relations, relix.columns and relix.functions still describe only the user's namespace — and it cannot recurse, because it is computed in Java from the symbol table rather than derived over another catalog view. It exists because the exclusion above left the introspection surface as the one thing that could not be introspected.

Structural vs temporal extents

Every catalog above answers "what is" — it is a projection of the model being analysed, so its extent is fixed the moment analysis finishes. relix.events is the one that answers "what happened": its rows are the observability feed of the previously executed statement, handed in by the session host. It has to be the previous one, because the feed a statement produces does not exist until the optimizer, planner, and executor have run — all of which come strictly after the catalog is built. A batch script analysed once therefore sees an empty relix.events; a session that re-analyses per statement sees the run before it. This is the push→pull bridge of ADR-0017 Decision 3, and it is the only catalog whose extent is not a function of the script.

Scope

Ships relix.relations, relix.dependencies, relix.columns, relix.keys (candidate keys, the one collected statistic with no flat column to sit in), relix.functions, relix.connections (the last redacted — no raw URLs; ADR-0007 Decision 5), relix.plan (the queryable logical expression tree), relix.events (the previous run's feed), and relix.catalog (the reserved namespace describing itself).