# Proposal: Session-Bound Invocation Context

Replace caller-selected endpoint identity and the Service Object Identity
Migration with a simpler invariant: every process runs in exactly one live
session context. The kernel attaches that context to invocations and enforces
privacy/transfer invariants, but does not reveal subject details to endpoint
servers unless the call explicitly requests disclosure and policy allows the
requested fields through a broker/service disclosure scope.

Capabilities decide what a process may call. The calling process's session
context says who invokes, subject to privacy rules. Services receive only the
minimum routing/privacy metadata required by the invoked capability; request
fields remain ordinary data and must not select authority or caller identity.

## Problem

The prior service-object direction fixed a real bug: clients must not be able
to choose a service-visible numeric badge during spawn or IPC delegation. The
design then added service-minted object capabilities and a subject/proof open
protocol so services could bind identity without trusting request payloads.

That is too much machinery for the intended capOS process model. Normal
workload processes should not be bags of unrelated user sessions. They should
have one immutable session context, assigned at spawn, and all invocations from
that process should be attributable to that context. Delegated-subject
on-behalf-of behavior is a separate design and is intentionally out of this
first implementation path.

The target should therefore remove the caller-selected badge without replacing
it with a second service-object identity system. For a service such as chat,
holding `ChatRoot` already means the process may attempt to join chat under its
own session. More granular authority can come from narrower capabilities
granted by `AuthorityBroker`, not from client-selected receiver selectors or
local proof tokens on every open call.

## Decision

capOS adopts these invariants:

- Each process has exactly one immutable `SessionContext`.
- The session context is assigned at spawn and shared by all threads in that
  process.
- System services run under explicit service/system sessions.
- Network gateways create or select a session for each admitted connection and
  spawn per-session workers or shells; they do not run multiple user sessions
  as ambient subject context inside one ordinary workload process.
- Endpoint CALL delivery includes a privacy-preserving caller-session reference
  and optional freshness result, not full subject metadata by default.
- A held capability is the authority to invoke service root methods such as
  `ChatRoot.join`; the caller session supplies the invocation subject context.
  Services learn principal, profile, or display metadata only through explicit
  disclosure.
- Request fields such as `user`, `role`, `participant`, `principal`, or
  `session` are data. Services may validate them against the caller session,
  but they do not identify the caller or authorize by themselves.
- Subject disclosure is opt-in and policy-bounded. A call must explicitly ask
  for disclosure, and the requested fields must be allowed by a
  service-specific disclosure capability/scope. Without both signals, the
  server gets only an opaque session-local handle suitable for same-session
  state and audit correlation within that service.
- Cross-session capability transfer is supported when the transferred cap's
  transfer scope permits it. The transferred cap carries invoke authority; the
  receiver's session remains the invocation subject. Session-local caps require
  an explicit broker or service regrant operation.

The existing synthetic service-object routing proof remains useful as evidence
that request bytes cannot spoof endpoint receiver metadata, but the service
object identity model is no longer the active design direction.

## Normative Invariants

- Every normal workload process has exactly one immutable `SessionContext`.
- `SessionContext` is installed only by trusted spawn, session-manager, or
  broker paths; request payloads, shell strings, manifest data, endpoint
  receiver metadata, and copied `UserSession` caps cannot mutate or replace it.
- Capability possession remains the authority to invoke an interface. A live
  session without the target capability cannot call the target service.
- A normal endpoint call from a dead, revoked, or stale workload session fails
  closed, except for explicitly designated recovery, logout, or renewal caps.
- Endpoint default delivery never includes global principal, profile, account,
  role, tenant, external-claim, auth-factor, display-name, or source-network
  fields.
- Subject-detail disclosure requires both an explicit method/call disclosure
  request and a matching service-scoped disclosure scope.
- Disclosure is field-granular and service-scoped; an opaque session reference
  from one service is non-portable and non-authority-bearing in another.
- Cross-session raw cap transfer is rejected unless the cap's transfer scope
  permits it.
- After an allowed cross-session transfer, the receiver process session is the
  invocation context; raw transfer never implies act-on-behalf-of source
  session semantics.
