# Proposal: Service Object Capabilities

> **Status:** Superseded by
> [Session-Bound Invocation Context](session-bound-invocation-context-proposal.md).
> This document remains as historical design context for the already-landed
> synthetic routing/lifecycle proof. Do not continue the subject/proof
> root-opening or shared-service service-object migration from this proposal.

Replace caller-selected endpoint identity with service-minted object
capabilities.

## Problem

Endpoint client metadata currently carries service identity. A client endpoint
is a capability plus a caller-visible numeric tag; services can use that tag as
a member, session, role, connection, or actor key. That is too close to a
permission bitmask or ambient label: the generic IPC substrate accepts an
untyped number, and each service has to assign security meaning by convention.

The pre-containment problem became concrete through shell spawn syntax. A shell
that held a delegated `chat` client endpoint could request:

```text
run "chat-client" with { chat: client @chat badge 200 }
```

The launcher path could then pass the child a client facet with a different
value than the shell originally held. Gate 0 now rejects that relabeling for
ordinary delegated client facets, but the chat example remains the reason the
broader migration exists: service authority should not depend on a
caller-selected numeric tag.

capOS needs multi-client services, per-client state, service-created
attenuation, audit subject binding, and shell-spawned children. It does not
need caller-selected numeric identities.

## Goals

- Make the capability object itself carry the service authority.
- Keep endpoint transport generic while avoiding generic service roles,
  permission bits, or caller-selected labels.
- Let services expose many logical objects through one resident process.
- Let services bind subject/audit information at object creation time without
  putting identity policy into the kernel IPC fast path.
- Preserve explicit transfer semantics: copy and move pass the same object
  authority unless a trusted minter creates a new one.
- Provide a staged migration path for current chat, adventure, stdio, and
  endpoint smokes.

## Non-Goals

- A POSIX credential model.
- PID, PID@host, UID, role strings, or host names as service authority.
- Kernel interpretation of chat rooms, moderators, players, sessions, or
  principals.
- Generic per-capability permission bitmasks.
- Full network-transparent object references in the first slice.

## Design

An `Endpoint` remains a transport queue owned by a server process. Ordinary
clients should not hold "endpoint plus badge"; they should hold a capability
to a service object exported by that server.

## Design Grounding

Project files read for this design:

- `docs/capability-model.md`
- `docs/architecture/ipc-endpoints.md`
- `docs/proposals/service-architecture-proposal.md`
- `docs/proposals/shell-proposal.md`
- `docs/proposals/interactive-command-surface-proposal.md`
- `docs/backlog/stage-6-capability-semantics.md`
- `docs/backlog/runtime-network-shell.md`
- `docs/backlog/shared-service-demos.md`
- `docs/security/trust-boundaries.md`
- `docs/authority-accounting-transfer-design.md`
- `REVIEW_FINDINGS.md`

Research files checked from the actual `docs/research/` contents:

- `docs/research/sel4.md`
- `docs/research/eros-capros-coyotos.md`
- `docs/research/genode.md`

The design deliberately supersedes the prior seL4-style badge/mint direction
for service identity. Genode's RPC/session object model is a closer fit for
capOS services: clients hold capabilities to service-created objects, while
delegation passes the same object authority. EROS/CapROS/Coyotos and the
authority-accounting design reinforce the rule that authority should remain in
the capability graph, not in caller-selected numeric metadata.

Examples by service:

```text
Chat service:
ChatRoot
ChatParticipant
ChatRoom
ChatModerator

Terminal/child I/O service:
StdIO

Adventure service:
AdventurePlayer
AdventureNpc
```

Each object capability has one interface ID and one server-selected receiver
selector. The receiver selector is opaque to the client. It is not a user
field, not shell syntax, and not a policy label. It exists only so the kernel
can route the call to the resident server and the server can dispatch it to
the right object state.

Conceptually:

```text
service object cap = target endpoint + interface id + opaque receiver selector
```

Only trusted minting paths may create a new receiver selector:

- the endpoint owner/server,
- a supervisor or broker that holds explicit mint authority from the server,
- transitional manifest/init wiring for boot services.

Copying or moving a service object cap preserves the same receiver selector. An
ordinary client cannot relabel a delegated cap into a sibling object.

## Subject / Proof Binding

A service should be able to learn who or what a service object represents, but
that subject must be bound through trusted issuance rather than caller payload
claims.

The general shape is:

```capnp
interface Subject {
    deriveProof @0 (request :DelegationRequest) -> (proof :SubjectProof);
}

interface SubjectProof {
    attest @0 (challenge :Challenge) -> (statement :SubjectStatement);
}

interface ServiceRoot {
    open @0 (proof :SubjectProof, request :OpenRequest)
        -> (object :ServiceObject);
}

interface ServiceObject {
    call @0 (request :Request) -> (response :Response);
}
```

`UserSession` is the interactive-user case and can derive a proof scoped to a
service root, request digest, audience, and freshness window. A service account,
workload identity, broker-issued proof, anonymous session, guest session, or
other typed subject cap can fill the same role when that is the right trust
boundary. The root/factory validates the proof through a verifier, broker,
account, audit, or application policy interface it was granted, stores verified
metadata in its own object table, and returns a service object cap.

Later calls on the returned object do not need caller-supplied identity.
Possession of that object cap is the authority. The service can still record
principal/session audit identifiers, display names, channel memberships,
quota/accounting state, moderation state, workload labels, or other policy
metadata internally, but those records are service state, not endpoint metadata
that the caller can edit.

