1419 lines
47 KiB
TypeScript
1419 lines
47 KiB
TypeScript
import * as fs from 'node:fs';
|
|
import * as path from 'node:path';
|
|
|
|
import { RequestReply } from '@open-matrix/core/engine/remoting/RequestReply.js';
|
|
import type { MatrixActor } from '@open-matrix/core/core/MatrixActor.js';
|
|
import { MatrixRuntime } from '@open-matrix/core/runtime/MatrixRuntime.js';
|
|
import {
|
|
countRuntimeDirectory,
|
|
publishRuntimePresence,
|
|
} from '@open-matrix/core';
|
|
import type {
|
|
MatrixMountCardinality,
|
|
MatrixPlacementClaimMode,
|
|
RuntimeInventoryPlacementMetadata,
|
|
} from '@open-matrix/contracts/placement';
|
|
import type { RuntimePresenceRecordV1 } from '@open-matrix/contracts/runtime-presence';
|
|
import type { ILoadedMatrixPackageEnvironment } from './package-environment.js';
|
|
import type { IResolvedRunnerTransportPlan } from './runner-transport.js';
|
|
import type {
|
|
ILoadedMatrixBindingDeclaration,
|
|
ILoadedMatrixServiceManifest,
|
|
} from './service-manifest.js';
|
|
import { readRunnerPlacementStandbyResults } from './runner-placement-policy.js';
|
|
import { BindingsRegistryActor } from '@open-matrix/system-bindings';
|
|
import { ServiceRegistryActor } from '@open-matrix/system-platform';
|
|
import { RuntimeManagerActor } from '@open-matrix/system-runtimes';
|
|
|
|
const RUNTIME_REGISTRY_MOUNT = 'system.runtimes';
|
|
const LOGICAL_REGISTRY_MOUNT = 'system.registry';
|
|
const BINDINGS_REGISTRY_MOUNT = 'system.bindings';
|
|
const RUNTIME_PRESENCE_TTL_MS = 30_000;
|
|
const LOCAL_CONTROL_PLANE_REQUEST_TIMEOUT_MS = 5_000;
|
|
const LINKED_CONTROL_PLANE_REQUEST_TIMEOUT_MS = 15_000;
|
|
|
|
interface IRuntimeInventoryEntry {
|
|
readonly mount: string;
|
|
readonly type?: string;
|
|
readonly runtimeId: string;
|
|
readonly claimKind: MatrixPlacementClaimMode;
|
|
readonly placement: RuntimeInventoryPlacementMetadata;
|
|
readonly description?: string;
|
|
readonly hasChildren?: boolean;
|
|
}
|
|
|
|
interface IPlacementDeclarationIndex {
|
|
readonly byMount: ReadonlyMap<string, MatrixMountCardinality>;
|
|
}
|
|
|
|
export interface IRunnerControlPlaneState {
|
|
readonly runtimeRegistryMount: string;
|
|
readonly logicalRegistryMount: string;
|
|
readonly bindingsRegistryMount: string;
|
|
readonly createdRuntimeRegistry: boolean;
|
|
readonly createdLogicalRegistry: boolean;
|
|
readonly createdBindingsRegistry: boolean;
|
|
}
|
|
|
|
export interface IRunnerRuntimeRegistration {
|
|
readonly runtimeId: string;
|
|
readonly runtimeRoot: string;
|
|
readonly presence: RuntimePresenceRecordV1;
|
|
readonly registryMount: string;
|
|
readonly logicalRegistryMount: string;
|
|
readonly controlTargetRoot?: string;
|
|
readonly protocolRequest?: boolean;
|
|
readonly runtimeMount?: string;
|
|
readonly controlMount?: string;
|
|
readonly created: boolean;
|
|
readonly actorCount: number;
|
|
readonly inventoryClaimCount: number;
|
|
readonly runtimeCount: number;
|
|
readonly registryOptional?: boolean;
|
|
/**
|
|
* Re-issue the inventory `registry.claim` calls and re-publish presence.
|
|
* Used by the heartbeat tick when `registry.claim.renew` reports the
|
|
* registry no longer has our claim (e.g. after the registry actor's
|
|
* in-memory `_claims` map was wiped by a SYSTEM runtime restart).
|
|
* Idempotent: `onRegistryClaim` preserves `registeredAt`/`claimedAt`
|
|
* when an existing entry is found.
|
|
*/
|
|
readonly reclaim: () => Promise<{ inventoryClaimCount: number }>;
|
|
}
|
|
|
|
export interface IRunnerRuntimeDeregistration {
|
|
readonly runtimeId: string;
|
|
readonly registryMount: string;
|
|
readonly removed: boolean;
|
|
readonly runtimeCount: number;
|
|
}
|
|
|
|
export interface IRunnerRuntimeHeartbeat {
|
|
readonly runtimeId: string;
|
|
readonly known: boolean;
|
|
}
|
|
|
|
export interface IRunnerWebappRouteMetadata {
|
|
readonly appName: string;
|
|
readonly routePrefix?: string;
|
|
readonly displayName?: string;
|
|
readonly icon?: string;
|
|
readonly navOrder?: number;
|
|
readonly description?: string;
|
|
readonly shells?: readonly string[];
|
|
readonly assetMount?: string;
|
|
readonly origin?: string;
|
|
readonly port?: number;
|
|
}
|
|
|
|
export interface IRunnerBindingRegistration {
|
|
readonly runtimeId: string;
|
|
readonly registryMount: string;
|
|
readonly logicalRegistryMount: string;
|
|
readonly controlTargetRoot?: string;
|
|
readonly protocolRequest?: boolean;
|
|
readonly registeredCount: number;
|
|
readonly registryClaimCount: number;
|
|
readonly registryOptional?: boolean;
|
|
readonly bindings: ReadonlyArray<{
|
|
readonly binding: string;
|
|
readonly localMount: string;
|
|
readonly ops: readonly string[];
|
|
readonly packageName: string;
|
|
}>;
|
|
/**
|
|
* Re-issue every server-side record this runner published at registration
|
|
* time: the canonical-binding `registry.claim` calls AND the
|
|
* `bindings.register` calls. Both are in-memory on their respective
|
|
* registry actors, so a single SYSTEM restart wipes them together. The
|
|
* heartbeat tick triggers reclaim when `registry.claim.renew` reports the
|
|
* registry no longer has our claims; bindings.register loss is treated as
|
|
* a co-occurring symptom of the same SYSTEM restart and reclaimed in
|
|
* lockstep. Both server-side ops are idempotent (onRegistryClaim
|
|
* preserves identity; bindings.register is keyed by provider+match), so
|
|
* calling reclaim() against a healthy registry is harmless.
|
|
*/
|
|
readonly reclaim: () => Promise<{
|
|
readonly registryClaimCount: number;
|
|
readonly bindingsRegisteredCount: number;
|
|
}>;
|
|
}
|
|
|
|
export interface IRunnerBindingDeregistration {
|
|
readonly runtimeId: string;
|
|
readonly registryMount: string;
|
|
readonly logicalRegistryMount: string;
|
|
readonly removedCount: number;
|
|
readonly registryRemovedCount: number;
|
|
}
|
|
|
|
export interface IRunnerBindingRenewal {
|
|
readonly runtimeId: string;
|
|
readonly refreshed: number;
|
|
}
|
|
|
|
interface ICanonicalBindingDefinition {
|
|
readonly binding: string;
|
|
readonly localMount: string;
|
|
readonly ops: readonly string[];
|
|
readonly packageName: string;
|
|
}
|
|
|
|
interface IRunnerHostPlacement {
|
|
readonly deviceId: string;
|
|
readonly hostId: string;
|
|
readonly hostName?: string;
|
|
readonly deviceSlug?: string;
|
|
}
|
|
|
|
interface IControlPlaneRequestOptions {
|
|
readonly timeoutMs: number;
|
|
readonly targetRoot?: string;
|
|
readonly protocolRequest?: boolean;
|
|
}
|
|
|
|
export async function ensureRunnerControlPlane(runtime: MatrixRuntime): Promise<IRunnerControlPlaneState> {
|
|
const createdRuntimeRegistry = await ensureRegistryActor(runtime, RUNTIME_REGISTRY_MOUNT, RuntimeManagerActor);
|
|
const createdLogicalRegistry = await ensureRegistryActor(runtime, LOGICAL_REGISTRY_MOUNT, ServiceRegistryActor);
|
|
const createdBindingsRegistry = await ensureRegistryActor(runtime, BINDINGS_REGISTRY_MOUNT, BindingsRegistryActor);
|
|
return {
|
|
runtimeRegistryMount: RUNTIME_REGISTRY_MOUNT,
|
|
logicalRegistryMount: LOGICAL_REGISTRY_MOUNT,
|
|
bindingsRegistryMount: BINDINGS_REGISTRY_MOUNT,
|
|
createdRuntimeRegistry,
|
|
createdLogicalRegistry,
|
|
createdBindingsRegistry,
|
|
};
|
|
}
|
|
|
|
export interface IEnsureRunnerControlPlaneOptions {
|
|
readonly createLocalRegistries?: boolean;
|
|
}
|
|
|
|
export async function ensureRunnerControlPlaneForMode(
|
|
runtime: MatrixRuntime,
|
|
options: IEnsureRunnerControlPlaneOptions = {},
|
|
): Promise<IRunnerControlPlaneState> {
|
|
const createLocalRegistries = options.createLocalRegistries ?? true;
|
|
const createdRuntimeRegistry = createLocalRegistries
|
|
? await ensureRegistryActor(runtime, RUNTIME_REGISTRY_MOUNT, RuntimeManagerActor)
|
|
: false;
|
|
const createdLogicalRegistry = createLocalRegistries
|
|
? await ensureRegistryActor(runtime, LOGICAL_REGISTRY_MOUNT, ServiceRegistryActor)
|
|
: false;
|
|
const createdBindingsRegistry = createLocalRegistries
|
|
? await ensureRegistryActor(runtime, BINDINGS_REGISTRY_MOUNT, BindingsRegistryActor)
|
|
: false;
|
|
return {
|
|
runtimeRegistryMount: RUNTIME_REGISTRY_MOUNT,
|
|
logicalRegistryMount: LOGICAL_REGISTRY_MOUNT,
|
|
bindingsRegistryMount: BINDINGS_REGISTRY_MOUNT,
|
|
createdRuntimeRegistry,
|
|
createdLogicalRegistry,
|
|
createdBindingsRegistry,
|
|
};
|
|
}
|
|
|
|
export async function registerRunnerRuntime(
|
|
runtime: MatrixRuntime,
|
|
manifest: ILoadedMatrixServiceManifest,
|
|
environment: ILoadedMatrixPackageEnvironment | undefined,
|
|
runnerTransport: IResolvedRunnerTransportPlan | undefined,
|
|
serviceMount?: string,
|
|
runtimeMount?: string,
|
|
controlMount?: string,
|
|
webappRoute?: IRunnerWebappRouteMetadata,
|
|
): Promise<IRunnerRuntimeRegistration> {
|
|
const controlPlane = await ensureRunnerControlPlaneForMode(runtime, {
|
|
createLocalRegistries: !isHostManagedEnvironment(environment),
|
|
});
|
|
const runtimeId = resolveRunnerRuntimeId(manifest, environment);
|
|
const runtimeRoot = readTransportRoot(runtime)
|
|
?? environment?.environment.runtime.root
|
|
?? manifest.root
|
|
?? runtimeId;
|
|
const hostPlacement = resolveRunnerHostPlacement(runtime, environment);
|
|
const registryOptional = isHostedAuthorityLeafEnvironment(runtime, environment);
|
|
const actorCount = runtime.getAllComponents().length;
|
|
const placementDeclarations = readPlacementDeclarations(manifest);
|
|
const requestOptions = controlPlaneRequestOptions(
|
|
runtime,
|
|
environment,
|
|
controlPlaneRequestTimeoutMs(environment),
|
|
);
|
|
const inventory = buildRuntimeInventory(runtime, runtimeId, placementDeclarations);
|
|
const presence = buildRuntimePresenceRecord({
|
|
runtimeId,
|
|
runtimeRoot,
|
|
manifest,
|
|
environment,
|
|
actorCount,
|
|
inventory,
|
|
serviceMount,
|
|
runtimeMount,
|
|
controlMount,
|
|
runnerTransport,
|
|
webappRoute,
|
|
hostPlacement,
|
|
});
|
|
const inventoryClaimCount = await registerRuntimeInventoryClaims(
|
|
runtime,
|
|
controlPlane.logicalRegistryMount,
|
|
manifest,
|
|
environment,
|
|
runtimeId,
|
|
runtimeRoot,
|
|
inventory,
|
|
serviceMount,
|
|
webappRoute,
|
|
hostPlacement,
|
|
registryOptional,
|
|
);
|
|
|
|
publishRuntimePresence(runtime.getRootContext(), presence, 'announce');
|
|
const runtimeCount = await tryReadRegisteredRuntimeCount(
|
|
runtime,
|
|
controlPlane.logicalRegistryMount,
|
|
);
|
|
|
|
// Closure for re-issuing the same inventory claims when the registry has
|
|
// forgotten us (renew returns renewed: 0). Captures everything needed —
|
|
// the runtime, the original manifest/environment, runtimeId, etc. —
|
|
// so the heartbeat tick can call reclaim() without re-resolving any
|
|
// identity. `onRegistryClaim` is idempotent for an existing entry, so
|
|
// calling reclaim() when the registry IS healthy is harmless.
|
|
const reclaim = async (): Promise<{ inventoryClaimCount: number }> => {
|
|
const freshActorCount = runtime.getAllComponents().length;
|
|
const freshInventory = buildRuntimeInventory(runtime, runtimeId, placementDeclarations);
|
|
const freshPresence = buildRuntimePresenceRecord({
|
|
runtimeId,
|
|
runtimeRoot,
|
|
manifest,
|
|
environment,
|
|
actorCount: freshActorCount,
|
|
inventory: freshInventory,
|
|
serviceMount,
|
|
runtimeMount,
|
|
controlMount,
|
|
runnerTransport,
|
|
webappRoute,
|
|
hostPlacement,
|
|
});
|
|
const reclaimedCount = await registerRuntimeInventoryClaims(
|
|
runtime,
|
|
controlPlane.logicalRegistryMount,
|
|
manifest,
|
|
environment,
|
|
runtimeId,
|
|
runtimeRoot,
|
|
freshInventory,
|
|
serviceMount,
|
|
webappRoute,
|
|
hostPlacement,
|
|
registryOptional,
|
|
);
|
|
// Re-announce presence so RuntimeManagerActor's _runtimes Map repopulates
|
|
// with us even if it was cleared by a SYSTEM restart.
|
|
publishRuntimePresence(runtime.getRootContext(), freshPresence, 'announce');
|
|
return { inventoryClaimCount: reclaimedCount };
|
|
};
|
|
|
|
return {
|
|
runtimeId,
|
|
runtimeRoot,
|
|
presence,
|
|
registryMount: controlPlane.runtimeRegistryMount,
|
|
logicalRegistryMount: controlPlane.logicalRegistryMount,
|
|
...(requestOptions.targetRoot ? { controlTargetRoot: requestOptions.targetRoot } : {}),
|
|
...(requestOptions.protocolRequest === true ? { protocolRequest: true } : {}),
|
|
...(runtimeMount ? { runtimeMount } : {}),
|
|
...(controlMount ? { controlMount } : {}),
|
|
created: true,
|
|
actorCount,
|
|
inventoryClaimCount,
|
|
runtimeCount,
|
|
...(registryOptional ? { registryOptional: true } : {}),
|
|
reclaim,
|
|
};
|
|
}
|
|
|
|
export async function deregisterRunnerRuntime(
|
|
runtime: MatrixRuntime,
|
|
registration: IRunnerRuntimeRegistration | undefined,
|
|
): Promise<IRunnerRuntimeDeregistration | undefined> {
|
|
if (!registration) {
|
|
return undefined;
|
|
}
|
|
if (registration.registryOptional === true) {
|
|
const departedAt = Date.now();
|
|
publishRuntimePresence(
|
|
runtime.getRootContext(),
|
|
{
|
|
...registration.presence,
|
|
lastHeartbeat: departedAt,
|
|
health: {
|
|
status: 'stopped',
|
|
updatedAt: departedAt,
|
|
},
|
|
},
|
|
'departed',
|
|
);
|
|
return {
|
|
runtimeId: registration.runtimeId,
|
|
registryMount: registration.registryMount,
|
|
removed: false,
|
|
runtimeCount: 0,
|
|
};
|
|
}
|
|
|
|
const registryReleased = await RequestReply.execute<Record<string, unknown>, { removed?: number }>(
|
|
runtime.getRootContext(),
|
|
registration.logicalRegistryMount,
|
|
'registry.release',
|
|
{ providerRuntimeId: registration.runtimeId },
|
|
registrationControlPlaneRequestOptions(registration, LOCAL_CONTROL_PLANE_REQUEST_TIMEOUT_MS),
|
|
);
|
|
const departedAt = Date.now();
|
|
publishRuntimePresence(
|
|
runtime.getRootContext(),
|
|
{
|
|
...registration.presence,
|
|
lastHeartbeat: departedAt,
|
|
health: {
|
|
status: 'stopped',
|
|
updatedAt: departedAt,
|
|
},
|
|
},
|
|
'departed',
|
|
);
|
|
|
|
const runtimeCount = await tryReadRegisteredRuntimeCount(
|
|
runtime,
|
|
registration.logicalRegistryMount,
|
|
);
|
|
|
|
return {
|
|
runtimeId: registration.runtimeId,
|
|
registryMount: registration.registryMount,
|
|
removed: typeof registryReleased.removed === 'number' && registryReleased.removed > 0,
|
|
runtimeCount,
|
|
};
|
|
}
|
|
|
|
async function tryReadRegisteredRuntimeCount(
|
|
runtime: MatrixRuntime,
|
|
_registryMount: string,
|
|
): Promise<number> {
|
|
// Slice 1b (control-actor-nav): runtime directory authority is
|
|
// `system.runtimes`, not the service registry. Read through the adapter.
|
|
try {
|
|
return await countRuntimeDirectory(runtime.getRootContext(), { includeStale: true });
|
|
} catch {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
export async function heartbeatRunnerRuntime(
|
|
runtime: MatrixRuntime,
|
|
registration: IRunnerRuntimeRegistration | undefined,
|
|
): Promise<IRunnerRuntimeHeartbeat | undefined> {
|
|
if (!registration) {
|
|
return undefined;
|
|
}
|
|
if (registration.registryOptional === true) {
|
|
const now = Date.now();
|
|
publishRuntimePresence(
|
|
runtime.getRootContext(),
|
|
{
|
|
...registration.presence,
|
|
inventory: rebuildRuntimeInventoryForHeartbeat(runtime, registration),
|
|
lastHeartbeat: now,
|
|
health: {
|
|
status: registration.presence.health?.status ?? 'ok',
|
|
...(registration.presence.health?.reason ? { reason: registration.presence.health.reason } : {}),
|
|
updatedAt: now,
|
|
},
|
|
},
|
|
'heartbeat',
|
|
);
|
|
return {
|
|
runtimeId: registration.runtimeId,
|
|
known: true,
|
|
};
|
|
}
|
|
const renewal = await RequestReply.execute<Record<string, unknown>, { renewed?: number }>(
|
|
runtime.getRootContext(),
|
|
registration.logicalRegistryMount,
|
|
'registry.claim.renew',
|
|
{
|
|
providerRuntimeId: registration.runtimeId,
|
|
ttlMs: 30_000,
|
|
},
|
|
registrationControlPlaneRequestOptions(registration, 5_000),
|
|
);
|
|
const now = Date.now();
|
|
publishRuntimePresence(
|
|
runtime.getRootContext(),
|
|
{
|
|
...registration.presence,
|
|
inventory: rebuildRuntimeInventoryForHeartbeat(runtime, registration),
|
|
lastHeartbeat: now,
|
|
health: {
|
|
status: registration.presence.health?.status ?? 'ok',
|
|
...(registration.presence.health?.reason ? { reason: registration.presence.health.reason } : {}),
|
|
updatedAt: now,
|
|
},
|
|
},
|
|
'heartbeat',
|
|
);
|
|
return {
|
|
runtimeId: registration.runtimeId,
|
|
known: typeof renewal.renewed === 'number' && renewal.renewed > 0,
|
|
};
|
|
}
|
|
|
|
export async function registerRunnerBindings(
|
|
runtime: MatrixRuntime,
|
|
manifest: ILoadedMatrixServiceManifest,
|
|
environment: ILoadedMatrixPackageEnvironment | undefined,
|
|
runtimeId: string,
|
|
serviceMount?: string,
|
|
): Promise<IRunnerBindingRegistration> {
|
|
const controlPlane = await ensureRunnerControlPlaneForMode(runtime, {
|
|
createLocalRegistries: !isHostManagedEnvironment(environment),
|
|
});
|
|
const nonCallableBindingMounts = await resolveNonCallableBindingMounts(runtime, manifest, serviceMount);
|
|
const bindings = deriveCanonicalBindings(manifest, environment, serviceMount)
|
|
.filter((binding) => !nonCallableBindingMounts.has(binding.localMount));
|
|
const placementDeclarations = readPlacementDeclarations(manifest);
|
|
const runtimeWireRoot = readTransportRoot(runtime)
|
|
?? environment?.environment.runtime.root
|
|
?? manifest.root
|
|
?? runtimeId;
|
|
const authorityRoot = environment?.environment.runtime.root
|
|
?? manifest.root
|
|
?? runtimeWireRoot;
|
|
const hostPlacement = resolveRunnerHostPlacement(runtime, environment);
|
|
const registryOptional = isHostedAuthorityLeafEnvironment(runtime, environment);
|
|
const timeoutMs = controlPlaneRequestTimeoutMs(environment);
|
|
const requestOptions = controlPlaneRequestOptions(runtime, environment, timeoutMs);
|
|
const publishBindingForwards = shouldPublishBindingForwards(environment);
|
|
|
|
const issueBindingClaims = async (): Promise<number> => {
|
|
let issued = 0;
|
|
for (const binding of bindings) {
|
|
const placement = placementForMount(
|
|
binding.localMount,
|
|
binding.binding,
|
|
resolveCardinalityForMount(placementDeclarations, binding.localMount),
|
|
true,
|
|
);
|
|
await RequestReply.execute(
|
|
runtime.getRootContext(),
|
|
controlPlane.logicalRegistryMount,
|
|
'registry.claim',
|
|
{
|
|
mount: binding.binding,
|
|
kind: 'actor',
|
|
componentId: `${binding.packageName}:${binding.binding}`,
|
|
componentType: 'actor',
|
|
authorityRoot,
|
|
scope: 'identity-relative',
|
|
providerRuntimeId: runtimeId,
|
|
runtimeWireRoot,
|
|
localMount: binding.localMount,
|
|
packageName: binding.packageName,
|
|
mode: placement.claimMode,
|
|
cardinality: placement.cardinality,
|
|
claimMode: placement.claimMode,
|
|
logicalMount: placement.logicalMount,
|
|
physicalMount: placement.physicalMount,
|
|
callable: placement.callable,
|
|
...registryClaimPlacement(hostPlacement),
|
|
metadata: {
|
|
ops: [...binding.ops],
|
|
source: 'runner-control-plane',
|
|
placement,
|
|
...hostPlacementMetadata(hostPlacement),
|
|
},
|
|
},
|
|
requestOptions,
|
|
);
|
|
issued += 1;
|
|
}
|
|
return issued;
|
|
};
|
|
|
|
const registerBindingForwards = async (): Promise<number> => {
|
|
if (!publishBindingForwards) {
|
|
return 0;
|
|
}
|
|
let registered = 0;
|
|
for (const binding of bindings) {
|
|
await RequestReply.execute(
|
|
runtime.getRootContext(),
|
|
controlPlane.bindingsRegistryMount,
|
|
'bindings.register',
|
|
{
|
|
provider: runtimeId,
|
|
runtimeId,
|
|
packageName: binding.packageName,
|
|
localMount: binding.localMount,
|
|
match: binding.binding,
|
|
ops: [...binding.ops],
|
|
forward: binding.localMount,
|
|
},
|
|
requestOptions,
|
|
);
|
|
registered += 1;
|
|
}
|
|
return registered;
|
|
};
|
|
|
|
const registryClaimCount = registryOptional ? 0 : await issueBindingClaims();
|
|
const bindingsRegisteredCount = registryOptional ? 0 : await registerBindingForwards();
|
|
|
|
// Reclaim re-issues every server-side record this runner published at
|
|
// registration time. Both registries (system.registry and system.bindings)
|
|
// store their state in-memory. A single SYSTEM restart wipes them
|
|
// together, so the heartbeat tick reclaims both in lockstep when
|
|
// registry.claim.renew reports loss.
|
|
const reclaim = async (): Promise<{
|
|
registryClaimCount: number;
|
|
bindingsRegisteredCount: number;
|
|
}> => {
|
|
const registryClaimCount = registryOptional ? 0 : await issueBindingClaims();
|
|
const bindingsRegisteredCount = registryOptional ? 0 : await registerBindingForwards();
|
|
return { registryClaimCount, bindingsRegisteredCount };
|
|
};
|
|
|
|
return {
|
|
runtimeId,
|
|
registryMount: controlPlane.bindingsRegistryMount,
|
|
logicalRegistryMount: controlPlane.logicalRegistryMount,
|
|
...(requestOptions.targetRoot ? { controlTargetRoot: requestOptions.targetRoot } : {}),
|
|
...(requestOptions.protocolRequest === true ? { protocolRequest: true } : {}),
|
|
registeredCount: bindingsRegisteredCount,
|
|
registryClaimCount,
|
|
...(registryOptional ? { registryOptional: true } : {}),
|
|
bindings,
|
|
reclaim,
|
|
};
|
|
}
|
|
|
|
export async function deregisterRunnerBindings(
|
|
runtime: MatrixRuntime,
|
|
registration: IRunnerBindingRegistration | undefined,
|
|
): Promise<IRunnerBindingDeregistration | undefined> {
|
|
if (!registration) {
|
|
return undefined;
|
|
}
|
|
if (registration.registeredCount === 0) {
|
|
return {
|
|
runtimeId: registration.runtimeId,
|
|
registryMount: registration.registryMount,
|
|
logicalRegistryMount: registration.logicalRegistryMount,
|
|
removedCount: 0,
|
|
registryRemovedCount: 0,
|
|
};
|
|
}
|
|
|
|
const removed = await RequestReply.execute(
|
|
runtime.getRootContext(),
|
|
registration.registryMount,
|
|
'bindings.remove',
|
|
{ provider: registration.runtimeId },
|
|
registrationControlPlaneRequestOptions(registration, 5_000),
|
|
) as { removed?: number };
|
|
|
|
return {
|
|
runtimeId: registration.runtimeId,
|
|
registryMount: registration.registryMount,
|
|
logicalRegistryMount: registration.logicalRegistryMount,
|
|
removedCount: typeof removed.removed === 'number' ? removed.removed : 0,
|
|
registryRemovedCount: 0,
|
|
};
|
|
}
|
|
|
|
export async function renewRunnerBindings(
|
|
runtime: MatrixRuntime,
|
|
registration: IRunnerBindingRegistration | undefined,
|
|
): Promise<IRunnerBindingRenewal | undefined> {
|
|
if (!registration) {
|
|
return undefined;
|
|
}
|
|
if (registration.registryOptional === true) {
|
|
return {
|
|
runtimeId: registration.runtimeId,
|
|
refreshed: registration.bindings.length,
|
|
};
|
|
}
|
|
const renewal = await RequestReply.execute(
|
|
runtime.getRootContext(),
|
|
registration.logicalRegistryMount,
|
|
'registry.claim.renew',
|
|
{
|
|
packageName: registration.bindings[0]?.packageName,
|
|
providerRuntimeId: registration.runtimeId,
|
|
},
|
|
registrationControlPlaneRequestOptions(registration, 5_000),
|
|
) as { renewed?: number };
|
|
return {
|
|
runtimeId: registration.runtimeId,
|
|
refreshed: typeof renewal.renewed === 'number' ? renewal.renewed : 0,
|
|
};
|
|
}
|
|
|
|
export function resolveRunnerRuntimeId(
|
|
manifest: ILoadedMatrixServiceManifest,
|
|
environment: ILoadedMatrixPackageEnvironment | undefined,
|
|
): string {
|
|
const runtimeId = environment?.environment.runtime.runtimeId?.trim();
|
|
if (runtimeId) {
|
|
return runtimeId;
|
|
}
|
|
const manifestId = manifest.serviceInstance.id.trim();
|
|
if (manifestId.length > 0) {
|
|
return manifestId;
|
|
}
|
|
return manifest.packageName;
|
|
}
|
|
|
|
function isHostManagedEnvironment(environment: ILoadedMatrixPackageEnvironment | undefined): boolean {
|
|
const matrixDir = environment?.environment.host?.matrixDir;
|
|
return typeof matrixDir === 'string' && matrixDir.trim().length > 0;
|
|
}
|
|
|
|
function isHostedAuthorityLeafEnvironment(
|
|
runtime: MatrixRuntime,
|
|
environment: ILoadedMatrixPackageEnvironment | undefined,
|
|
): boolean {
|
|
if (!isHostManagedEnvironment(environment)) {
|
|
return false;
|
|
}
|
|
const topology = readRecord(runtime.getRootContext().getService<unknown>('matrix-node-topology'))
|
|
?? readRecord(environment?.environment.topology);
|
|
const roles = readRecord(topology?.roles);
|
|
const authorityCoordinator = readRecord(roles?.authorityCoordinator);
|
|
if (authorityCoordinator?.active === false) {
|
|
return true;
|
|
}
|
|
const hostHome = trimToUndefined(runtime.getRootContext().getService<unknown>('matrix-host-home'))
|
|
?? trimToUndefined(environment?.environment.host?.matrixDir);
|
|
if (!hostHome) {
|
|
return false;
|
|
}
|
|
const linkRecord = readJsonObjectIfPresent(path.join(hostHome, 'credentials', 'hivecast-link.json'));
|
|
return linkRecord?.authorityCoordinator === false
|
|
&& Boolean(trimToUndefined(linkRecord.deviceRegistryRoot));
|
|
}
|
|
|
|
function readTransportRoot(runtime: MatrixRuntime): string | undefined {
|
|
const root = (runtime.transport as { root?: unknown }).root;
|
|
return typeof root === 'string' && root.trim().length > 0 ? root.trim() : undefined;
|
|
}
|
|
|
|
async function resolveNonCallableBindingMounts(
|
|
runtime: MatrixRuntime,
|
|
manifest: ILoadedMatrixServiceManifest,
|
|
serviceMount: string | undefined,
|
|
): Promise<ReadonlySet<string>> {
|
|
const mounts = new Set<string>();
|
|
for (const standby of readRunnerPlacementStandbyResults(runtime)) {
|
|
if (standby.placement.callable === false) {
|
|
mounts.add(standby.mount);
|
|
}
|
|
}
|
|
|
|
if (manifest.packageName === '@open-matrix/system') {
|
|
const systemMount = serviceMount?.trim() || manifest.mount?.trim() || manifest.packageRoot?.mount?.trim() || 'system';
|
|
const response = await RequestReply.execute<Record<string, unknown>, {
|
|
readonly children?: readonly unknown[];
|
|
}>(
|
|
runtime.getRootContext(),
|
|
systemMount,
|
|
'system.children',
|
|
{},
|
|
{ timeoutMs: 2_000 },
|
|
);
|
|
for (const child of response.children ?? []) {
|
|
const record = isRecord(child) ? child : null;
|
|
const mount = trimToUndefined(record?.mount);
|
|
const status = trimToUndefined(record?.status);
|
|
if (mount && status !== 'mounted') {
|
|
mounts.add(mount);
|
|
}
|
|
}
|
|
}
|
|
|
|
return mounts;
|
|
}
|
|
|
|
function buildRuntimeInventory(
|
|
runtime: MatrixRuntime,
|
|
runtimeId: string,
|
|
placementDeclarations: IPlacementDeclarationIndex,
|
|
): IRuntimeInventoryEntry[] {
|
|
const inventory: IRuntimeInventoryEntry[] = [];
|
|
for (const component of runtime.getAllComponents()) {
|
|
const mount = component.mount.trim();
|
|
if (mount.length === 0) {
|
|
continue;
|
|
}
|
|
const placement = placementForMount(
|
|
mount,
|
|
mount,
|
|
resolveCardinalityForMount(placementDeclarations, mount),
|
|
true,
|
|
);
|
|
const description = typeof (component.constructor as { description?: unknown }).description === 'string'
|
|
? (component.constructor as { description?: string }).description
|
|
: undefined;
|
|
inventory.push({
|
|
mount,
|
|
...(component.constructor.name ? { type: component.constructor.name } : {}),
|
|
runtimeId,
|
|
claimKind: placement.claimMode,
|
|
placement,
|
|
...(description ? { description } : {}),
|
|
...(hasIntrospectChildren(component) ? { hasChildren: true } : {}),
|
|
});
|
|
}
|
|
appendStandbyPlacementResults(inventory, runtime, runtimeId);
|
|
return inventory;
|
|
}
|
|
|
|
function rebuildRuntimeInventoryForHeartbeat(
|
|
runtime: MatrixRuntime,
|
|
registration: IRunnerRuntimeRegistration,
|
|
): IRuntimeInventoryEntry[] {
|
|
const previousPlacementByMount = new Map<string, RuntimeInventoryPlacementMetadata>();
|
|
for (const entry of registration.presence.inventory ?? []) {
|
|
if (entry.placement) {
|
|
previousPlacementByMount.set(entry.mount, entry.placement);
|
|
}
|
|
}
|
|
const inventory: IRuntimeInventoryEntry[] = [];
|
|
for (const component of runtime.getAllComponents()) {
|
|
const mount = component.mount.trim();
|
|
if (mount.length === 0) {
|
|
continue;
|
|
}
|
|
const previousPlacement = previousPlacementByMount.get(mount)
|
|
?? placementForMount(mount, mount, 'named-instance', true);
|
|
const description = typeof (component.constructor as { description?: unknown }).description === 'string'
|
|
? (component.constructor as { description?: string }).description
|
|
: undefined;
|
|
inventory.push({
|
|
mount,
|
|
...(component.constructor.name ? { type: component.constructor.name } : {}),
|
|
runtimeId: registration.runtimeId,
|
|
claimKind: previousPlacement.claimMode,
|
|
placement: previousPlacement,
|
|
...(description ? { description } : {}),
|
|
...(hasIntrospectChildren(component) ? { hasChildren: true } : {}),
|
|
});
|
|
}
|
|
appendStandbyPlacementResults(inventory, runtime, registration.runtimeId);
|
|
return inventory;
|
|
}
|
|
|
|
function appendStandbyPlacementResults(
|
|
inventory: IRuntimeInventoryEntry[],
|
|
runtime: MatrixRuntime,
|
|
runtimeId: string,
|
|
): void {
|
|
const seen = new Set(inventory.map((entry) => entry.mount));
|
|
for (const standby of readRunnerPlacementStandbyResults(runtime)) {
|
|
if (standby.runtimeId !== runtimeId || seen.has(standby.mount)) {
|
|
continue;
|
|
}
|
|
inventory.push({
|
|
mount: standby.mount,
|
|
runtimeId,
|
|
claimKind: standby.placement.claimMode,
|
|
placement: standby.placement,
|
|
});
|
|
seen.add(standby.mount);
|
|
}
|
|
}
|
|
|
|
function buildRuntimePresenceRecord(params: {
|
|
runtimeId: string;
|
|
runtimeRoot: string;
|
|
manifest: ILoadedMatrixServiceManifest;
|
|
environment: ILoadedMatrixPackageEnvironment | undefined;
|
|
actorCount: number;
|
|
inventory: readonly IRuntimeInventoryEntry[];
|
|
serviceMount: string | undefined;
|
|
runtimeMount: string | undefined;
|
|
controlMount: string | undefined;
|
|
runnerTransport: IResolvedRunnerTransportPlan | undefined;
|
|
webappRoute: IRunnerWebappRouteMetadata | undefined;
|
|
hostPlacement: IRunnerHostPlacement | undefined;
|
|
}): RuntimePresenceRecordV1 {
|
|
const now = Date.now();
|
|
const namespaceRoot = params.runtimeRoot;
|
|
return {
|
|
version: '1',
|
|
namespaceRoot,
|
|
runtimeId: params.runtimeId,
|
|
runtimeType: 'folder',
|
|
runtimeRoot: params.runtimeRoot,
|
|
authorityRoot: namespaceRoot,
|
|
app: params.manifest.packageName,
|
|
claims: buildRuntimeClaims(params.serviceMount, params.environment),
|
|
inventoryMode: 'embedded',
|
|
inventory: [...params.inventory],
|
|
...(params.controlMount ? { controlMount: params.controlMount } : {}),
|
|
heartbeatTtlMs: RUNTIME_PRESENCE_TTL_MS,
|
|
registeredAt: now,
|
|
lastHeartbeat: now,
|
|
health: {
|
|
status: 'ok',
|
|
updatedAt: now,
|
|
},
|
|
metadata: {
|
|
source: 'mx-runner-runtime-presence',
|
|
actorCount: params.actorCount,
|
|
packageDir: params.manifest.packageDir,
|
|
packageName: params.manifest.packageName,
|
|
// Runtime displayName is set by host-service at spawn (--name or
|
|
// generated). The host passes it via env so the runtime can publish
|
|
// it in its registry presence record. Consumers read this field
|
|
// directly; no derivation from package or runtimeId.
|
|
...(process.env.MATRIX_RUNTIME_DISPLAY_NAME ? { displayName: process.env.MATRIX_RUNTIME_DISPLAY_NAME } : {}),
|
|
...(params.environment ? {
|
|
environmentName: params.environment.envName,
|
|
environmentPath: params.environment.environmentPath,
|
|
} : {}),
|
|
...(params.runtimeMount ? { runtimeMount: params.runtimeMount } : {}),
|
|
...(params.manifest.mount ? { serviceMount: params.manifest.mount } : {}),
|
|
...(params.environment?.environment.package?.publicRoot ? {
|
|
publicRoot: params.environment.environment.package.publicRoot,
|
|
} : {}),
|
|
...(params.runnerTransport ? { runnerTransport: params.runnerTransport } : {}),
|
|
...(params.webappRoute ? { webapp: params.webappRoute } : {}),
|
|
...hostPlacementMetadata(params.hostPlacement),
|
|
},
|
|
};
|
|
}
|
|
|
|
function hasIntrospectChildren(component: MatrixActor): boolean {
|
|
const constructor = component.constructor as { readonly hasDynamicChildren?: unknown };
|
|
if (constructor.hasDynamicChildren === true) {
|
|
return true;
|
|
}
|
|
try {
|
|
const introspection = component.on$introspect({ depth: 'basic' });
|
|
return Array.isArray(introspection.children) && introspection.children.length > 0;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function registerRuntimeInventoryClaims(
|
|
runtime: MatrixRuntime,
|
|
logicalRegistryMount: string,
|
|
manifest: ILoadedMatrixServiceManifest,
|
|
environment: ILoadedMatrixPackageEnvironment | undefined,
|
|
runtimeId: string,
|
|
runtimeWireRoot: string,
|
|
inventory: readonly IRuntimeInventoryEntry[],
|
|
_serviceMount: string | undefined,
|
|
webappRoute: IRunnerWebappRouteMetadata | undefined,
|
|
hostPlacement: IRunnerHostPlacement | undefined,
|
|
registryOptional: boolean,
|
|
): Promise<number> {
|
|
if (registryOptional) {
|
|
return 0;
|
|
}
|
|
const authorityRoot = environment?.environment.runtime.root
|
|
?? manifest.root
|
|
?? runtimeWireRoot;
|
|
const timeoutMs = controlPlaneRequestTimeoutMs(environment);
|
|
const requestOptions = controlPlaneRequestOptions(runtime, environment, timeoutMs);
|
|
let registered = 0;
|
|
for (const entry of inventory) {
|
|
const logicalMount = entry.mount.trim();
|
|
if (entry.placement.callable === false) {
|
|
continue;
|
|
}
|
|
if (!shouldRegisterRuntimeInventoryClaim(runtimeId, logicalMount)) {
|
|
continue;
|
|
}
|
|
await RequestReply.execute(
|
|
runtime.getRootContext(),
|
|
logicalRegistryMount,
|
|
'registry.claim',
|
|
{
|
|
mount: logicalMount,
|
|
kind: 'actor',
|
|
componentId: `${manifest.packageName}:inventory:${logicalMount}`,
|
|
componentType: entry.type ?? 'actor',
|
|
authorityRoot,
|
|
scope: 'identity-relative',
|
|
providerRuntimeId: runtimeId,
|
|
runtimeWireRoot,
|
|
localMount: logicalMount,
|
|
packageName: manifest.packageName,
|
|
mode: entry.placement.claimMode,
|
|
cardinality: entry.placement.cardinality,
|
|
claimMode: entry.placement.claimMode,
|
|
logicalMount: entry.placement.logicalMount,
|
|
physicalMount: entry.placement.physicalMount,
|
|
callable: entry.placement.callable,
|
|
...(entry.placement.standbyReason ? { standbyReason: entry.placement.standbyReason } : {}),
|
|
...(typeof entry.placement.placementEpoch === 'number' ? { placementEpoch: entry.placement.placementEpoch } : {}),
|
|
...registryClaimPlacement(hostPlacement),
|
|
ttlMs: 30_000,
|
|
metadata: {
|
|
source: 'runner-runtime-inventory',
|
|
placement: entry.placement,
|
|
...(entry.description ? { description: entry.description } : {}),
|
|
...(entry.hasChildren === true ? { hasChildren: true } : {}),
|
|
...hostPlacementMetadata(hostPlacement),
|
|
},
|
|
},
|
|
requestOptions,
|
|
);
|
|
registered++;
|
|
}
|
|
if (webappRoute) {
|
|
registered += await registerWebappSurfaceClaim(
|
|
runtime,
|
|
logicalRegistryMount,
|
|
manifest,
|
|
authorityRoot,
|
|
runtimeId,
|
|
runtimeWireRoot,
|
|
webappRoute,
|
|
hostPlacement,
|
|
requestOptions,
|
|
);
|
|
}
|
|
return registered;
|
|
}
|
|
|
|
async function registerWebappSurfaceClaim(
|
|
runtime: MatrixRuntime,
|
|
logicalRegistryMount: string,
|
|
manifest: ILoadedMatrixServiceManifest,
|
|
authorityRoot: string,
|
|
runtimeId: string,
|
|
runtimeWireRoot: string,
|
|
webappRoute: IRunnerWebappRouteMetadata,
|
|
hostPlacement: IRunnerHostPlacement | undefined,
|
|
requestOptions: IControlPlaneRequestOptions,
|
|
): Promise<number> {
|
|
const appName = webappRoute.appName.trim();
|
|
if (!appName) {
|
|
return 0;
|
|
}
|
|
const routePrefix = normalizeWebappRoutePrefix(webappRoute.routePrefix, appName);
|
|
const placement = placementForMount(
|
|
webappRoute.assetMount ?? appName,
|
|
appName,
|
|
'named-instance',
|
|
true,
|
|
);
|
|
const displayName = readOptionalString(webappRoute.displayName);
|
|
const icon = readOptionalString(webappRoute.icon);
|
|
const description = readOptionalString(webappRoute.description);
|
|
const shells = [...new Set((webappRoute.shells ?? []).map((entry) => entry.trim()).filter((entry) => entry.length > 0))];
|
|
await RequestReply.execute(
|
|
runtime.getRootContext(),
|
|
logicalRegistryMount,
|
|
'registry.claim',
|
|
{
|
|
mount: appName,
|
|
kind: 'app',
|
|
componentId: `${manifest.packageName}:webapp:${appName}`,
|
|
componentType: 'webapp',
|
|
authorityRoot,
|
|
scope: 'identity-relative',
|
|
providerRuntimeId: runtimeId,
|
|
runtimeWireRoot,
|
|
localMount: webappRoute.assetMount ?? appName,
|
|
packageName: manifest.packageName,
|
|
runtimeId,
|
|
packageId: manifest.packageName,
|
|
packageRef: manifest.packageName,
|
|
mode: placement.claimMode,
|
|
cardinality: placement.cardinality,
|
|
claimMode: placement.claimMode,
|
|
logicalMount: placement.logicalMount,
|
|
physicalMount: placement.physicalMount,
|
|
callable: placement.callable,
|
|
...registryClaimPlacement(hostPlacement),
|
|
ttlMs: 30_000,
|
|
app: {
|
|
route: routePrefix,
|
|
title: displayName ?? titleFromAppName(appName),
|
|
...(icon ? { icon } : {}),
|
|
surface: 'page',
|
|
openable: true,
|
|
appName,
|
|
},
|
|
metadata: {
|
|
source: 'runner-webapp-surface',
|
|
placement,
|
|
routeKind: webappRoute.assetMount ? 'matrix-asset-endpoint' : 'standalone-webapp',
|
|
...(description ? { description } : {}),
|
|
...(shells.length > 0 ? { shells } : {}),
|
|
...(typeof webappRoute.navOrder === 'number' && Number.isFinite(webappRoute.navOrder) ? { navOrder: webappRoute.navOrder } : {}),
|
|
...(webappRoute.assetMount ? { assetMount: webappRoute.assetMount } : {}),
|
|
...(webappRoute.origin ? { origin: webappRoute.origin } : {}),
|
|
...(typeof webappRoute.port === 'number' ? { port: webappRoute.port } : {}),
|
|
...hostPlacementMetadata(hostPlacement),
|
|
},
|
|
},
|
|
requestOptions,
|
|
);
|
|
return 1;
|
|
}
|
|
|
|
function controlPlaneRequestOptions(
|
|
runtime: MatrixRuntime,
|
|
environment: ILoadedMatrixPackageEnvironment | undefined,
|
|
timeoutMs: number,
|
|
): IControlPlaneRequestOptions {
|
|
const targetRoot = environment?.environment.runtime.root
|
|
?? readTransportRoot(runtime);
|
|
const protocolRequest = isHostManagedEnvironment(environment)
|
|
&& typeof environment?.environment.nats?.credentialsRef === 'string'
|
|
&& environment.environment.nats.credentialsRef.trim().length > 0;
|
|
return {
|
|
timeoutMs,
|
|
...(targetRoot ? { targetRoot } : {}),
|
|
...(protocolRequest ? { protocolRequest: true } : {}),
|
|
};
|
|
}
|
|
|
|
function registrationControlPlaneRequestOptions(
|
|
registration: Pick<IRunnerRuntimeRegistration | IRunnerBindingRegistration, 'controlTargetRoot' | 'protocolRequest'>,
|
|
timeoutMs: number,
|
|
): IControlPlaneRequestOptions {
|
|
return {
|
|
timeoutMs,
|
|
...(registration.controlTargetRoot ? { targetRoot: registration.controlTargetRoot } : {}),
|
|
...(registration.protocolRequest === true ? { protocolRequest: true } : {}),
|
|
};
|
|
}
|
|
|
|
function shouldPublishBindingForwards(environment: ILoadedMatrixPackageEnvironment | undefined): boolean {
|
|
return !isHostManagedEnvironment(environment);
|
|
}
|
|
|
|
function controlPlaneRequestTimeoutMs(environment: ILoadedMatrixPackageEnvironment | undefined): number {
|
|
return environment?.environment.nats?.credentialsRef
|
|
? LINKED_CONTROL_PLANE_REQUEST_TIMEOUT_MS
|
|
: LOCAL_CONTROL_PLANE_REQUEST_TIMEOUT_MS;
|
|
}
|
|
|
|
function normalizeWebappRoutePrefix(value: string | undefined, appName: string): string {
|
|
const routePrefix = value?.trim() || `/apps/${appName}/`;
|
|
return routePrefix.endsWith('/') ? routePrefix : `${routePrefix}/`;
|
|
}
|
|
|
|
function readOptionalString(value: string | undefined): string | undefined {
|
|
const trimmed = value?.trim() ?? '';
|
|
return trimmed.length > 0 ? trimmed : undefined;
|
|
}
|
|
|
|
function titleFromAppName(appName: string): string {
|
|
return appName
|
|
.split(/[-_.]/)
|
|
.filter(Boolean)
|
|
.map((segment) => `${segment.charAt(0).toUpperCase()}${segment.slice(1)}`)
|
|
.join(' ');
|
|
}
|
|
|
|
export function shouldRegisterRuntimeInventoryClaim(runtimeId: string, logicalMount: string): boolean {
|
|
const trimmedRuntimeId = runtimeId.trim();
|
|
const trimmedMount = logicalMount.trim();
|
|
if (!trimmedRuntimeId || !trimmedMount) {
|
|
return false;
|
|
}
|
|
const runtimeMount = `system.runtimes.${trimmedRuntimeId}`;
|
|
if (trimmedMount === runtimeMount || trimmedMount.startsWith(`${runtimeMount}.`)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function buildRuntimeClaims(
|
|
serviceMount: string | undefined,
|
|
environment: ILoadedMatrixPackageEnvironment | undefined,
|
|
): Array<{ prefix: string; kind: 'authoritative' }> {
|
|
const claims = new Set<string>();
|
|
const publicRoot = environment?.environment.package?.publicRoot?.trim();
|
|
if (publicRoot) {
|
|
claims.add(publicRoot);
|
|
}
|
|
const resolvedServiceMount = serviceMount?.trim();
|
|
if (resolvedServiceMount) {
|
|
claims.add(resolvedServiceMount);
|
|
}
|
|
return [...claims].map((prefix) => ({ prefix, kind: 'authoritative' as const }));
|
|
}
|
|
|
|
function deriveCanonicalBindings(
|
|
manifest: ILoadedMatrixServiceManifest,
|
|
environment: ILoadedMatrixPackageEnvironment | undefined,
|
|
serviceMount: string | undefined,
|
|
): ICanonicalBindingDefinition[] {
|
|
const bindings = new Map<string, ICanonicalBindingDefinition>();
|
|
const packageName = manifest.packageName;
|
|
const publicRoot = trimToUndefined(environment?.environment.package?.publicRoot)
|
|
?? trimToUndefined(manifest.packageNamespace);
|
|
const resolvedServiceMount = trimToUndefined(serviceMount)
|
|
?? trimToUndefined(manifest.mount);
|
|
const rootKind = trimToUndefined(manifest.serviceInstance.rootKind) ?? 'package-root';
|
|
|
|
if (rootKind === 'package-root' && manifest.packageRoot) {
|
|
const canonicalRoot = resolveCanonicalRootBinding(manifest.packageRoot, publicRoot);
|
|
const localRootMount = resolvedServiceMount
|
|
?? resolveLocalRootMount(manifest.packageRoot, publicRoot);
|
|
if (canonicalRoot && localRootMount) {
|
|
bindings.set(canonicalRoot, {
|
|
binding: canonicalRoot,
|
|
localMount: localRootMount,
|
|
ops: manifest.packageRoot.accepts,
|
|
packageName,
|
|
});
|
|
}
|
|
}
|
|
|
|
for (const declaration of manifest.packageComponents) {
|
|
const binding = resolveCanonicalComponentBinding(declaration, publicRoot);
|
|
const localMount = resolveLocalComponentMount(declaration, resolvedServiceMount, rootKind);
|
|
if (!binding || !localMount) {
|
|
continue;
|
|
}
|
|
bindings.set(binding, {
|
|
binding,
|
|
localMount,
|
|
ops: declaration.accepts,
|
|
packageName,
|
|
});
|
|
}
|
|
|
|
return [...bindings.values()];
|
|
}
|
|
|
|
function resolveCanonicalRootBinding(
|
|
declaration: ILoadedMatrixBindingDeclaration,
|
|
publicRoot: string | undefined,
|
|
): string | undefined {
|
|
if (isExplicitAbsoluteMount(declaration.mount)) {
|
|
return declaration.mount;
|
|
}
|
|
return publicRoot ?? declaration.mount;
|
|
}
|
|
|
|
function resolveCanonicalComponentBinding(
|
|
declaration: ILoadedMatrixBindingDeclaration,
|
|
publicRoot: string | undefined,
|
|
): string | undefined {
|
|
if (isExplicitAbsoluteMount(declaration.mount)) {
|
|
return declaration.mount;
|
|
}
|
|
if (!publicRoot) {
|
|
return undefined;
|
|
}
|
|
return `${publicRoot}.${declaration.mount}`;
|
|
}
|
|
|
|
function resolveLocalRootMount(
|
|
declaration: ILoadedMatrixBindingDeclaration,
|
|
publicRoot: string | undefined,
|
|
): string | undefined {
|
|
return declaration.mount
|
|
|| publicRoot;
|
|
}
|
|
|
|
function resolveLocalComponentMount(
|
|
declaration: ILoadedMatrixBindingDeclaration,
|
|
serviceMount: string | undefined,
|
|
rootKind: string,
|
|
): string | undefined {
|
|
if (isExplicitAbsoluteMount(declaration.mount)) {
|
|
return declaration.mount;
|
|
}
|
|
if (rootKind === 'package-root' && serviceMount) {
|
|
return `${serviceMount}.${declaration.mount}`;
|
|
}
|
|
return declaration.mount;
|
|
}
|
|
|
|
function isExplicitAbsoluteMount(mount: string): boolean {
|
|
return mount.includes('.');
|
|
}
|
|
|
|
function trimToUndefined(value: unknown): string | undefined {
|
|
if (typeof value !== 'string') {
|
|
return undefined;
|
|
}
|
|
const trimmed = value.trim();
|
|
return trimmed.length > 0 ? trimmed : undefined;
|
|
}
|
|
|
|
function readPlacementDeclarations(manifest: ILoadedMatrixServiceManifest): IPlacementDeclarationIndex {
|
|
const byMount = new Map<string, MatrixMountCardinality>();
|
|
const manifestRecord = readJsonObjectIfPresent(path.join(manifest.packageDir, 'matrix.json'));
|
|
if (!manifestRecord) {
|
|
return { byMount };
|
|
}
|
|
addPlacementDeclaration(byMount, manifestRecord.root);
|
|
const components = manifestRecord.components;
|
|
if (Array.isArray(components)) {
|
|
for (const component of components) {
|
|
addPlacementDeclaration(byMount, component);
|
|
}
|
|
}
|
|
return { byMount };
|
|
}
|
|
|
|
function addPlacementDeclaration(
|
|
byMount: Map<string, MatrixMountCardinality>,
|
|
value: unknown,
|
|
): void {
|
|
if (!isRecord(value)) {
|
|
return;
|
|
}
|
|
const mount = trimToUndefined(value.mount);
|
|
if (!mount) {
|
|
return;
|
|
}
|
|
const placement = isRecord(value.placement) ? value.placement : undefined;
|
|
const cardinality = readMountCardinality(placement?.cardinality);
|
|
if (cardinality) {
|
|
byMount.set(mount, cardinality);
|
|
}
|
|
}
|
|
|
|
function resolveCardinalityForMount(
|
|
declarations: IPlacementDeclarationIndex,
|
|
mount: string,
|
|
): MatrixMountCardinality {
|
|
return declarations.byMount.get(mount) ?? 'named-instance';
|
|
}
|
|
|
|
function placementForMount(
|
|
physicalMount: string,
|
|
logicalMount: string,
|
|
cardinality: MatrixMountCardinality,
|
|
callable: boolean,
|
|
): RuntimeInventoryPlacementMetadata {
|
|
return {
|
|
cardinality,
|
|
claimMode: claimModeForCardinality(cardinality),
|
|
logicalMount,
|
|
physicalMount,
|
|
callable,
|
|
};
|
|
}
|
|
|
|
function claimModeForCardinality(cardinality: MatrixMountCardinality): MatrixPlacementClaimMode {
|
|
if (cardinality === 'authority-singleton') {
|
|
return 'authoritative';
|
|
}
|
|
if (cardinality === 'queue-service') {
|
|
return 'pool-member';
|
|
}
|
|
if (cardinality === 'replicated-read') {
|
|
return 'mirror';
|
|
}
|
|
return 'local-only';
|
|
}
|
|
|
|
function readMountCardinality(value: unknown): MatrixMountCardinality | undefined {
|
|
return value === 'authority-singleton'
|
|
|| value === 'device-singleton'
|
|
|| value === 'device-local-control'
|
|
|| value === 'queue-service'
|
|
|| value === 'named-instance'
|
|
|| value === 'replicated-read'
|
|
? value
|
|
: undefined;
|
|
}
|
|
|
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
}
|
|
|
|
function readRecord(value: unknown): Record<string, unknown> | undefined {
|
|
return isRecord(value) ? value : undefined;
|
|
}
|
|
|
|
function resolveRunnerHostPlacement(
|
|
runtime: MatrixRuntime,
|
|
environment: ILoadedMatrixPackageEnvironment | undefined,
|
|
): IRunnerHostPlacement | undefined {
|
|
const hostHome = trimToUndefined(runtime.getRootContext().getService<unknown>('matrix-host-home'))
|
|
?? trimToUndefined(environment?.environment.host?.matrixDir);
|
|
if (!hostHome) {
|
|
return undefined;
|
|
}
|
|
|
|
const credentialsDir = path.join(hostHome, 'credentials');
|
|
const linkRecord = readJsonObjectIfPresent(path.join(credentialsDir, 'hivecast-link.json'));
|
|
const installRecord = readJsonObjectIfPresent(path.join(credentialsDir, 'hivecast-install.json'));
|
|
const hostId = trimToUndefined(linkRecord?.hostId) ?? trimToUndefined(installRecord?.installId);
|
|
if (!hostId) {
|
|
return undefined;
|
|
}
|
|
const hostName = trimToUndefined(linkRecord?.hostName) ?? trimToUndefined(installRecord?.hostName);
|
|
const deviceSlug = trimToUndefined(linkRecord?.deviceSlug);
|
|
return {
|
|
deviceId: hostId,
|
|
hostId,
|
|
...(hostName ? { hostName } : {}),
|
|
...(deviceSlug ? { deviceSlug } : {}),
|
|
};
|
|
}
|
|
|
|
function registryClaimPlacement(hostPlacement: IRunnerHostPlacement | undefined): Record<string, string> {
|
|
return hostPlacement
|
|
? {
|
|
deviceId: hostPlacement.deviceId,
|
|
hostId: hostPlacement.hostId,
|
|
}
|
|
: {};
|
|
}
|
|
|
|
function hostPlacementMetadata(hostPlacement: IRunnerHostPlacement | undefined): Record<string, string> {
|
|
return hostPlacement
|
|
? {
|
|
deviceId: hostPlacement.deviceId,
|
|
hostId: hostPlacement.hostId,
|
|
...(hostPlacement.hostName ? { hostName: hostPlacement.hostName } : {}),
|
|
...(hostPlacement.deviceSlug ? { deviceSlug: hostPlacement.deviceSlug } : {}),
|
|
}
|
|
: {};
|
|
}
|
|
|
|
function readJsonObjectIfPresent(filePath: string): Record<string, unknown> | undefined {
|
|
if (!fs.existsSync(filePath)) {
|
|
return undefined;
|
|
}
|
|
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8').replace(/^\uFEFF/, '')) as unknown;
|
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
throw new Error(`Expected JSON object at ${filePath}`);
|
|
}
|
|
return parsed as Record<string, unknown>;
|
|
}
|
|
|
|
async function ensureRegistryActor<TActor extends object>(
|
|
runtime: MatrixRuntime,
|
|
mount: string,
|
|
ActorClass: new () => TActor,
|
|
): Promise<boolean> {
|
|
if (runtime.hasComponent(mount)) {
|
|
return false;
|
|
}
|
|
await runtime.createSupervised(ActorClass as never, mount);
|
|
return true;
|
|
}
|