- `service_regrant_only` caps cannot cross sessions through raw copy, move,
  IPC, or spawn grants. A service or broker regrant path must mint the target
  session authority explicitly.
- Legacy receiver metadata remains internal transport state. It must not be
  user-facing syntax, manifest policy, subject disclosure, or service identity.

## Authority And Context

Capability possession answers one question:

```text
May this process invoke this capability/interface at all?
```

It does not answer:

```text
Which live session is this invocation attributable to?
Is that session still fresh?
Which resource/profile bucket should pay for server-side state?
What subject facts may this service learn?
May this capability be transferred into another session?
```

Those are invocation-context and disclosure questions. The split is deliberate.
`ChatRoot` can mean "the holder may ask chat to join"; it does not by itself
tell chat whether the call is from an operator, a guest, an anonymous Telnet
session, or an expired session, nor whether chat may see a global principal id.

A service decision has three layers:

```text
capability authority
+ invocation subject context
+ service-local policy/state
```

Only the first layer is authority to invoke. The session layer supplies
information about who invokes, freshness, resource/accounting labels, and what
may be disclosed to the service. Service-local policy may accept or reject the
operation based on that information, but the session context is not a second
capability.

Examples:

- `ChatRoot` means the holder may ask chat to join, subject to chat policy and
  whatever session facts the call explicitly requests and broker/service policy
  makes available to chat.
- `ChatModerator` means the holder may call moderator methods, again under the
  caller's live session.
- `TerminalSession` means the holder may read/write that terminal endpoint,
  but audit and policy still see the process session.

Session-bound invocation context exists so services can make those second-order
decisions without trusting payload fields and without forcing the kernel to
reveal private subject metadata to every endpoint server. The kernel can say
"this call came from a live session and here is an opaque service-scoped
reference"; the service or broker can decide whether that is enough, whether a
guest-specific facet is required, or whether the user must explicitly disclose
bounded subject facts.

The kernel enforces capability possession, process session assignment, and
disclosure invariants. It may report freshness/liveness as invocation context.
Session expiry should bound behavior through capability lifecycle, broker
refusal, or service policy, not by treating the session context itself as a
second authority. The kernel still does not interpret chat rooms, handles,
moderator state, adventure players, account roles, OIDC claims, or tenant
groups.

## Privacy And Disclosure

Session-bound invocation context must not become ambient subject leakage. A service
should not receive global principal identifiers, account names, display names,
profile names, external issuer keys, group claims, auth factors, source
network, or tenant metadata merely because a process called an endpoint.

The default endpoint metadata is privacy-preserving:

```text
caller_session_ref = opaque, service-scoped, non-portable reference
session_live = true/false or epoch/freshness result
```

That is enough for a service to keep per-session state, reject stale sessions,
and correlate its own audit events without learning a broader identity.

Current proof implementation:

```text
scoped_ref: low 64-bit ABI field of the opaque reference
scoped_ref_hi: high 64-bit ABI field of the opaque reference
epoch: u64
derivation: HMAC-SHA256 with an entropy-backed boot key, a non-reused endpoint
  service-scope id, and the kernel session id
```

The ABI layout is preserved, but the old unkeyed low-half value is not. Both
`scoped_ref` and `scoped_ref_hi` are halves of the keyed opaque reference.
`epoch` is a separate domain-separated keyed value so service-local
freshness/audit correlation rotates with the same boot key and endpoint scope
without being folded into the opaque reference itself.

Current `caller_session_ref` derivation rules:

```text
width:
  128 bits minimum for the opaque reference, separate from freshness epoch.

derivation:
  keyed opaque value over boot secret, service scope, and kernel session id.

scope:
  a non-reused endpoint service-scope id plus the boot-scoped key. Endpoint
  object replacement or boot-key replacement intentionally rotates the
  reference. Stable service-audit identity across upgrades remains future work.

reuse:
  logout/login or session recreation gets a new kernel session id and therefore
  a new service-scoped reference.

stale epoch:
  stale references may remain recognizable to the same service for bounded
  audit/denial correlation, but they must not become live again after expiry.

service move/upgrade:
  endpoint replacement currently breaks correlation. Retaining correlation
  across service replacement requires a future stable service-audit scope.

privacy:
  global principal, account, profile, display name, auth source, and tenant
  metadata are not derivable from the opaque reference without broker/audit
  disclosure authority.
```

