Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Manifest and Service Startup

The manifest is the boot package and init configuration. It names embedded binaries, the single kernel-launched init process, kernel boot parameters, and the init-owned service graph used by focused executor manifests.

Current Behavior

tools/mkmanifest requires the repo-pinned CUE compiler, evaluates system.cue, embeds declared binaries, validates binary references and the init-owned authority graph under initConfig, compiles supported generated launch-parameter descriptors from CUE, serializes SystemManifest, and places manifest.bin into the ISO. The kernel receives that file as the single Limine module. The diagram below is intentionally large: it separates the default init-owned boot path from the focused spawn-proof path.

flowchart TD
    Cue[system.cue or system-spawn.cue] --> Mkmanifest[tools/mkmanifest]
    Binaries[release userspace binaries] --> Mkmanifest
    Mkmanifest --> Manifest[manifest.bin SystemManifest]
    Manifest --> Limine[Limine boot module]
    Limine --> Kernel[kernel parse and validate]
    Kernel --> InitCaps[init CapTable and CapSet page]
    InitCaps --> Init[enter initConfig.init process]
    Init --> ShellPath[default system.cue: spawn shell/remote CapSet gateway/services]
    Init --> SpawnPath[focused system-spawn.cue: standalone init executor]
    SpawnPath --> BootPackage[BootPackage.readManifest chunks]
    BootPackage --> Plan[capos-config ManifestBootstrapPlan validation]
    SpawnPath --> Spawner[ProcessSpawner.spawn]
    Spawner --> Children[init-spawned child processes]
    Manifest --> Launch[per-binary typed launch envelopes]
    Launch --> Init
    Launch --> Spawner

SystemManifest.launchParameters is append-only per-binary startup data. CUE selects a generated descriptor by name and supplies its value; the compiler derives the schema ID from that generated type and serializes the payload. The kernel installs the bounded envelope in a fixed read-only, non-executable mapping before the first user instruction. capos-rt checks the mapping, envelope, schema ID, payload bound, and generated root type distinctly. The envelope contains no capabilities and cannot grant authority.

The initial descriptor set contains capos.EmptyLaunchParameters for native programs and capos.PosixLaunchParameters for bounded byte-string argv. ProcessSpawner, RestrictedLauncher, and RestrictedShellLauncher carry an optional envelope; an explicit request takes precedence over the manifest default. Broker-minted RestrictedLauncher capabilities retain the manifest revision that supplied their allowlist. resolveBinary returns the selected binary’s generated descriptor, defaulting descriptor-less native binaries to EmptyLaunchParameters; spawn rejects an explicitly supplied schema or size that does not match that descriptor before child publication. The native shell uses only this local resolver result. It accepts a descriptor-named params { ... } literal or conventional arguments as PosixLaunchParameters.argv sugar, encodes a bounded envelope, and submits it atomically with the explicit CapGrant list for both spawn and run. It has no SchemaRegistry or string-keyed environment channel. POSIX execve and posix_spawn submit the typed message directly. Legacy posix_argv and initConfig.init.posixArgs are read only when the typed envelope is absent; a present malformed or mismatched envelope never falls back.

The default manifest starts only initConfig.init from the kernel, and that process is now the standalone init ELF. Init receives the bootstrap authority needed to read BootPackage, validate initConfig.services, spawn the foreground shell, remote-session CapSet gateway, resident chat service, and other default services, then wait according to the manifest policy. The shell is an init-started service; it receives terminal, credential-store, session-manager, audit-log, and authority-broker caps, mints its own anonymous UserSession, and waits for an explicit login or setup command before upgrading. It never holds BootPackage or broad ProcessSpawner authority.

Focused shell-family manifests such as system-smoke.cue and system-shell.cue also put standalone init in initConfig.init; init launches capos-shell as a single service and exits when that service exits. The focused graphs preserve the shell’s narrow proof-specific grants without giving the shell BootPackage or broad ProcessSpawner authority.

