# Executable Images and Service Launch Authority

capOS needs to install and replace independently built services without turning
the kernel into a package manager or treating a string in the boot manifest as
program authority. This proposal separates four concerns that the current
`ProcessSpawner` path combines:

- immutable executable identity;
- permission to construct a process;
- authority and resource policy for one service role; and
- package-generation, deployment, and rollback policy.

The manifest-builtin binary registry is a bootstrap mechanism, not the target
system package namespace. Normal services should ultimately come from
authenticated installed generations. Only the minimum code needed to reach and
repair those generations belongs in the recovery floor.

## Status and Scope

This is future design. The following foundations exist:

- `ProcessSpawner.spawn(name, binaryName, grants)` constructs isolated native
  processes from a boot-time binary set and returns a `ProcessHandle` plus a
  child-scoped `CapabilityManager`.
- Resident boot binaries are immutable `Arc<[u8]>` snapshots. The ISO path
  reads a bounded extent and verifies its manifest-committed SHA-256 digest
  before ELF loading.
- `MemoryObject` provides mapped page-backed storage, but its mappings are
  mutable and therefore are not sealed executable identity.
- the installable-system path persists content-addressed configuration
  generations and active/candidate/known-good rollback pointers; persistent
  service-binary generation roots are not implemented;
- `ProcessControl.retargetCaps` can preserve endpoint identity across a
  stateless replacement when the old process is already quiesced, but
  readiness, quiesce, graceful drain, state migration, and complete successor
  handoff remain future work.

This proposal does not claim production package signing, executable-image
capabilities, runtime deployment, or live service replacement already works.
It specifies the authority boundary those implementations should converge on.

## Current Misalignment

The current `ProcessSpawner` is a valid broad capability: possession of it is
explicit authority, and a capability may legitimately expose a parameterized
factory. The problem is delegation breadth, not the existence of parameters.
One invocation currently lets the holder choose:

1. any `binaryName` in the spawner's attached boot-wide registry;
2. an arbitrary list of supported `CapGrant` records;
3. supported child-local `KernelCapSource` objects; and
4. a child lifecycle handle plus post-spawn capability-table management.

That surface is appropriate only for a root constructor held by init or a
trusted supervisor. It is too broad to represent permission to launch one
service. Recursively granting `ProcessSpawner` also copies the same boot-wide
binary selection authority into another process.

The current string selector has a second problem: a human-readable name is not
stable code identity. A later boot image may bind the same name to different
bytes. That is insufficient for audit, rollback, update policy, or reasoning
about which code a capability authorized.

The existing `RestrictedLauncher` direction demonstrates that launch authority
can be narrowed by binary allowlists, fixed sessions, and bounded grant shapes.
The durable design should generalize that attenuation model while replacing
the boot-wide name registry with exact executable capabilities.

## Authority Decomposition

The durable model has four distinct objects. They may share implementation
state, but their authority must not be collapsed.

### `ExecutableImage`: exact code identity

An `ExecutableImage` capability authorizes nomination of one immutable native
image for process construction. It identifies exact bytes, not a package name,
service role, filesystem path, or update channel.

Each published object records at least:

- a cryptographic content digest;
- byte length;
- architecture and capOS ABI identity;
- validated ELF entry point, segment, TLS, and page-permission metadata;
- the resource charge for its immutable backing; and
- optional admission metadata, such as the release-root identifier that
  authorized the image.

Different content generations are different executable-image objects. Two
objects may contain identical bytes while carrying different admission
provenance; the content digest establishes integrity and deduplication identity,
while the surrounding package and launch policy establishes authorization.

Possessing an `ExecutableImage` is not, by itself, permission to create a
process. Process construction requires a separate constructor or launch-plan
capability. Conversely, a constructor cannot choose code the caller has not
supplied through an executable-image capability.

### `ServiceRevision`: exact role configuration

A `ServiceRevision` is an immutable userspace policy object that binds:

- one exact `ExecutableImage`;
- a stable service-role identifier;
- a fixed launch/grant template;
- an ABI/schema compatibility declaration;
- a resource profile and instance budget;
- readiness and health contracts;
- the result facets visible to the launch caller; and
- package, provenance, and update-channel metadata.

Those metadata fields are descriptive, not self-authenticating. A production
revision is valid only when an authenticated `SystemGeneration` names it and
the generation admission path derives the revision capability. A development
image sealed under a local unsigned policy cannot cross into a production
controller merely by copying production-looking provenance fields; it must be
re-admitted through the production generation policy.

The same executable image may participate in multiple revisions. A shell
binary launched for an operator and the same bytes launched for a guest are
different authority configurations. An interpreter image and the script or
Wasm payload it executes are also separate authorities.

