// The capability registry: one declarative entry per capability, so that // adding a new hardware kind touches THIS file (plus, at most, one probe or // effect implementation registered against it) and nothing else. Placement, // the proto, and the conversions are name-blind already; the consumers that // historically switched on hardware names — worker inventory probes, the // compose hardware overlay, container-user gid substitution, sideload // selection — are expected to iterate this registry instead. Contract // (norsk-mgr-deploy docs/design/placement-requirements.md §8): no // per-capability proto fields, no capability-name switches outside registry // consumers, no per-capability form components. import { KnownCapabilities, KnownCapabilityName } from "./knownCapabilities"; /** * How claims against this capability behave. * - "countable": N interchangeable units, count-exclusive (v1 semantics). * - "fractional": shareable by capacity; today only the GPU pool models * this (`GpuResource`), and the capability entry covers the whole-device * exclusive claim alongside it. * - "identity": entries are physically distinct and non-interchangeable — * one advertised entry per device, distinguished by attributes (a capture * card is wherever its cable is plugged in). * * @public */ export type CapabilityKind = "countable" | "fractional" | "identity"; /** * How a worker discovers this capability. Declarative device-node globs * cover the simple presence checks; anything richer registers a named * probe implementation on the worker side. Capabilities with no probe are * declared in worker config — operational knowledge (which NIC sits on * the media VLAN) that probing cannot know. * * @public */ export type CapabilityProbe = | { kind: "device-nodes"; globs: string[] } | { kind: "named"; id: string }; /** * What granting this capability does to a launch — the declarative half of * the old per-name switches. The launch machinery interprets these fields * generically; CDI-vs-legacy GPU delivery stays a *mode* inside the one * nvidia entry rather than a reason for a second vocabulary. * * @public */ export interface CapabilityLaunchEffect { /** Plain compose `devices:` mappings, e.g. "/dev/netint:/dev/netint". */ composeDevices?: string[]; /** CDI device names when the engine supports CDI, e.g. "nvidia.com/gpu=all". */ cdiDevices?: string[]; /** Fall back to the docker `deploy.resources.reservations` GPU request * when CDI is unavailable (the pre-CDI nvidia runtime path). */ legacyGpuReservation?: boolean; /** Host group whose gid the container user must carry to reach the * device (Quadra's /dev/netint* is group `disk`). */ gidGroup?: string; /** Sideload bundle this hardware wants alongside the product (e.g. CUDA). */ sideloadKey?: string; } /** @public */ export interface CapabilityDefinition { name: KnownCapabilityName; kind: CapabilityKind; /** Human-facing name for requirement selectors and inventory views. */ displayName: string; /** Attribute keys this capability conventionally carries — schema-lite, * for form rendering and validation hints, not enforcement. */ attributeKeys?: string[]; /** Absent = never probed; the worker's config declares it or a * NodeService provides it. */ probe?: CapabilityProbe; launchEffect?: CapabilityLaunchEffect; } /** * One entry per known capability. Growth happens here. * * @public */ export const CapabilityRegistry: Record = { [KnownCapabilities.NvidiaGpu]: { name: KnownCapabilities.NvidiaGpu, kind: "fractional", displayName: "NVIDIA GPU", attributeKeys: ["model"], probe: { kind: "device-nodes", globs: ["/dev/nvidia0"] }, launchEffect: { cdiDevices: ["nvidia.com/gpu=all"], legacyGpuReservation: true, sideloadKey: "cuda", }, }, [KnownCapabilities.IntelQsv]: { name: KnownCapabilities.IntelQsv, kind: "countable", displayName: "Intel Quick Sync", probe: { kind: "device-nodes", globs: ["/dev/dri/renderD*"] }, launchEffect: { composeDevices: ["/dev/dri:/dev/dri"] }, }, [KnownCapabilities.Quadra]: { name: KnownCapabilities.Quadra, kind: "countable", displayName: "Netint Quadra", probe: { kind: "device-nodes", globs: ["/dev/nidx0", "/dev/netint0"] }, launchEffect: { composeDevices: ["/dev/netint:/dev/netint"], gidGroup: "disk", }, }, [KnownCapabilities.Decklink]: { name: KnownCapabilities.Decklink, kind: "identity", displayName: "Blackmagic DeckLink", // cardIndex/profile identify the physical port layout; `signal` is the // operator's label for what the cable carries ("stadium-cam-3") — the // label-targeting handle, unique by operator discipline. attributeKeys: ["cardIndex", "model", "profile", "signal"], probe: { kind: "named", id: "decklink-enumerate" }, }, [KnownCapabilities.St2110Nic]: { name: KnownCapabilities.St2110Nic, kind: "identity", displayName: "ST 2110 NIC", // Declared in worker config, never probed: which NIC is on the media // VLAN / PTP domain is operational knowledge. attributeKeys: ["interface", "ptpDomain", "vlan"], }, [KnownCapabilities.Mxl]: { name: KnownCapabilities.Mxl, kind: "countable", displayName: "MXL slot", // Provided dynamically by the MXL bridge NodeService, not probed. }, }; /** Every registry entry, for consumers that iterate. @public */ export function allCapabilityDefinitions(): CapabilityDefinition[] { return Object.values(CapabilityRegistry); }