That ownership shape covers the complete set migrated under the init mandate – Gate B there, which requires every boot manifest’s init binary to be init. The shell service is init-spawned under exitWhenServiceExits in manifests/system-smoke.cue, manifests/system-shell.cue, manifests/system-login.cue, manifests/system-login-setup.cue, the granted and ungranted manifests/system-shell-uptime.cue / manifests/system-shell-uptime-ungranted.cue and manifests/system-shell-fs.cue / manifests/system-shell-fs-ungranted.cue pairs, manifests/system-manual-smoke.cue, and manifests/system-local-users.cue. The same structure launches the former top-level proof binary as the named service in manifests/system-credential.cue, manifests/system-terminal.cue, manifests/system-revocable-read.cue, and manifests/system-memoryobject-shared.cue. In each graph, init holds Console, BootPackage, ProcessSpawner, and only the scoped grants it must forward; the service receives the proof-specific capability set that the former top-level binary held. The unresolved validation rule for non-init selectors and the classification of the remaining direct-boot proof manifests belong to the init-mandate scope task.

This is the Gate B migration inventory, not a complete inventory of init-owned manifests. For example, manifests/system-shell-log.cue, manifests/system-shell-log-ungranted.cue, manifests/system-shell-unbounded-session.cue, and manifests/system-restricted-shell-launcher.cue were already init-owned and therefore do not appear in the migration list.

The focused system-spawn.cue manifest still puts the standalone init ELF in initConfig.init. There, init receives ProcessSpawner, a read-only BootPackage cap, and Console. It reads bounded manifest chunks into a metadata-only capos-config::ManifestBootstrapPlan, validates binary references, authority graph structure, exports, cap sources, and interface IDs, then spawns the focused smoke services. Low-level spawn grants still model receiver selectors for hostile and compatibility proofs, but normal shell client @... grants omit selector syntax and preserve delegated client endpoint identity. Raw parent-capability grants must preserve the source hold metadata, endpoint-client grants may mint selectors only from an endpoint owner or a ProcessSpawner-returned parent endpoint facet without widening it to server authority, and kernel-source Endpoint, FrameAllocator, VirtualMemory, Timer, ThreadControl, ThreadSpawner, and EntropySource grants mint fresh child-local caps without receiver selectors. QEMU-only PersistentStore grants mount the root store through the same child-local kernel-source path when a focused proof manifest names that source. Endpoint kernel grants also return parent-side client facets as ProcessSpawner result caps so init can wire later service-sourced imports without ever holding child endpoint owner caps.

mkmanifest cue-to-capnp is the adjacent general conversion path for CUE-authored data that should not become part of SystemManifest. It evaluates the input with the same pinned CUE compiler, package mode, tag injection, and CAPOS_CUE_TAGS handling as the manifest path, then passes the exported JSON to the pinned Cap’n Proto compiler through capnp convert json:binary. The caller supplies the .capnp schema file, root struct type, output path, and optional Cap’n Proto import paths. This is schema-aware serialization for data messages rooted at arbitrary specified structs; it is not a live capability or interface-object serialization path.

Design

Manifest validation has three layers:

  • Kernel bootstrap references: binary names are unique, initConfig.init.binary resolves, referenced payloads are non-empty, and init kernel cap sources match their expected interface IDs.
  • Init-owned binary references: initConfig.services[*].binary references resolve before the executor spawns children.
  • Init-owned authority graph: service names, cap names, export names, and service-sourced references are unique and resolvable; re-exporting service-sourced caps is rejected.
  • Init-owned cap sources: expected interface IDs match kernel sources or declared service exports.

Kernel startup now resolves only initConfig.init.caps. Init performs service execution in two userspace passes. The preflight pass walks initConfig.services in manifest order, resolves kernel and service-sourced caps against init grants and prior exports, and rejects an unstartable graph before spawning children. The spawn pass grants caps in declaration order, records declared exports, keeps owned parent client facets for exported child endpoints, and attenuates endpoint exports to client-only facets for importers. After every child is spawned, init drops and flushes those parent facets before waiting on children; a dropped init facet therefore cannot owner-cancel queued, pending, or in-flight child endpoint state.