Richer disclosure requires both an explicit act and an allowed policy scope:

- the client calls a method whose contract requests disclosure, such as
  `ChatRoot.join(discloseProfile = true, handle = "alice")`, or transfers a
  `SessionDisclosure` capability as part of that call;
- `AuthorityBroker` or service policy grants a root/facet with a matching
  disclosure scope, such as "chat may see display name and profile class";
- an administrator-configured system service may expose methods whose contract
  explicitly requests audit disclosure, but those methods still need bounded
  service policy for the fields they receive.

Disclosure should be minimized and service-scoped. A chat service may need a
display name, guest/operator class, and per-service audit pseudonym. It does
not need raw OIDC claims, credential identifiers, account-store records, or
global principal ids unless a later policy explicitly grants that.

## Session Context

A `SessionContext` is kernel-carried metadata minted through trusted session
creation paths and installed by `ProcessSpawner`:

```text
SessionContext {
    session_id,
    principal_id,
    principal_kind,
    auth_strength,
    policy_profile_id,
    resource_profile_id,
    created_at_ms,
    expires_at_ms,
    epoch,
}
```

The exact ABI can be smaller in the first implementation. The required
properties are immutability for the process lifetime, a stable kernel-visible
session id for enforcement, a service-scoped opaque reference for default
endpoint delivery, and enough freshness metadata for brokers/services to fail
closed or revoke/withhold capabilities when a session expires or is revoked.
These conceptual fields may exist in trusted session storage. They are not
endpoint-delivered default metadata. Endpoint delivery gets only a
service-scoped opaque session reference and liveness/freshness result unless an
explicit disclosure request and matching disclosure scope allow named fields.

The session context is not a replacement for capabilities. A process with a
valid operator session but no `ChatRoot` cannot join chat. A process with
`ChatRoot` but an expired session should lose or fail to refresh the
capability authority that was issued for that session.

## Kernel Contract

The kernel should enforce generic mechanics only:

- A process has one session context pointer or compact session descriptor.
- Spawning a child requires selecting the child's session context. The default
  is to inherit the parent's session; creating a different session is broker or
  session-manager capability authority.
- Session expiry is represented as freshness metadata and capability lifecycle:
  normal workload endpoint calls from dead, revoked, or stale sessions fail
  closed except for explicit recovery, logout, or renewal caps. The current
  implementation rejects stale normal endpoint invocations before transfer
  preparation or enqueue, rejects fresh shell-bundle minting for stale sessions,
  and expires retained broker-issued non-endpoint shell bundle caps at their
  bound session boundary. `RestrictedLauncher` rejects spawn/list calls after
  the session it was minted for expires, and broker-issued `SystemInfo` results
  are session-bound wrappers. The session context itself is not the authority
  being invoked.
- Endpoint delivery includes privacy-preserving caller session metadata
  alongside the existing method, params, transfer descriptors, and result
  target. It must not include subject details unless the SQE/method contract
  explicitly requests them and a granted disclosure scope permits them.
  The current implementation uses a CALL SQE disclosure mask intersected with
  cap-held disclosure scope for field-granular delivery; unsupported fields
  are rejected or narrowed, and global principal ids and display names remain
  absent from default endpoint metadata.
- Capability transfer checks session scope. Same-session transfer preserves the
  held cap. Cross-session transfer is rejected unless the cap is explicitly
  cross-session-shareable or the transfer is the result of a broker/service
  delegation method.
- Legacy receiver metadata remains transport state only. It must not be exposed
  as user-facing identity syntax, manifest policy, service capability, or a
  workaround for subject disclosure.

The kernel should not validate external tokens, parse account stores, evaluate
roles, or choose application objects.

## Broker And Service Contract

`AuthorityBroker` and related session services decide which capabilities a
session receives:

