Skip to content

system.catalog

system.catalog is a read-only projection. The system runtime can compose system.registry + system.runtimes into a single response so a client doesn't have to do that work itself. CLAUDE.md ("Architecture") explicitly classifies it as a read projection, not the canonical state.

Ops Director consumes

catalog.list (optional, read)

DirectorRuntimeAdapter.loadSystemCatalog (src/services/DirectorRuntimeAdapter.ts:383-411):

ts
const result = await RequestReply.execute(
  context,
  'system.catalog',
  'catalog.list',
  {},
  { timeoutMs: 5000, targetRoot: root },
) as {
  ok?: boolean,
  catalog?: CatalogEntry[],
  runtimes?: IDirectorRuntimeEntry[],
  bindings?: BindingRuleEntry[],
};
if (result?.ok !== true || !Array.isArray(result.catalog)) {
  return null;
}
return { catalog, runtimes: ..., bindings: ... };

The function is not the default code path. loadCatalogWithRuntimes does its own registry + runtimes composition (line 354-381) regardless of whether system.catalog is mounted. loadSystemCatalog exists as a shortcut that callers can use when they explicitly want the unified response without round-tripping two queries.

When system.catalog is missing or returns malformed data, loadSystemCatalog returns null and callers fall back to the parallel-fetch path. That keeps Director resilient to deployments where system.catalog isn't enabled.

Why Director uses the parallel path by default

Two reasons:

  1. Provenance. loadCatalogWithRuntimes returns source: 'registry' | 'empty-registry' and fallbackUsed: boolean. That distinction is meaningful for diagnostics. A unified system.catalog projection blurs whether a result came from registry truth or from inventory inference.
  2. Targeting. The parallel path keeps BindingRuleEntry[] available alongside the catalog. Director needs binding rules to resolve canonical → local mount before issuing $introspect to the right address; embedding that in system.catalog would force a derived shape Director doesn't need.

Fields produced by the projection

When the projection is used, the response shape is:

ts
{
  ok: true,
  catalog: CatalogEntry[],     // see system.registry page
  runtimes: IDirectorRuntimeEntry[],
  bindings: BindingRuleEntry[],
}

A correct projection must satisfy: every bindings.match resolves to at least one runtime; every catalog entry's runtimeId (when set) matches a runtime in the runtimes array.

What system.catalog is not

  • Not a write surface. Director never calls catalog.write/upsert/remove. There is no such op in the current adapter.
  • Not the actor-namespace facade. system.catalog is operator/UI-facing; logical mount paths go through system.registry.
  • Not the device-namespace facade. system.devices (also operator-facing, for Devices page) is a separate facade.

See also

Source: projects/matrix-3/packages/director/src/services/DirectorRuntimeAdapter.ts:354-411.