# Rejected Proposal: Endpoint Badges as Service Identity

## Status

Rejected. This was the short-lived seL4-style model where a capability hold
edge carried a `u64` badge and endpoint servers used that badge as the
service-visible caller identity.

The model was superseded by
[Service Object Capabilities](service-object-capabilities-proposal.md), which
reframed the badge field as an opaque receiver selector owned by a service
object capability. That proposal is also superseded: the active direction is
[Session-Bound Invocation Context](session-bound-invocation-context-proposal.md),
where each process has one immutable session context and endpoint calls expose
privacy-preserving caller-session metadata instead of caller-selected badges or
service-object identity migration.

This document records what badges were, how they were intended to be used, what
was implemented, and why the design was rejected.

## Proposal

Add a word-sized badge to each capability hold edge and deliver that value to an
endpoint server whenever the holder invokes the endpoint. Multiple clients could
therefore share one endpoint object while the server still distinguished them:

```text
endpoint object
  client cap hold badge 100 -> chat participant 100
  client cap hold badge 200 -> chat participant 200
  client cap hold badge 302 -> adventure player 302
```

The model came from seL4's endpoint badge and mint pattern. A trusted holder of
an endpoint owner capability could mint differently badged client facets for
children or services. Copy and move transfer preserved the badge, so delegation
kept the same service-visible identity unless a trusted mint path created a
fresh one.

## Intended Use

Badges were intended to solve a real early shared-service problem: chat,
adventure, stdio bridges, and endpoint smokes needed more than one logical
client on a resident service endpoint. Creating one kernel endpoint per client
was unnecessary overhead for the demo stage, and putting a caller name or role
in request bytes would have been trivial to spoof.

The intended rules were:

- a badge is not a generic rights bitmask;
- a badge is hold-edge metadata, not part of the endpoint object;
- endpoint CALL delivery reports the invoked hold badge to the server;
- copy and move transfer preserve the badge;
- raw spawn grants preserve the source badge;
- endpoint owners and ProcessSpawner-created parent endpoint result facets may
  mint a requested child client badge;
- delegated client facets may be passed on only with the same badge.

Under that model, a chat server could key membership by badge and an adventure
server could key per-player room/inventory state by badge. The badge was meant
to be server-visible caller identity, not a user-facing permission flag.

## Implementation Specifics

The concrete implementation landed in several steps:

- Commit `3ee5240` (`feat: propagate endpoint capability badges`,
  2026-04-22) added `CapRef.badge` to the manifest schema, parsed optional
  CUE `badge` fields, stored the value in `CapHold`, and changed endpoint CALL
  dispatch so `call.badge` came from the invoked capability slot. The
  cross-process IPC smoke asserted a nonzero badge on RECV and RETURN
  completions.
- Commit `df0d140` (`feat: add spawn grant badge attenuation`,
  2026-04-22) added `CapGrant.badge` to the ProcessSpawner ABI. Raw grants
  failed closed if the requested badge differed from the source hold.
  `ClientEndpoint` grants could mint the requested badge only from an endpoint
  owner source. The init spawn proof printed `[init] Spawn badge attenuation
  ok.` after exercising the path.
- Commit `2face05` (`demos: extract badged endpoint service loop`,
  2026-04-24) extracted `serve_badged_endpoint` into `demos/service-common/`.
  The helper performed endpoint RECV, released unexpected transferred caps,
  decoded params, and called service handlers as
  `handle_request(state, badge, method_id, params)`.
- Chat and adventure used that helper to route per-client service state by
  badge. Manifest examples such as `system-chat.cue` and
  `system-adventure.cue` carried explicit badge values for shared-service
  clients and NPC/client identities.
- Commit `3e59540` (`fix: narrow endpoint result badge minting`,
  2026-04-25) stopped treating every endpoint `ResultCap` as trusted badge
  minting authority. Only endpoint owners and ProcessSpawner-created parent
  endpoint result facets retained mint authority; ordinary IPC result transfers
  stayed `ResultCap` and could not become a badge-mint path.
- Commit `f955cd5` (`fix: reject delegated endpoint relabeling`,
  2026-04-25) fixed the first containment failure: already-delegated client
  facets could no longer request a different badge through `ClientEndpoint`
  spawn grants.
