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.binaryresolves, referenced payloads are non-empty, and init kernel cap sources match their expected interface IDs. - Init-owned binary references:
initConfig.services[*].binaryreferences 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.
expectedInterfaceIdchecks 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 genericCueValuestorage forinitConfig.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, andshell/src/main.rs- restricted-launcher descriptor resolution, shell literal/argument parsing, descriptor validation, and atomic spawn submission.capos-config/src/manifest.rs- manifest structs,initConfigCUE parsing, capnp encode/decode, metadata-onlyManifestBootstrapPlan, 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.rsandtools/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.rsandcapos-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.cueandmanifests/system-spawn.cue- default init-owned login/service graph and focused init-owned spawn manifests usinginitConfig.
Validation
cargo test-configvalidates manifest decode, CUE conversion, graph checks, source checks, and binary reference checks.cargo test-mkmanifestvalidates 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 throughcapnp convertwhenCAPOS_CAPNPis available.make run-smokevalidates the focused init-owned scripted login manifest: standalone-init boot fromsystem-smoke.cue, init-spawnedcapos-shell, failed-auth redaction, successful password auth, broker-issued shell launch, terminal isolation, and clean halt.make runis the operator-facing interactive boot path with the terminal UART on stdio and console/debug output logged separately.make run-spawnvalidates the narrowersystem-spawn.cuegraph: the kernel boot-launches only standaloneinit, init validates BootPackage metadata, ProcessSpawner launches each focused child service, grants Timer to the timer smokes, and init waits for them.make generated-code-checkvalidates schema-generated Rust stays in sync.make capability-expectations-testvalidates the bounded syntax inventory and warning-only ServiceRevision comparison on the host.make capability-expectations-package-checkpackages 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-diagnosticsproves 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-parametersproves manifest-selected native parameters are decoded by an init-spawnedtimer-smoke;make test-posix-args-smokeproves manifest-compiled POSIX argv; andmake test-posix-execve-inherit-smokeproves 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.