This is why "one binary equals one launch capability" is incomplete. Exact
binary identity should be a capability, but the delegated permission to run a
service must also constrain its role, grants, session, resources, multiplicity,
and management surface.

### `LaunchPlan`: delegated instance creation

A `LaunchPlan` (or `ServiceLauncher`) is an attenuated userspace-served
capability derived from one admitted `ServiceRevision`. It is the authority an
ordinary supervisor or runtime caller uses to create an instance. It fixes the
revision, grant-slot schema, session and resource ceilings, multiplicity,
restart-rate budget, and result facets. Callers may fill only the dynamic
binding slots declared by the plan.

The plan implementation retains whatever root `ProcessConstructor`, image,
and child-management facets it needs. A plan holder does not receive those
facets and cannot replace the image, author arbitrary grants, mint arbitrary
kernel sources, or obtain a general child `CapabilityManager`.

### `ServiceController`: mutable lifecycle authority

A `ServiceController` (or service slot) is a userspace-served capability for
one stable service identity. It may stage an authorized `ServiceRevision`,
start a candidate, check readiness, activate it, retain the prior generation,
and roll back. It owns the process handles and any child capability manager
needed to implement that protocol.

The controller is not a kernel-global service registry and is not the stable
identity clients invoke. Stable client identity remains the endpoint object
preserved by `ProcessControl.retargetCaps`. A controller only owns lifecycle
policy for the processes serving those endpoints.

## Kernel Mechanism

The kernel should retain final ELF validation, address-space construction,
cap-table construction, W^X enforcement, resource charging, and transactional
process publication. Moving those operations wholesale into userspace would
grant a loader authority to assemble another process's page tables, initial CPU
state, ring, CapSet, and capability table; the kernel would still need to
revalidate nearly every result.

The proposed low-level interfaces are conceptually:

```capnp
interface ExecutableImageFactory {
    # Snapshot bytes from a caller-held MemoryObject, verify the expected
    # digest, validate the immutable snapshot as a capOS executable, and
    # return a newly published ExecutableImage capability.
    sealMemory @0 (
        sourceMemoryCapId :UInt32,
        length :UInt64,
        expectedSha256 :Data
    ) -> (imageIndex :UInt16);
}

interface ExecutableImage {
    # Report immutable code identity and validated load metadata.
    info @0 () -> (
        sha256 :Data,
        size :UInt64,
        architecture :Text,
        abi :Text
    );
}

interface ProcessConstructor {
    # Construct a child from the exact caller-held ExecutableImage. This is a
    # root mechanism for init and trusted launch-plan implementations, not an
    # ordinary application capability.
    spawnImage @0 (
        name :Text,
        imageCapId :UInt32,
        grants :List(CapGrant)
    ) -> (
        handleIndex :UInt16,
        capabilityManagerIndex :UInt16
    );
}
```

This sketch follows the current manual-dispatch convention: a method that
names another capability in the caller's table passes a local `CapId`, and
result capabilities arrive through bounded CQE transfer records. The exact
schema is not frozen by this proposal.

The initial synchronous contract is hard-bounded to
`MAX_EXECUTABLE_IMAGE_BYTES = 4 MiB`, matching the current spawn ceiling,
`MAX_EXECUTABLE_PROGRAM_HEADERS = 128`, and
`MAX_EXECUTABLE_LOAD_SEGMENTS = 64` with at most one TLS segment. `length` and
every ELF offset/count conversion must be checked before allocation or
iteration. These are admission limits, not promises that the eventual package
format can never carry larger programs.

`ProcessConstructor` deliberately remains broad. Ordinary callers should see
a narrower userspace `LaunchPlan` or `ServiceLauncher` interface instead. That
interface accepts only declared dynamic bindings and returns only the lifecycle
or service facets authorized by the plan.

Pipe allocation is independent authority and should eventually move from
`ProcessSpawner.createPipe` to a separate `PipeFactory` capability. POSIX
subprocess code can then receive pipe construction without receiving general
process construction.

## Sealing and Immutability

The factory must validate and load the same immutable snapshot. Validation of
a mutable `MemoryObject`, `File`, or userspace-served `Store` followed by a
later read would create a validation-to-use race.

The initial implementation should:

1. validate the caller's source range and expected digest shape;
2. reserve the complete image-page and metadata charge;
3. copy the source range into fresh kernel-owned pages;
4. hash and parse that snapshot;
5. reject invalid architecture, ABI, ELF bounds, permissions, or digest;
6. publish the `ExecutableImage` capability only after all checks succeed; and
7. roll back every reservation and page allocation on every failure.

