Skip to content

Discovery projections

A discovery projection is a read-only view computed from authoritative state. Matrix has three first-class projections today: system.catalog, /api/apps, and /api/identity/runtime-summary. Each derives from the same authority sources but answers a different question.

Source: WORKSTREAMS/core-and-packaging/MATRIX-AUTHORITY-MODEL.md § "Authority / projection table". The rule is enforced everywhere: projections accept no writes; consistency comes from the authorities.

system.catalog

Canonical search and namespace projection. Composes system.runtimes, system.registry, system.gateway.http route status, and $introspect.

Op surface (verbatim from projects/matrix-3/packages/system-catalog/src/SystemCatalogActor.ts:140):

typescript
static override accepts: Record<string, unknown> = {
  'catalog.list':     { description: 'List canonical catalog entries derived from system.registry and system.runtimes', prefix: 'string?' },
  'catalog.resolve':  { description: 'Resolve one canonical catalog entry and include provider metadata', mount: 'string' },
  'catalog.roots':    { description: 'List top-level canonical catalog namespace roots', options: 'object?' },
  'catalog.children': { description: 'List immediate child namespace segments under a canonical catalog prefix', prefix: 'string?' },
  'catalog.tree':     { description: 'Return the canonical catalog namespace tree', prefix: 'string?' },
  'catalog.search':   { description: 'Search live runtime catalog surfaces and actor/service mounts', text: 'string?', prefix: 'string?', mount: 'string?', packageName: 'string?', accepts: 'string?', emits: 'string?', skill: 'string?', kind: 'string?', inferredKind: 'string?', declaredKind: 'string?', shells: 'string?', status: 'running|stopped|all?' },
};

catalog.search is the workhorse. Filter by:

FilterMatches against
textfull-text on description, mount, package
prefixmount path prefix (chat., system.inference.)
mountexact mount
packageNamethe package owning the mount
acceptsactors that accept this op (e.g., inference.start)
emitsactors that emit this event
skilldeclared skill (from MatrixActor.skills)
kind / inferredKind / declaredKindactor kind classification
shellswebapp shell role (platform, edge, developer)
statusrunning / stopped / all

Concrete catalog entry (from ISystemCatalogEntry in source):

typescript
{
  mount: "system.inference.openai",
  type: "OpenAIInferenceActor",
  purpose: "OpenAI inference provider",
  ops: ["inference.start", "inference.cancel"],
  packageName: "@open-matrix/driver-openai",
  providerCount: 1,
  providers: [
    {
      provider: "rt-inference-openai",
      runtimeId: "rt-inference-openai",
      packageName: "@open-matrix/driver-openai",
      localMount: "system.inference.openai",
      runtimeRoot: "COM.NIMBLETEC.RICHARD-SANTOMAURO",
      runtimeType: "service",
      authorityRoot: "COM.NIMBLETEC.RICHARD-SANTOMAURO"
    }
  ]
}

/api/apps

HTTP-side shell launcher feed. Returns the list of webapp routes the gateway is currently serving, plus enough metadata for the shell to render a launcher (icon, displayName, navOrder, shells).

/api/apps is derived from system.gateway.http route metadata, which is itself derived from runtime registrations whose packages declare a webapp block in matrix.json. The rule (verbatim from MATRIX-AUTHORITY-MODEL.md):

"/api/apps MUST be derivable from gateway routes that, in turn, derive from runtime metadata. No standalone hosted-apps store."

Per-entry shape (target):

json
{
  "appName": "chat",
  "displayName": "Matrix Chat",
  "icon": "...",
  "navOrder": 30,
  "shells": ["platform", "edge"],
  "routePrefix": "/apps/chat/",
  "runtimeId": "rt-chat-dev-01",
  "packageName": "@open-matrix/chat",
  "description": "..."
}

The shell calls /api/apps, builds the navbar, and uses the returned routePrefix for navigation.

/api/identity/runtime-summary

Dashboard composite. Session-scoped read of system.devices + device links + registered runtimes + /api/apps for the signed-in principal. Returns a single response with everything the dashboard needs to render the user's view of their devices and apps.

The rule (verbatim):

*"/api/identity/runtime-summary MUST be derivable from system.devices

  • deviceLinks + registered runtimes + /api/apps, scoped to the authenticated principal. No cross-tenant data."*

Cross-tenant boundary is enforced at the HTTP gateway: the principal in the session cookie governs which records appear.

What is NOT a projection

These look like they could be projections but are something else:

  • system.runtimes — authority. Owns the physical inventory; accepts runtimes.register/runtimes.heartbeat.
  • system.registry — authority. Owns the logical mount claims.
  • system.devices — authority. Owns the linked-Device inventory.
  • system.bindings — legacy compatibility surface that accepts writes. Not a projection.

A projection has zero register/update/store ops. If you see a write op on a "catalog" or "view" actor, it is misnamed (or ought to be moved to an authority).

Building a new projection

When you need a new view, the recipe is:

  1. Identify the authority surfaces it derives from (system.runtimes / system.registry / system.gateway.http / etc.).
  2. Define the projection actor with read-only ops<view>.list, <view>.resolve, <view>.search.
  3. Implement each read by composing authority reads on demand. No local write-through cache that another writer doesn't refresh.
  4. Document divergence: if the projection caches, document the staleness bound (e.g., "10 s stale max").

Do not create an authoritative system.apps or system.deployments registry without explicit ontology approval. The discovery-metadata spec (WORKSTREAMS/core-and-packaging/MATRIX-DISCOVERY-METADATA-SPEC.md) is the gate for new authority types.

See also