Appearance
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:
- Provenance.
loadCatalogWithRuntimesreturnssource: 'registry' | 'empty-registry'andfallbackUsed: boolean. That distinction is meaningful for diagnostics. A unifiedsystem.catalogprojection blurs whether a result came from registry truth or from inventory inference. - Targeting. The parallel path keeps
BindingRuleEntry[]available alongside the catalog. Director needs binding rules to resolve canonical → local mount before issuing$introspectto the right address; embedding that insystem.catalogwould 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.catalogis operator/UI-facing; logical mount paths go throughsystem.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.