The source session's executable-ingress ledger owns the snapshot pages,
validated segment metadata, and sealing work reservation until publication;
the published image retains that charge or transfers it atomically to an
explicit repository budget. The factory rejects a request before copying when
the byte, page, header, segment, metadata, or work reservation cannot be made.

The 4 MiB snapshot copy, SHA-256 pass, and bounded parse may run synchronously
only after a worst-case timing proof shows they fit the kernel dispatch budget.
If that proof does not hold, the interface must become a staged seal job: it
first obtains an immutable snapshot through exclusive source consumption or
write-map revocation, then advances hashing and parsing under fixed per-quantum
page/header work limits, and publishes only from `finish`. It must not yield
while retaining a merely mutable source reference.

Hostile proofs must cover an oversized source, excessive program headers and
load segments, every checked-conversion boundary, exhausted page/metadata/work
budgets, source mutation attempts, and rollback after reservation, snapshot,
hash, parse, image insertion, and result-cap transfer failures.

The backing must be page-accounted through the existing resource-ledger model.
A multi-megabyte `Arc<[u8]>` charged only to the kernel heap is not an acceptable
long-term cache. Immutable executable pages may later be shared read-only
between processes or deduplicated by digest, but sharing must not bypass the
ledger of record.

Releasing one image capability removes only that local reference; outstanding
copies remain usable. Object-wide prevention of future launches requires an
explicit generation/repository epoch or revoker facet checked by launch plans.
Neither local release nor epoch revocation terminates processes already created
from the image; termination and replacement require process-lifecycle
authority. Audit and crash records should retain the image digest and
service-revision identity after references are released or revoked.

## Package Ingestion and Persistent Generations

Files, Store objects, boot media, network downloads, and development uploads
are artifact sources, not executable authority. A userspace package importer
is responsible for:

- bounded artifact acquisition;
- chunk assembly and content hashing;
- signature and publisher verification;
- provenance and release-channel policy;
- architecture and ABI admission;
- anti-rollback epoch checks; and
- requesting sealing only after policy succeeds.

The current Store blob ceiling is smaller than a normal statically linked ELF,
so persistent packages require a chunked or Merkle-rooted artifact format. A
package root should bind chunk hashes, total length, executable digest, ABI,
and provenance metadata. Hashes prove integrity; accepted signatures and local
policy establish authenticity.

Installed software should use one authenticated immutable `SystemGeneration`
umbrella root. It atomically binds:

- the `SystemConfigOverlay` or successor configuration root;
- stable service-role identifiers to exact service revisions and package roots;
- architecture and ABI admission policy;
- release/signature provenance; and
- one monotonic release epoch.

Configuration and package roots remain separate content-addressed objects, but
activation pointers never select them independently. The existing
configuration-only generation is a compatibility predecessor; it must not
silently become an unversioned package database or a second independently
movable half of system state.

Candidate, active, known-good, and retained rollback pointers each name a whole
umbrella root. Staging validates the root signature and its complete transitive
hash closure before recording a candidate. Before starting any candidate
process, boot records the attempt against that same root. Generation validation
then authorizes exact `ServiceRevision` objects and derives their launch plans;
an image digest or provenance string outside that admission chain is
insufficient.

On boot, init or a recovery-owned package service will:

1. mount the persistent Store and configuration region;
2. select and authenticate the active or recovery generation;
3. resolve and assemble its executable artifacts;
4. seal exact executable images;
5. derive the generation's launch plans;
6. start services and reach their health checkpoints; and
7. confirm the candidate generation only after the required system health
   floor is running, atomically promoting that root to active and known-good.

A failed candidate clears or marks only its candidate/attempt record and
reactivates the prior whole known-good root. The anti-rollback acceptance floor
does not advance merely because a candidate boots. An authorized promotion may
advance it only after policy explicitly abandons every retained rollback root
below the new floor; otherwise known-good fallback would reject itself.
Garbage collection must retain every object reachable from active, candidate,
known-good, configured rollback, unconfirmed boot-attempt, live
executable-image, and running-process records.

## Launch Plans and Attenuation

A launch plan is derived monotonically under ceilings held by init or a trusted
launch-authority service. It is the delegated instance-creation object defined
above; its interface must not accept arbitrary `CapGrant` or `KernelCapSource`
lists.

Each grant slot is one of:

- **plan-owned:** a fixed capability held by the plan and forwarded to every
  instance;
- **caller binding:** the caller supplies a capability, but the plan fixes the
  name, expected interface, transfer mode, transfer scope, and any attenuator;
- **child-local:** the plan authorizes construction of one specific primitive
  under the child's resource budget; or