```text
SessionManager.login/guest/anonymous -> UserSession metadata/control cap
trusted broker/session-manager spawn path -> child SessionContext
AuthorityBroker.shellBundle(session) -> launcher fixed to that SessionContext,
                                   ChatRoot, SystemInfo, ...
```

For basic local service access, no additional subject/proof token is required.
The process session context supplies caller information and a default
service-scoped session reference, and the held capability supplies access to
the service. Human-readable or policy-rich subject details are separate
disclosure, not automatic endpoint metadata.

`UserSession` remains useful as an informational/control capability and broker
input. It is not itself the ambient invocation subject, and copying it into a
process cannot install a second process session. A trusted broker or
session-manager path may use a verified `UserSession` to spawn a child with a
matching immutable `SessionContext`; ordinary cap transfer only transfers that
capability object.

External assertions still stop at the admission boundary. OIDC, passkey,
certificate, cloud workload, or SSH-authenticated claims are validated by
admission/session services, normalized into a local or pseudonymous session,
and then disappear from ordinary application calls. Chat should not parse OIDC
claims, and `ChatRoot.join` should not require a bearer proof object merely to
learn who the caller is.

## Chat Flow

The target chat flow is:

```text
login/setup/guest
    -> UserSession metadata/control cap

trusted broker/session-manager spawn path
    -> child process with SessionContext(operator or guest)

AuthorityBroker.shellBundle(session)
    -> ChatRoot if the profile may use chat

spawn chat-client with inherited session and ChatRoot

chat-client:
    ChatRoot.join(channel = "general", handle = "alice")
```

The kernel delivers the endpoint call with privacy-preserving caller session
metadata:

```text
target = ChatRoot
method = join
caller_session_ref = chat-scoped opaque session reference
session_live = true
payload = { channel = "general", handle = "alice" }
```

`chat-service` checks:

- the caller holds `ChatRoot`;
- the caller session is live;
- the requested channel and handle are syntactically valid request data.

Then it stores service-local state keyed by the caller session:

```text
ParticipantRecord {
    caller_session_ref,
    service_assigned_member_label,
    optional_disclosed_display_name,
    joined_channels,
    quota_bucket,
    audit_context,
}
```

If chat needs to distinguish operator from guest, use explicit disclosure with
a matching disclosure scope. If chat only needs narrower behavior, the broker
may grant `GuestChatRoot` with behavior that encodes the policy without
revealing subject fields. The service should not receive the global principal
id by default.

Later calls can use the same root/facet capability:

```text
Chat.send(channel = "general", text = "hi")
Chat.poll(max_events = 32)
Chat.who(channel = "general")
```

If the service permits multiple handles for one session, it may return a
server-issued `participant_id` as data. That id must be scoped to the caller
session and validated on every use:

```text
Chat.send(participant_id = 7, channel = "general", text = "hi")
```

`participant_id = 7` is not transferable authority. A different session cannot
use it unless chat or the broker performs an explicit share/delegation
operation.

Moderator behavior is a narrower capability, not a generic role bit in a
payload:

```text
AuthorityBroker.shellBundle(operator_session) -> ChatModerator
ChatModerator.kick(participant_id, channel)
```

The call still carries the operator session for audit and policy.

## Transfer Rules

Same-session delegation is ordinary capability transfer:

```text
operator shell -> child helper in the same session
    transfers ChatRoot or ChatModerator
```

The child acts under the same session context, so no subject ambiguity exists.

Cross-session transfer is where the distinction matters most:

```text
capability transfer carries authority to invoke;
the receiver process session supplies who invokes.
```

If session A transfers a cap to session B and the transfer is allowed, later
calls are made by session B, not by session A. The service sees the transferred
capability as the invoked authority and session B as the invocation subject
context. It must not infer that session B is impersonating session A merely
because the cap originally came from A.

This is acceptable for caps whose semantics are deliberately shareable, such
as a read-only document, a public chat invite, or a scoped terminal endpoint
intended for handoff. It is wrong for caps that encode session-local standing,
such as "my chat participant", "my account settings", or "my active adventure
player", unless the service explicitly defines what sharing means.

