Skip to content

Mount claims (registry reference)

This page is the registry-side reference for mount claims. The conceptual overview is on Mount claims (addressing); this page is the op-by-op surface for system.registry.

Source: projects/matrix-3/packages/system-platform/src/ServiceRegistryActor.ts. The actor is mounted at system.registry and exposes the registry.* ops listed below.

The op surface (verbatim)

typescript
// ServiceRegistryActor.ts:175
static override accepts = {
  'service.register':   { description: 'Register a service endpoint (name -> mount path)', name: 'string', mount: 'string' },
  'service.resolve':    { description: 'Resolve a service name to its mount path', name: 'string' },
  'service.list':       { description: 'List all registered service endpoints', includeAll: 'boolean?' },
  'service.deregister': { description: 'Remove a service registration', name: 'string' },
  'registry.register':  { description: 'Register a live logical mount claim for a component/provider', logicalMount: 'string', componentId: 'string?', providerRuntimeId: 'string?', localMount: 'string?' },
  'registry.heartbeat': { description: 'Refresh liveness for a logical mount claim/provider', logicalMount: 'string?', componentId: 'string?', providerRuntimeId: 'string?' },
  'registry.unregister':{ description: 'Unregister logical mount claims for a component/provider', logicalMount: 'string?', componentId: 'string?', providerRuntimeId: 'string?' },
  'registry.list':      { description: 'List live logical mount claims', prefix: 'string?', includeStale: 'boolean?' },
  'registry.resolve':   { description: 'Resolve a logical mount to live providers', logicalMount: 'string', includeStale: 'boolean?' },
  'registry.children':  { description: 'List direct logical children under a prefix', prefix: 'string?', includeStale: 'boolean?' },
  'registry.providers': { description: 'List providers for logical mounts', logicalMount: 'string?', includeStale: 'boolean?' },
};

The service.* family is a v1 compatibility alias for named service endpoints (a single name → mount mapping). New code uses registry.* exclusively.

registry.register

Register a live mount claim. Called by a runtime when it mounts an actor that exposes a logical service.

Payload:

typescript
{
  logicalMount:       string;   // public name, e.g. 'chat.conversation'
  componentId?:       string;   // actor instance id (defaults to logicalMount)
  componentType?:     string;   // class name (e.g. 'ChatConversationActor')
  authorityRoot?:     string;   // owning Space root; defaults to caller's transport root
  scope?:             string;   // optional sub-scope
  providerRuntimeId?: string;   // process serving this claim (defaults to caller's runtimeId)
  runtimeWireRoot?:   string;   // wire prefix of that runtime
  localMount?:        string;   // mount inside that runtime (defaults to logicalMount)
  packageName?:       string;   // owning package
  metadata?:          object;   // arbitrary; consumed by catalog
  heartbeatTtlMs?:    number;   // default 30000 (DEFAULT_HEARTBEAT_TTL_MS)
}

Reply:

typescript
{
  ok: true,
  entry: IRegistryView,    // the stored claim, including status & expiresAt
}

Side effect: emits registry.registered on $events.

registry.heartbeat

Refresh liveness on an existing claim. The runtime must call this within heartbeatTtlMs (default 30 s) or the claim transitions to stale.

Payload: any of logicalMount, componentId, providerRuntimeId, packageName. The actor uses these to identify the claim. The most common form is { providerRuntimeId } to refresh all claims for one runtime in one call.

Reply: { ok: true, refreshed: number } — number of claims actually refreshed.

Side effect: emits registry.heartbeat on $events.

registry.resolve

Look up live providers for a logical mount.

Payload:

typescript
{
  logicalMount: string;
  includeStale?: boolean;  // default false
}

Reply:

typescript
{
  ok: true,
  logicalMount: string,
  providers: IRegistryView[],   // each entry includes runtimeWireRoot, localMount, status
}

If includeStale: true, the response includes claims past their TTL. Typical resolution code uses the first provider in live status:

typescript
const { providers } = await actor.invoke('system.registry', 'registry.resolve', {
  logicalMount: 'chat.conversation',
});
const live = providers.filter(p => p.status === 'live');
const target = live[0];   // simple policy: pick the first
const targetSubject = `${target.runtimeWireRoot}/${target.localMount}/$inbox`;

registry.list

List all live claims, optionally filtered by prefix.

Payload:

typescript
{
  prefix?: string;          // 'chat.' returns chat.* claims
  includeStale?: boolean;   // default false
}

Reply:

typescript
{
  ok: true,
  count: number,
  claims: IRegistryView[],
}

registry.children

List direct logical children under a prefix. Used by Director and the catalog to render a tree.

Payload:

typescript
{
  prefix?: string;
  includeStale?: boolean;
}

Reply:

typescript
{
  ok: true,
  prefix: string,
  children: Array<{
    mount: string,
    segment: string,
    isClaim: boolean,
    providerCount: number,
    hasChildren: boolean,
  }>,
}

registry.providers

List all providers for one or many logical mounts.

Payload:

typescript
{
  logicalMount?: string;
  includeStale?: boolean;
}

Reply: { ok: true, providers: IRegistryView[] } — flat list across mounts (filtered if logicalMount is given).

registry.unregister

Remove claims for a runtime or a specific (mount, runtime) pair.

Payload: identifier(s) — providerRuntimeId (most common, removes all claims for one runtime) or specific logicalMount + providerRuntimeId.

Reply:

typescript
{ ok: true, removed: IRegistryView[] }

Side effect: emits registry.unregistered on $events with the removed claims.

Heartbeat TTL

Default heartbeatTtlMs is 30 seconds (verbatim from ServiceRegistryActor.ts:119):

typescript
private static readonly DEFAULT_HEARTBEAT_TTL_MS = 30_000;

Runtimes typically heartbeat every 10 seconds with a 30-second TTL, giving 3x headroom for transient delays.

Stale handling today

registry.list and registry.resolve filter out stale claims by default. There is no automatic eviction of stale records — they remain in the in-memory map until explicit unregister. A future eviction sweep is backlog material; today the catalog and Director treat status: 'stale' records as "was here, may be back".

What registry.register does NOT do

  • It does not validate that no other provider already holds the mount. Two registrations for the same logical mount and different providerRuntimeId are accepted; resolution returns both.
  • It does not enforce singleton invariants. The authority-model rules require ops policy at the calling layer to detect double-claims on singletons. See Singleton claims.
  • It does not check the runtime is alive. A registration without a heartbeat goes stale after heartbeatTtlMs; the registry does not proactively detect dead runtimes.

See also