- **service export:** an endpoint or other result facet retained by the
  supervisor or exposed through a narrower interface.

The plan also fixes or bounds:

- the child session derivation rule;
- resource profile and image/process page budgets;
- maximum concurrent instances and restart rate;
- executable revision or permitted update channel;
- whether termination, observation, or child capability management is exposed;
- readiness timeout and failure policy; and
- structured audit identity.

Automatic return of a general `CapabilityManager` defeats a closed launch
profile. The manager may remain private to the plan/controller, while ordinary
callers receive distinct observer, termination, or exported-service facets.
Post-spawn mutation needed by an application should use named, slot-specific
grant ports rather than general authority over the child cap table.

## Runtime Deployment and Replacement

Tests and production should use the same deployment mechanism. The policy at
artifact ingress differs: a local-development profile may admit an unsigned
locally built image, while a production profile requires its configured release
signature, provenance, and anti-rollback checks. Both produce the same sealed
`ExecutableImage` object and use the same launch and lifecycle path.

Stateless service replacement proceeds as follows:

1. authenticate and seal the candidate image;
2. derive or resolve its service revision under the same role policy;
3. start the candidate while retaining the old process;
4. require an explicit readiness result;
5. stop admission to the old process and drain calls under a future graceful
   handoff protocol;
6. satisfy the current `retargetCaps` precondition that the old process has no
   in-flight call or parked receive, then retarget its served endpoint objects
   to the candidate;
7. terminate the old process after retargeting; and
8. promote the candidate generation only after the health checkpoint.

The currently implemented `retargetCaps` is only one mechanism in that
protocol. Its no-in-flight/no-parked-receive precondition is not graceful
drain. Until readiness, admission stop, graceful drain, and successor
endpoint-slot handoff exist, the system must not claim zero-disconnect or
state-preserving replacement. Stateful services additionally need an explicit
state snapshot or externalized-state handoff.

## Bootstrap and Recovery Floor

The kernel-embedded init image remains part of the kernel/bootstrap ABI. The
general manifest-builtin binary list should not remain the ordinary package
source merely because it exists before storage services start.

The recovery floor should contain only enough code and authority to:

- start canonical init;
- discover and mount the installed generation substrate;
- verify and activate a known-good generation;
- repair or roll back broken package/configuration pointers; and
- expose a bounded recovery console when normal startup cannot reach health.

If hardware requires userspace components before the persistent generation can
be mounted, those components belong in a sealed, measured `RecoveryBundle` with
fixed launch plans. The bundle is not enumerable application policy and must not
grow into a second system package namespace.

Focused tests that currently boot a demo directly as PID 1 should migrate
behind canonical init and the normal launch path. A test-specific artifact
ingress policy is acceptable; a test-only process-construction architecture is
not.

## POSIX and Language-Runtime Compatibility

POSIX `execve` needs a capability-scoped program resolver, not access to the
boot-wide `binaryName` registry. A `ProgramResolver` or `ExecDirectory`
capability maps the caller's permitted paths or command names to launch plans.
The POSIX adapter may supply declared fd/stdio binding slots, subject to
`FD_CLOEXEC`, transfer-scope, and interface checks, but cannot add arbitrary
kernel sources or child grants.

A broad operator command catalogue remains possible as an explicitly granted
resolver. Guest or service sessions receive narrower catalogues. Human-readable
paths remain resolver-local policy; the launch decision ultimately names an
exact image and role.

For Wasm, Lua, Python, and other interpreter-based runtimes, the interpreter is
one executable image. The module/script and its imported capability bundle are
separate data and authority supplied through the launch plan. Updating a script
does not require pretending the interpreter binary changed.

## Audit, Revocation, and Resource Accounting

Every launch attempt should record:

- launch-plan and service-role identity;
- exact executable digest and admitted generation;
- caller session reference and supervisor identity;
- grant-template identity and dynamic binding summary;
- resource budget and instance number;
- resulting pid/process generation; and
- success or typed failure outcome.

Revoking a launch plan prevents future instances. Controller policy decides
whether revocation also terminates existing instances. Revoking an update
channel prevents admission of future revisions without invalidating an already
active, known-good revision. Emergency termination and endpoint disconnection
remain explicit lifecycle operations.

Image storage, sealed snapshots, loaded executable pages, candidate overlap,
capability slots, and restart attempts need one auditable ledger per resource
class. Deployment must reserve for the old and candidate processes
simultaneously before it begins; rollback must release all candidate charges.

## Alternatives

### Keep the boot-wide name registry

Rejected as the long-term model. It provides useful whole-image recovery and
simple current tests, but it couples ordinary service deployment to image
rebuild/reboot and delegates broad caller-selected choice within the registry.
It remains only as a compatibility adapter during migration.