Therefore caps need an explicit transfer scope:

- `same_session`: may move/copy only to processes with the same session
  context;
- `cross_session_shareable`: may be transferred to another session and then
  invoked as the receiver's session;
- `service_regrant_only`: cannot be raw-transferred across sessions; the
  holder must ask the service or broker to issue a new cap for the target
  session.

Session-local services that want to share state across sessions should use an
explicit regrant/share path:

```text
Chat.share(participant_id, target_session_or_invitation)
AuthorityBroker.delegate(source_session, target_session, requested_cap)
```

The service or broker records the policy decision and mints or grants the
appropriate capability for the target session. Raw transfer of a session-scoped
cap across sessions must fail closed unless the cap has an explicit
cross-session-shareable scope.

This keeps privacy and accountability aligned. The transferred cap is not a
portable identity token for the source session. If the receiver invokes it, the
receiver's session context is used for audit/disclosure by default. If the
service needs to preserve source attribution, it should encode that as
service-local state during an explicit share/regrant operation, not rely on the
kernel to attach source-session subject data to future receiver calls.

The useful matrix is:

```text
cap transfer only:
    receiver gets authority to invoke;
    receiver invokes as its own process session.

service regrant:
    service or broker issues a new target-session capability;
    future calls still invoke as the target process session.
```

## What Happens To Service Object Routing

The synthetic service-object routing proof added in commit `a4655f0` should not
drive the next design step. Its useful artifacts are narrower:

- delegated-client relabeling is contained;
- receiver-cookie spoofing through request bytes is tested;
- close/revoke/stale-cookie paths have coverage;
- internal receiver metadata can be generation-checked.

Those mechanics can remain as low-level transport tests. They are not the
application authority model. The active migration should stop before
subject/proof root opening and shared-service conversion to service object
capabilities.

## Migration Plan

1. Record this proposal as the active Stage 6 direction and mark Service Object
   Identity Migration as superseded.
2. Add the kernel/process invariant: every process has exactly one immutable
   session context, including explicit service/system sessions.
3. Thread caller session metadata through endpoint CALL delivery.
4. Define session freshness propagation and the cap lifecycle rule needed to
   close the open review finding: expired sessions must not continue to receive
   or refresh interactive capability authority.
5. Define cap transfer scopes for `same_session`,
   `cross_session_shareable`, and `service_regrant_only`.
6. Replace chat's legacy receiver-selected member identity with session-keyed
   participant state and broker-granted `ChatRoot`/`ChatModerator` facets.
   The first chat migration is implemented for ordinary `Chat` membership:
   member records are keyed by the endpoint caller-session key, visible member
   labels are service-assigned, and join handles remain non-authority request
   data.
7. Apply the same pattern to adventure and terminal/stdio bridges. The first
   Aurelian migration is implemented for adventure player state: ordinary player
   records are keyed by live endpoint caller-session metadata instead of
   receiver badges. Terminal output now requires live caller-session dispatch,
   and shell-serviced stdio bridge waits bind to opaque live caller-session
   metadata while rejecting mismatched callers.
8. Retire user-facing badge/receiver selector syntax. Keep receiver metadata
   only as internal endpoint transport state or hostile-test fixture.

## Non-Goals

- Reintroducing POSIX `uid/gid` authorization.
- Allowing clients to choose identity through request bytes.
- Making external tokens ordinary application-service credentials.
- Delegated-subject or act-on-behalf-of semantics; those belong in a separate
  proposal and should not block this first implementation path.
- Preserving Service Object Identity Migration as the active design.
- Building network-transparent object references in this slice.

## Open Questions

- Whether all caps are `same_session` by default, or whether every cap entry
  should carry an explicit `same_session`, `service_regrant_only`, or
  `cross_session_shareable` scope.
- How much session metadata should be copied into endpoint delivery headers
  versus looked up by `session_id` in a kernel/session table.
- Whether multi-connection gateways must always spawn per-session workers, or
  may multiplex unauthenticated transport while delegating all session-bearing
  work to child processes.
