# Session-Bound Invocation Context

Selected milestone backlog for replacing caller-selected endpoint identity
without continuing the Service Object Identity Migration.

The detailed design lives in
[`docs/proposals/session-bound-invocation-context-proposal.md`](../proposals/session-bound-invocation-context-proposal.md).

## Design Target

The final model has one live session context per process:

- `Process.session_context` is immutable after spawn.
- Endpoint calls deliver privacy-preserving caller session metadata to the
  server. Subject details are not disclosed unless the caller explicitly asks
  for disclosure through the service call and a broker/service disclosure scope
  allows the requested fields.
- Broker-granted capabilities decide which service roots/facets a process may
  invoke.
- Services key user-facing state by caller session plus service-local records.
- Request payload fields are data and cannot select authority.
- Cross-session raw transfer is governed by cap transfer scope:
  `same_session`, `cross_session_shareable`, or `service_regrant_only`.
  If a cap crosses sessions, the receiver session supplies the future
  invocation subject context.

The existing service-object routing proof remains historical coverage for
receiver-cookie spoofing, lifecycle, and transfer behavior. It is not the
application authority model.

## Gate 1: Process Session Invariant

Visible proof: a focused QEMU session/process smoke shows a spawned shell and
child process have exactly one immutable session context, inherited by default,
while an attempt to inject or use a second independent invocation subject
fails.

Implementation scope:

- Add process-owned session context metadata with explicit system/service
  session support.
- Make `ProcessSpawner` select the child session context through inherit or a
  trusted broker/session-manager path.
- Prevent ordinary processes from holding or using multiple independent
  `UserSession` values as ambient invocation subjects.
- Keep `SessionContext` internal to process/session mechanics. Do not expose
  principal, profile, account, role, tenant, auth-factor, external-claim, or
  display fields through endpoint defaults or proof-only shortcuts.
- Add host tests for spawn/session validation and QEMU proof output for child
  inheritance.
- Hostile proof cases must show that copied `UserSession` caps, payload data,
  shell strings, and manifest grant data cannot install a second process
  session or select another child session outside the trusted broker/session
  path.
- Define the fail-closed freshness rule used by later endpoint work: normal
  endpoint calls from dead, revoked, or stale workload sessions fail except
  explicit recovery, logout, or renewal caps.
- Preserve existing anonymous/operator shell behavior while making guest shell
  behavior explicitly manifest-gated and narrow.

Verification gate:

- `make fmt-check`
- `cargo test-config`
- relevant host tests for session metadata
- focused QEMU process/session proof
- one existing login or shell proof touched by the session path

Status 2026-04-28 17:01 UTC: the kernel now gives each process an immutable
`SessionContext`, `ProcessSpawner` inherits the caller context by default, and
trusted broker/session paths can mint launchers fixed to a validated child
context. `make run-session-context` proves a copied `UserSession` cap cannot
relabel the child invocation context and that a broker profile mismatch fails
closed. It also proves an expired guest session cannot refresh a broker shell
bundle. Endpoint-delivered caller-session metadata, payload spoofing, and
field-granular disclosure remain Gate 2 work.

## Gate 2: Endpoint Caller Session Metadata And Disclosure

Visible proof: an endpoint server receives only an opaque service-scoped caller
session reference by default, rejects payload attempts to spoof `user`,
`session`, `role`, or participant identity, and receives bounded subject
details only when the caller explicitly requests disclosure and a
broker/service disclosure scope permits the requested fields.

Implementation scope:

- Extend endpoint delivery metadata with an opaque service-scoped caller
  session reference and minimal freshness/liveness information.
- Add an explicit disclosure mechanism for bounded subject fields, such as a
  per-call disclosure flag or a `SessionDisclosure` cap, and require a matching
  broker/service disclosure scope before fields are delivered.
- Decide the first freshness enforcement point needed to close the open session
  expiry review finding.
- Keep endpoint receiver metadata internal and non-authority-bearing.
- Add hostile endpoint tests proving request bytes cannot override caller
  session context or force subject disclosure.
- Add transfer-scope tests proving a `same_session` cap cannot cross into
  another session, while a `cross_session_shareable` cap invokes under the
  receiver's session context after transfer.
- Default transfer scope is fail-closed for cross-session movement:
  user/session-local caps use `same_session` or `service_regrant_only`, while
  `cross_session_shareable` must be explicitly chosen by the service or broker.
- Add expiry/revocation cases before shared-service migration: broker refuses
  fresh bundles for stale sessions, stale normal endpoint invocations fail or
  report the documented freshness failure, and service-scoped session refs
  cannot be replayed as authority.

Verification gate:

- `make fmt-check`
- `cargo test-lib`
- `cargo test-config`
- focused endpoint/session QEMU proof
- `make run-spawn`