### Pass raw ELF bytes through `ProcessSpawner`

Rejected. Ring parameters are bounded well below current executable sizes, and
copying multi-megabyte artifacts through a control-plane invocation is the
wrong transport boundary.

### Let the kernel load an arbitrary `File` or `Store` directly

Deferred. The kernel cannot synchronously invoke an arbitrary userspace storage
server while already dispatching process construction without a larger
re-entrant or asynchronous kernel-to-userspace RPC design. Supporting only
kernel fixture files would create a second, test-shaped architecture.

### Load directly from mutable `MemoryObject`

Rejected without sealing. Another holder or writable mapping could change the
bytes between validation and use. Snapshot-and-seal preserves the current
boot-path property that ELF parsing and loading observe one immutable byte
sequence.

### Move complete process construction into userspace

Deferred. It substantially widens authority over address spaces, initial CPU
state, cap tables, and publication while leaving the kernel responsible for
final validation and rollback. The split can be reconsidered after executable
images and launch plans establish a concrete userspace builder use case.

### Make each binary the complete launch permission

Rejected as incomplete. It constrains code identity but not grants, session,
resources, multiplicity, lifecycle, or update policy. The chosen decomposition
uses exact image capabilities under role-specific launch plans.

## Migration

Migration does not require a flag day:

1. Add page-backed `ExecutableImage`, sealing, digest process identity, and a
   low-level image-based spawn method.
2. Represent current resident and ISO boot binaries internally as executable
   images. Keep `spawn(binaryName, grants)` as a compatibility adapter over the
   new constructor.
3. Prove production-shaped ingestion: assemble an ELF through ordinary data
   capabilities, seal it, mutate or drop the source, and spawn the unchanged
   digest. Prove callers lacking the factory, image, or launch authority fail
   closed.
4. Generalize restricted launchers into immutable service revisions and
   role-specific launch plans. Stop returning broad child-management authority
   to ordinary launch callers.
5. Add the authenticated umbrella generation root and make candidate,
   active, known-good, rollback, epoch, and garbage-collection operations
   atomic across configuration and service payloads.
6. Migrate init, ordinary services, POSIX program resolution, and focused
   tests away from the manifest binary registry.
7. Add controller-driven readiness and stateless replacement, followed by
   graceful drain and stateful migration.
8. Shrink the boot package to canonical init and an explicit recovery bundle,
   then retire general `binaryName` spawning.

Each implementation slice must keep the legacy and new paths behaviorally
distinct in evidence. A successful image-based spawn does not prove package
authenticity, a local QEMU upload does not prove production release authority,
and `retargetCaps` alone does not prove graceful live upgrade.

## Open Decisions

- Which trusted principal owns executable-image sealing in production, and how
  secure/measured boot anchors its accepted signing roots.
- Whether sealing always copies source pages or can consume an exclusively
  owned memory object without violating immutability or accounting.
- The package chunk/Merkle format and garbage-collection reachability rules.
- The stable capOS userspace ABI identifier and compatibility rules enforced at
  image admission.
- Whether image-page sharing is charged to the repository, each process, or a
  donated shared budget.
- The precise launch-plan schema and which attenuation wrappers remain
  userspace-served versus kernel-backed.
- How a successor receives inherited endpoint slot identities during
  `retargetCaps` without an out-of-band integer protocol.
- The minimum hardware-specific contents of the recovery bundle.

## Design Grounding

This design builds on:

- [Capability Model](../capability-model.md) for typed-interface authority,
  attenuation, transfer scope, and resource-ledger invariants;
- [Process Model](../architecture/process-model.md) for current kernel process
  construction and lifecycle authority;
- [Manifest and Service Startup](../architecture/manifest-startup.md) for the
  current boot-package executor and service graph;
- [Service Architecture](service-architecture-proposal.md) for supervisors,
  service roles, restart policy, and the pre-init boundary;
- [Userspace Binaries](userspace-binaries-proposal.md) for native ELF, runtime,
  POSIX, and language-adapter contracts;
- [Installable System](installable-system-proposal.md) for persistent
  generations, candidate confirmation, and rollback;
- [Live Upgrade](live-upgrade-proposal.md) for endpoint retargeting, quiesce,
  drain, and state migration; and
- [Capability Systems Survey](../research/capability-systems-survey.md) and
  [EROS, CapROS, and Coyotos](../research/eros-capros-coyotos.md) for explicit
  capability attenuation, constructor-based confinement, persistent object
  identity, and the choice to keep persistence and package policy explicit
  rather than transparently kernel-global.