## Example Chat Shape

The current chat service uses one `Chat` endpoint and maps legacy endpoint
metadata to members. The target model is a root/factory plus participant
objects.

```capnp
interface ChatRoot {
    join @0 (channel :Text, handle :Text, session :UserSession)
        -> (participant :ChatParticipant);
}

interface ChatParticipant {
    join @0 (channel :Text) -> (joined :Bool);
    leave @1 (channel :Text) -> (left :Bool);
    send @2 (channel :Text, text :Text) -> (sent :Bool);
    who @3 (channel :Text) -> (members :List(Text));
    poll @4 (maxEvents :UInt16) -> (events :List(ChatEvent));
    close @5 () -> ();
}

interface ChatModerator {
    kick @0 (participant :ChatParticipant, channel :Text) -> (kicked :Bool);
}
```

`ChatParticipant` is the participant authority. If a child process receives
that cap, it acts as that same participant. It cannot type another receiver
selector and become another participant.

Moderator behavior is a separate cap/interface. The service may internally
associate participant and moderator state with the same subject, but the
kernel does not provide a role field and the client does not choose one.

Chat does not need to know about `AdventurePlayer` or `AdventureNpc`.
Adventure-specific caps belong to the adventure service. Room speech should
cross into chat through ordinary chat object caps such as `ChatParticipant` or
a future room-scoped chat object; the chat service should see chat subjects
and channels, not adventure interfaces.

## Kernel Contract

The kernel should enforce object-cap invariants and avoid service semantics.

Required invariants:

- Only endpoint owners or explicit mint-authority holders may create a service
  object cap for a new receiver selector.
- Delegating an existing service object cap preserves the receiver selector.
- Process spawning may copy or move service object caps but may not relabel
  them.
- Client-held object caps cannot receive or return endpoint messages unless
  their interface explicitly grants server authority.
- Receiver selectors are scoped to the target endpoint object; no global numeric
  namespace is part of the ABI.
- Process exit and cap release still drive endpoint cleanup for queued calls,
  in-flight returns, and server-visible cancellation.

The first compatibility step can keep the current `u64` storage field but
change the rules: a delegated client endpoint's numeric identity is preserved
on re-delegation. The target step renames and narrows the concept from
`badge` to an opaque receiver selector for service object caps.

## Shell And Launcher Contract

Shell users should launch applications, not assign service identities.

Target user shape:

```text
run "chat-client"
run "adventure-client"
```

Prototype explicit-grant shape while migration is incomplete:

```text
run "chat-client" with { stdio: client @stdio, chat: @chat_participant }
```

The normal shell must not expose `badge N` as user-facing authority syntax.
If a grant parser keeps legacy badge syntax for manifest or smoke migration,
the kernel must still reject any delegated-client relabeling.
Omitting a badge in shell syntax preserves the source identity; low-level
legacy badge-zero encodings remain hostile-test inputs and must still fail
closed for nonzero delegated client facets.

## External And Network Boundaries

External identity assertions do not open service objects directly. OIDC ID
tokens, passkey assertions, certificate chains, cloud workload tokens, and
remote gateway-authenticated claims first pass through an admission service
that normalizes provider kind, issuer, tenant, and subject; maps the result to
a local or pseudonymous principal when policy allows; and mints a local
subject/proof capability. Imported groups, roles, tenants, `acr`, `amr`, device
posture, source network, and token age are ABAC inputs to that mint decision,
not downstream object authority.

Network-transparent capability transport is also out of the first slice. A
future bridge should maintain connection-local export/import tables and expose
broken-reference semantics on disconnect. It must not serialize local cap-table
handles, endpoint generations, receiver selectors, or server cookies as
portable authority. Persistent restore, if needed, should go through a
capability-bearing naming or persistence service that authorizes and mints a
fresh live object.

## Migration Plan

The current execution plan lives in
`docs/backlog/service-object-identity-migration.md` and uses four large chunks.
Gate 0 containment below is already historical substrate. It does not mean the
service-object model is implemented. This proposal records the design sequence;
the backlog owns task breakdown and verification gates.

### 0. Contain delegated-client relabeling, landed

The kernel and shell paths now reject ordinary delegated-client relabeling.
This is containment, not the final model.

### 1. Core service-object routing and lifecycle, landed

Commit `a4655f0` at `2026-04-28 14:10 UTC` added the synthetic QEMU service
proof. It covers trusted `serviceObject` minting, receiver-cookie routing,
copy/move IPC transfer, nested spawn delegation, generation-checked service
receiver cookies, close/revoke rejection, and stale-cookie rejection after
record reuse.

### 2. Subject/proof root opening

Validate local subject/proof authority before object mint. External assertions
must first normalize through admission into local or pseudonymous subject/proof
caps.

### 3. Convert shared-service demos

Move chat, adventure, and stdio/terminal child bridges from caller-selected
endpoint identity to root/factory-opened service object caps.

### 4. Retire legacy endpoint identity

Remove compatibility syntax and rename internal fields once normal smokes no
longer depend on caller-selected endpoint identity.

## Security Notes

This design keeps the kernel out of role and identity policy. The kernel only
knows whether a caller holds a particular object cap and whether transfer rules
allow that cap to move. Services decide what their object records mean.

PID, PID@host, and process names are diagnostics. They are not authority:
process IDs recycle, hosts need cryptographic naming for federation, and a
single subject can legitimately hold multiple service objects with different
authority.

The broker and session services remain the right place to validate subjects
and policy before a service object is minted. After minting, the object cap is
the authority.