initConfig.services currently supplies service name, binary, restart policy, and capability grants, but no explicit resource-profile grant. ProcessSpawner resolves policy from caller session context and may use defaults for an empty or unknown profile. That is not fail-closed service quota binding and does not create hierarchical process/subtree credit. The target contract is an explicit validated profile or attenuated resource grant per service, with effective limits enforced at each resource owner; see Resource Governance.

Capability expectation diagnostics

ServiceRevision.grantSlots is package/build-time conformance metadata, not receiver authority and not a launch predicate. The tools/capability-expectations analyzer consumes the generated Cap’n Proto ServiceRevision wire type directly and walks Rust syntax trees for capset().get* calls. It resolves byte literals and referenced byte-string constants, obtains client interface types from turbofish arguments or an unambiguous annotated binding, and reports unresolved names or types explicitly. It checks declared numeric interface IDs against the generated schema IDs known to capos-config; the numeric ID remains the wire identity. Missing, unused, and interface mismatch results are warnings only. Optional unused slots do not produce a required-slot warning. One analysis is bounded to 1,024 Rust files, 1 MiB per file, 4,096 uses, and 256 grant slots. Coverage ends at the immutable bootstrap CapSet; result capabilities, endpoint-delivered authority, and later grants are outside the inventory.

The analyzer is the schema-bound consumer. Manifest packaging can emit one deterministic, generated Cap’n Proto ServiceRevision artifact per declared service from the package’s validated grant slots, and the host package check feeds the emitted artifact to the analyzer. Unknown interface identities stay unresolved in the artifact and visible as warnings. These artifacts are strictly diagnostic: the boot manifest does not carry or read them, and they do not participate in launch authorization, grant or attenuate authority, or refuse a launch.

At runtime, a missing lookup carries the bounded requested name and a lazy, bounded view of the available name/interface inventory, so ordinary absence probes do not copy diagnostic entries. Startup-failure publication materializes that inventory only when requested. A mistyped lookup also retains the requested name. Runtime::report_startup_failure and Runtime::fail_startup publish either error through a fixed bounded bootstrap syscall record that does not depend on an application capability. ProcessHandle.lifecycleStatus retains and exposes the typed record before and after exit, so a missing console or StdIO grant cannot suppress its own launcher-visible diagnosis. Reporting does not authorize, attenuate, grant, or refuse launch.

Invariants

  • The manifest is schema data plus an init config tree, not shell script or ambient namespace.
  • Launch parameters are bounded data, not authority; capabilities travel only through validated grant slots.
  • A present typed envelope must match the decoder’s generated schema ID and decode successfully. Invalid typed input never falls back to legacy input.
  • Omitted cap sources fail closed.
  • Cap names within one service are unique and are the names userspace sees in CapSet.
  • Service exports must name caps declared by the same service.
  • Service-sourced imports must reference a declared service export.
  • Endpoint exports to importers must be attenuated to client-only facets.
  • Init must not hold endpoint owner caps for child-local manifest endpoints.
  • expectedInterfaceId checks compatibility; it is not the authority selector.
  • Profile names and service metadata select policy; only resource-owner ledgers or generation-bound grants authorize quantitative spend. Unknown service profile references must fail before child publication once binding lands.
  • Legacy receiver metadata travels with cap-table hold edges and endpoint invocation metadata. Spawn-time client endpoint minting may carry the requested child selector only from owner or trusted parent endpoint result sources instead of copying the parent’s hold selector. Client facets received through ordinary spawn grants are not selector-minting authority for later spawns. Caller-selected endpoint badges are transitional compatibility state; session-bound invocation context plus broker-granted service roots/facets is the target shared-service authority model.