Status 2026-04-28 17:43 UTC: commit `687511a` implements the first Gate 2
slice. Endpoint delivery includes only a service-scoped opaque caller-session
reference, epoch, and live/stale flags by default; it does not expose principal,
profile, account, role, tenant, auth-factor, external-claim, display-name, or
source-network fields. Normal endpoint calls from stale process sessions fail
closed before transfer preparation or enqueue. `make run-session-context`
proves a live child endpoint call carries nonzero opaque metadata despite
spoofed `user`, `session`, and `role` payload labels, then proves the same
child cannot invoke the endpoint after its session expires.

Status 2026-04-28 18:38 UTC: commit `f0cb74b` implements Gate 2 transfer-scope
enforcement. Cap holds now distinguish `same_session`,
`cross_session_shareable`, and `service_regrant_only` transfer policy.
Endpoint IPC, endpoint returns, and spawn grants reject cross-session movement
unless the scope permits it; fixed-session broker/launcher paths can regrant
`service_regrant_only` caps. `make run-session-context` proves same-session
spawn denial, raw IPC denial for a service-regrant-only `UserSession`, and
receiver-session invocation after an allowed endpoint-cap transfer. Remaining
Gate 2 work at that checkpoint was the explicit field-granular disclosure
mechanism.

Status 2026-04-28 19:33 UTC: commit `0f92d77` completes the Gate 2 explicit
disclosure mechanism. CALL SQEs carry a field-granular disclosure request
mask, capability holds carry service/broker disclosure scope, and endpoint
delivery exposes only the requested-and-allowed subject fields. The focused
QEMU proof covers all three privacy cases: request without scope exposes no
fields, scope without request exposes no fields, and request plus matching
scope exposes only allowed fields while narrowing broader requests. Gate 3 is
the chat session-keyed migration.

## Gate 3: Chat Session-Keyed Migration

Visible proof: `make run-chat` shows chat membership keyed by an opaque
service-scoped caller session reference and broker-granted chat capability,
with no user-facing badge or receiver selector. Payload identity spoofing,
unauthorized subject disclosure, and unauthorized cross-session participant id
reuse fail closed.

Implementation scope:

- Replace legacy chat receiver-selected member identity with session-keyed
  records.
- Treat `ChatRoot` possession plus caller session context as sufficient for
  join, subject to broker/profile policy.
- Keep global principal/account metadata private by default. If chat needs
  display name or guest/operator class, obtain it through explicit disclosure
  with a matching disclosure scope. If it only needs narrower behavior, use a
  broker-granted chat facet that encodes policy without revealing subject
  fields.
- Add a narrower moderator facet if moderator behavior is needed; do not use
  payload roles or generic rights bits.
- If chat supports multiple participant records per session, make returned
  participant ids server data scoped to the caller session, not transferable
  authority.
- Decide chat cap transfer scope explicitly. Plain `ChatRoot` may be
  same-session or broker-shareable; participant-like state must not raw-transfer
  across sessions unless chat defines a share/regrant method. If chat accepts a
  share, future calls use the receiver session as the invocation subject.
- Update shell examples and chat docs.

Verification gate:

- `make fmt-check`
- `cargo test-config`
- `cargo test-lib`
- `make run-chat`
- hostile chat spoofing QEMU coverage

Status 2026-04-28 20:06 UTC: commit `dc7ece4` implements the Gate 3 chat
session-keyed migration. `chat-server` now serves with endpoint caller
metadata, derives an opaque live caller-session key, and uses that key for
member records, channel membership, sends, leaves, and polls. Calls without a
live session key fail closed. `system-chat.cue` no longer assigns static chat
badges to the shell or bot, and `make run-chat` proves normal chat runs through
operator-session `chat-client` processes while the attempted delegated endpoint
relabel remains rejected. The `handle` join field is request data only, not
membership authority. After review, chat-visible sender labels are also
service-assigned `member-N` values, so request handles do not drive displayed
sender identity.

## Gate 4: Shared-Service And Legacy Cleanup

Visible proof: normal shared-service demos no longer expose caller-selected
service-visible identity, and service-object identity planning is retired from
the active path.

Implementation scope:

- Apply the session-keyed model to adventure player/NPC state and
  terminal/stdio child bridges where they currently use legacy receiver
  metadata as identity.
  - Stdio bridges should bind parent-side servicing to opaque live endpoint
    caller-session metadata and reject a bridge that later changes caller
    session, without asking the child to disclose global subject fields.
- Remove normal shell and manifest syntax that lets a caller select a badge or
  receiver selector.
- Keep low-level receiver metadata only as internal endpoint transport state or
  hostile-test fixture.
- Update `docs/capability-model.md`, `docs/architecture/ipc-endpoints.md`,
  `docs/security/trust-boundaries.md`, demos, and status pages.

Verification gate:

- `make fmt-check`
- `cargo test-lib`
- `cargo test-config`
- `make run-smoke`
- `make run-spawn`
- `make run-chat`
- `make run-adventure`
- `make docs`

