Device Mount Pattern
Synthetic devices (containerFS, future factotum, compose) are mounted during namespace construction via buildNamespace(), not ad-hoc in boot paths.
How It Works
buildNamespace() accepts NamespaceBuildOpts.devices which extends the existing DeviceSpec with optional device fields:
interface DeviceSpec { // Base devices (always available) dev?: boolean // /dev proc?: boolean // /proc srv?: boolean // /srv user?: string // /dev/user tty?: 'inherit' | 'none'
// Synthetic devices (opt-in) container?: boolean | ContainerDeviceOpts // /dev/container + CLI bins // Future: factotum, compose, etc.}When a device field is truthy, buildNamespace() creates the device’s fileserver, mounts it, and registers any associated bins — all within the canonical namespace construction sequence.
Adding a New Device
To add a new synthetic device (e.g., factotum for M4):
1. Define the opts type
export interface FactotumDeviceOpts { readonly authProvider?: AuthProvider}Add it to DeviceSpec:
export interface DeviceSpec { // ... existing fields ... readonly factotum?: boolean | FactotumDeviceOpts}2. Wire in buildNamespace()
Add a step after the existing device steps (15b, 15c, etc.):
// Step 15c: [DEVICE] factotum — /dev/factotum + auth binslet factotumManager: FactotumManager | undefinedif (devices.factotum !== undefined && devices.factotum !== false) { const opts = typeof devices.factotum === 'object' ? devices.factotum : {} const { createFactotum } = await import('../auth/factotum') const fm = createFactotum(opts) mounts.set('/dev/factotum', fm.server) for (const [name, fn] of Object.entries(fm.bins)) { registerExec(name, fn) } factotumManager = fm.manager}3. Expose on InterpretedNamespace
export interface InterpretedNamespace { // ... existing fields ... readonly factotumManager: FactotumManager | undefined}4. Consume in RootContainer or boot paths
const ns = await buildNamespace(ctx, caps, { devices: { factotum: { authProvider: myProvider } },})// ns.factotumManager is available for higher-level APIDesign Principles
-
Single construction site. All device wiring happens in
buildNamespace(). Boot paths andRootContainer.start()both call it — no inline wiring. -
Lazy imports. Device modules are dynamically imported to avoid circular dependencies at module load time.
-
Opt-in, not default.
bootFromImage()does not wire devices unless explicitly requested.RootContainer.start()opts in via itsNamespaceBuildOpts. -
Type-safe access. Each device exposes its manager on
InterpretedNamespaceso consumers can interact with it after construction. -
Deterministic ordering. Devices are mounted in a fixed sequence (step 15b, 15c, …) before union binds (step 16). Mount order is load-bearing.