- Commit `a64c216` (`spawn: preserve delegated endpoint identities`,
  2026-04-25) fixed the shell/defaulting case. Omitted shell badge syntax began
  preserving the source badge via `PRESERVE_CLIENT_ENDPOINT_BADGE = u64::MAX`,
  while explicit relabel attempts and low-level legacy badge-zero encodings
  failed closed for delegated client facets.

The final contained implementation still has a `badge` field in several ABI and
implementation structs. Current docs call it legacy receiver metadata or a
receiver selector when it is still needed for low-level tests, service-object
history, or non-identity parameters such as scoped TCP listen ports. It is no
longer the target identity model.

## What Failed

The design gave too much meaning to an untyped number. Even when the kernel
preserved badges across copy/move transfer, shell and spawn surfaces could still
turn a caller-selected integer into service-visible identity unless every grant
path handled mint authority perfectly.

The concrete failure was delegated endpoint relabeling. A shell holding a
delegated `chat` client endpoint could request:

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

Before the containment fixes, that could produce a child client facet whose
service-visible identity differed from the delegated source. Omitted badge
syntax was also dangerous because the old parser defaulted it to badge `0`,
which was another relabeling path for a nonzero source client.

The bug was narrow, but it exposed the wrong abstraction. The server was being
asked to treat a generic transport field as identity. The kernel could enforce
some mint rules, but the meaning of `100`, `200`, `302`, or `0` lived in each
service by convention. That made ordinary shell syntax look like an authority
selector and made future network-backed shell exposure too easy to get wrong.

## Rationale For Rejection

Endpoint badges are a useful low-level routing mechanism, but not a good
service identity model for capOS.

Problems:

- **Caller-selected identity pressure.** The natural user-facing syntax was
  `client @service badge N`, which invited users and tests to select service
  identity directly.
- **Untyped service semantics.** The same `u64` field could mean a chat member,
  an adventure player, an NPC, a stdio bridge, a TCP port, or a test fixture.
  The kernel could not validate those meanings.
- **Policy by convention.** Each service had to remember whether a badge was a
  participant, a session, a role, a receiver cookie, or just a transport tag.
- **Delegation hazards.** Copy/move propagation was straightforward, but spawn
  minting needed subtle distinctions between endpoint owners,
  ProcessSpawner-created parent endpoint result facets, ordinary IPC result
  caps, and delegated client facets.
- **Bad privacy shape.** A server-visible endpoint field encouraged exposing
  stable caller identity by default, while the active model wants
  privacy-preserving session references and explicit bounded disclosure.
- **Poor long-term composition.** Cross-service and network-transparent
  designs need typed roots/facets, session context, transfer policy, and
  disclosure policy. A single badge value cannot carry those contracts.

The accepted historical fix was first to contain relabeling, then to stop
treating badges as the target architecture. Service Object Capabilities moved
identity into service-minted object capabilities and receiver selectors. That
was still too much machinery for normal workload identity and was replaced by
Session-Bound Invocation Context.

## Replacement Direction

The active replacement is:

- capabilities answer whether the process may invoke a service at all;
- each process has exactly one immutable session context;
- endpoint delivery carries privacy-preserving caller-session metadata by
  default;
- richer subject disclosure requires an explicit request and a matching
  broker/service disclosure scope;
- shared services key user-facing state by broker-granted service
  capabilities plus service-scoped session references, not by caller-selected
  badges.

Legacy badge fields may remain as internal receiver metadata, hostile-test
fixtures, or non-identity configuration encodings until the corresponding code
paths are migrated. They should not appear as normal user-facing service
identity syntax.

## Design Grounding

Project files read for this post-mortem:

- `docs/capability-model.md`
- `docs/architecture/ipc-endpoints.md`
- `docs/proposals/service-object-capabilities-proposal.md`
- `docs/proposals/session-bound-invocation-context-proposal.md`
- `docs/authority-accounting-transfer-design.md`
- `docs/research.md`
- `docs/security/trust-boundaries.md`
- `WORKPLAN.md`
- `REVIEW_FINDINGS.md`

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

- `docs/research/sel4.md`

The historical badge model followed the seL4 badge/mint precedent recorded in
the repo research notes. The rejection is capOS-specific: schema-typed
interfaces, session-bound process identity, broker-issued service authority,
and privacy-bounded disclosure fit the project better than making a generic
endpoint metadata word carry service identity.