Status 2026-04-28 20:48 UTC: the guest-bundle cleanup slice narrows one Gate 4
identity/policy leak without touching adventure content. `SessionManager.guest`
now requires an explicit manifest guest seed, `AuthorityBroker.shellBundle`
returns no default guest service endpoints, and guest launchers use a
resource-profile `launcherProfile` instead of the full manifest binary list.
The default guest profile has an empty launcher; the session-context proof uses
a dedicated one-binary guest profile for `session-context-child`; and the
default smoke proof covers manifest-without-guest-seed denial.

Status 2026-04-28 21:36 UTC: the session-expiry review finding is closed for
current shell/broker authority. Endpoint CALLs already required live caller
sessions. Retained broker-issued non-endpoint bundle caps now expire at their
bound session boundary: `RestrictedLauncher` rejects spawn/list calls after the
minted session expires, and broker-issued `SystemInfo` caps are session-bound
wrappers. Raw process-spawner, capability-manager, and process-handle control
caps opt into live caller-session dispatch for any path that still exposes
them. `make run-local-users` proves an expired operator shell cannot keep
launcher authority through an already-issued bundle, and `make
run-session-context` proves the narrow guest proof launcher also fails closed
after expiry.

Status 2026-04-28 22:02 UTC: the normal shell parser now rejects explicit
`client @... badge N` grants and preserves delegated client endpoint identity
when badge syntax is omitted. Default MOTD and adventure docs use omitted-badge
launches, while hostile selector fixtures remain in low-level smoke coverage.

Status 2026-04-29 05:59 UTC: the focused chat manifest now routes the same
kernel singleton `chat_endpoint` through init to the resident chat server that
the broker facets into operator shell bundles. The focused chat shell no longer
receives the resident chat-server export directly from `system-chat.cue`; the
normal shell path uses the broker-issued operator bundle `chat` endpoint, while
the resident bot keeps its manifest service grant.

Status 2026-04-29 06:17 UTC: terminal output is now behind the same live caller
session dispatch gate as terminal input. Both UART-backed `TerminalSession`
and socket-backed `SocketTerminalSession` require a live caller session for
`write`, `writeLine`, and `readLine`, so stale shell sessions cannot keep a
terminal bridge useful through write-only calls. `TcpSocket.intoTerminalSession`
continues to return a move-only terminal cap, but the result hold is explicitly
cross-session shareable because the Telnet gateway converts an accepted socket
in its service session and then grants the terminal to the broker-minted shell
session.

Status 2026-04-29 09:00 UTC: shell-serviced stdio bridge waits now bind to
opaque live endpoint caller-session metadata during the active child wait and
reject mismatched callers without asking the child to disclose global subject
fields. Normal `StdIO.close` exits cleanly, rejected calls drain transferred
caps before returning, and `make run-session-context` covers a transfer-bearing
cross-session rejection. `demos/service-common` no longer exposes a
badge-serving helper or badge field on `EndpointCaller`; new shared endpoint
loop code uses `EndpointUserData`, with the old badge-named user-data alias
kept for checked-in adventure compatibility while peer-owned adventure work
migrates.

Status 2026-04-29 09:44 UTC: the non-adventure endpoint caller-session
reference is widened to 128 bits while keeping `scoped_ref` as the low 64-bit
compatibility half and adding `scoped_ref_hi` as the high half. Endpoint
delivery fills both halves from independently domain-separated, nonzero hashes,
keeps `epoch` separate, and non-adventure service/session-context/stdout bridge
guards now require and compare both halves. This remains proof-grade opaque
reference derivation; a true keyed secret, scope-key rotation, and rotation
lifetime policy are still deferred.

Status 2026-04-29 10:20 UTC: endpoint caller-session references now use an
entropy-backed boot secret and HMAC-SHA256 over a non-reused endpoint
service-scope id plus the kernel session id. `scoped_ref` remains the low ABI
field, but it is no longer value-compatible with the old unkeyed low-half hash;
`scoped_ref_hi` is the high ABI field of the same keyed opaque reference.
`epoch` stays a separate field and is also domain-separated under the boot key
so stale/freshness audit correlation rotates with boot-key and endpoint-scope
changes. References rotate on reboot and endpoint object replacement. Stable
service-audit identity across service upgrades remains future work.

Status 2026-04-29 11:00 UTC: the session-context QEMU proof now calls two
distinct endpoint service scopes from one child process/session before expiry
and asserts their opaque caller-session reference tuples differ while both
remain live. This covers endpoint-object replacement/scope changes at the
demo-proof level; stable service-audit identity across upgrades remains
future work.

## Deferred Work

- Remote capability transport and network transparency.
- Durable account store and external identity binding persistence.
- Full quota service and scheduling-context donation policy.
- Explicit cross-session sharing UX and audit workflow.
- Stable service-audit identity for endpoint caller-session references across
  intentional service replacement or upgrade.
- Delegated-subject / act-on-behalf-of context. See
  `docs/proposals/delegated-subject-context-proposal.md`.