Code Map

  • schema/capos.capnp - SystemManifest, launch envelopes and generated parameter roots, NamedBlob, SystemConfig, KernelCapSource, and generic CueValue storage for initConfig.
  • capos-config/src/launch_parameters.rs - descriptor IDs derived from generated readers, common bounds, and descriptor-specific encoders.
  • capos-rt/src/client.rs, shell/src/lib.rs, and shell/src/main.rs - restricted-launcher descriptor resolution, shell literal/argument parsing, descriptor validation, and atomic spawn submission.
  • capos-config/src/manifest.rs - manifest structs, initConfig CUE parsing, capnp encode/decode, metadata-only ManifestBootstrapPlan, and schema-version storage.
  • capos-config/src/validation.rs - kernel bootstrap, init-owned graph, binary-reference, and capability-source validation policy.
  • tools/mkmanifest/src/lib.rs and tools/mkmanifest/src/main.rs - host-side manifest build pipeline, binary embedding, and general CUE-to-Cap’n Proto data-message conversion.
  • kernel/src/main.rs - kernel manifest module parse and validation.
  • kernel/src/cap/mod.rs - bootstrap cap creation and CapSet entry construction for init.
  • kernel/src/cap/boot_package.rs - read-only manifest-size and chunked manifest-read capability.
  • kernel/src/cap/process_spawner.rs - init-callable spawn path for packaged boot binaries and manifest/default launch-parameter selection.
  • capos-rt/src/entry.rs and capos-rt/src/client.rs - typed startup decoder and ProcessSpawner request encoding, bounded startup-failure publication, and ProcessHandle lifecycle decoding.
  • tools/capability-expectations/ - diagnostic-only bootstrap CapSet source inventory and ServiceRevision grant-slot comparison.
  • init/src/main.rs - BootPackage manifest reader, graph preflight, generic spawn loop, hostile spawn checks, and child waits.
  • system.cue and manifests/system-spawn.cue - default init-owned login/service graph and focused init-owned spawn manifests using initConfig.

Validation

  • cargo test-config validates manifest decode, CUE conversion, graph checks, source checks, and binary reference checks.
  • cargo test-mkmanifest validates host-side manifest conversion, embedded binary handling, pinned CUE path/version checks, pinned Cap’n Proto path/version checks, and schema-aware JSON-to-binary conversion through capnp convert when CAPOS_CAPNP is available.
  • make run-smoke validates the focused init-owned scripted login manifest: standalone-init boot from system-smoke.cue, init-spawned capos-shell, failed-auth redaction, successful password auth, broker-issued shell launch, terminal isolation, and clean halt.
  • make run is the operator-facing interactive boot path with the terminal UART on stdio and console/debug output logged separately.
  • make run-spawn validates the narrower system-spawn.cue graph: the kernel boot-launches only standalone init, init validates BootPackage metadata, ProcessSpawner launches each focused child service, grants Timer to the timer smokes, and init waits for them.
  • make generated-code-check validates schema-generated Rust stays in sync.
  • make capability-expectations-test validates the bounded syntax inventory and warning-only ServiceRevision comparison on the host.
  • make capability-expectations-package-check packages the focused diagnostic service, refuses stale ServiceRevision artifact sets, consumes the emitted artifact, and fails if the focused source comparison reports any warning.
  • make test-capability-expectation-diagnostics proves missing, mistyped, optional, and dynamically named bootstrap grants through launcher-observed lifecycle status without granting console or StdIO to the failing children.
  • make test-typed-launch-parameters proves manifest-selected native parameters are decoded by an init-spawned timer-smoke; make test-posix-args-smoke proves manifest-compiled POSIX argv; and make test-posix-execve-inherit-smoke proves explicit exec argv.

Open Work

  • The shell-family focused manifests are init-owned. The init-mandate scope task still needs to define whether self-contained direct-boot proof manifests such as system-storage-persist.cue, system-wasi-env.cue, and the scheduler CPU-isolation manifests remain an explicit class or migrate behind init before a loader or mkmanifest guard can be specified.
  • Service object identity migration still needs to retire caller-selected endpoint badge syntax from normal manifest paths. Normal shell paths already reject explicit client-grant selector syntax; low-level hostile fixtures and manifest-scoped non-identity encodings such as TCP listen ports remain separate cases.