# Proposal: User Identity, Sessions, and Policy

How capOS should represent human users, service identities, guests,
anonymous callers, and policy systems without reintroducing Unix-style
ambient authority.

**Status:** partially implemented. The current tree has entropy-backed
`UserSession` metadata for anonymous, operator, and guest profiles; a bootstrap
`CredentialStore`; shell-driven `login`, `setup`, and `guest` profile changes;
`AuthorityBroker.shellBundle` returning broker-issued launcher, copied session,
`SystemInfo`, and operator-scoped service endpoint caps; and manifest seed
records for local operator/guest proofs. Guest shell bundles are manifest-gated
and receive no default service endpoints. Endpoint calls now keep subject
details private by default and disclose only requested-and-allowed fields from
cap-held service/broker disclosure scope. The broader proposal remains target
design for durable account storage, external identity bindings, session
logout/revocation/renewal lifecycle, quota-backed profiles, ABAC/MAC policy
engines, and POSIX compatibility metadata.

## Problem

capOS has processes, address spaces, capability tables, object identities,
badges, quotas, and transfer rules. It deliberately does not have global
paths, ambient file descriptors, a privileged `root` bit, or Unix `uid/gid`
authorization in the kernel.

Interactive operation still needs a way to answer practical questions:

- Who is using this shell session?
- Which caps should a normal daily session receive?
- How does a service distinguish Alice, Bob, a service account, a guest,
  and an anonymous network caller?
- How do RBAC, ABAC, and mandatory policy fit a capability system?
- How does POSIX compatibility expose users without letting `uid` become
  authority?

The answer should keep the enforcement model simple: capabilities are the
authority. Identity and policy decide which capabilities get minted, granted,
attenuated, leased, revoked, and audited.

## Design Principles

- `user` is not a kernel primitive.
- `uid`, `gid`, role, and label values do not authorize kernel operations.
- A process is authorized only by capabilities in its table.
- Authentication proves or selects a principal; it does not itself grant
  authority.
- An account is a durable local record for a principal; it is not a
  running subject.
- A session is a live policy context with selected policy and resource
  profiles that receives a cap bundle.
- A workload is a process or supervision subtree launched with explicit caps.
- POSIX user concepts are compatibility metadata over scoped caps.
- Guest and anonymous access are explicit policy profiles, not missing policy.
- External roles, groups, claims, and local roles are broker inputs, not
  authority after the corresponding caps are absent.

## Concepts

### Principal

A principal is a durable or deliberately ephemeral identity known to auth and
policy services. It is useful for policy decisions, ownership metadata, audit
records, and user-facing display. It is not a kernel subject.

Examples:

- human account
- operator account
- service account
- cloud instance or deployment identity
- guest profile
- anonymous caller
- pseudonymous key-bound identity

The schema excerpt below is proposal-level shape. Where the interfaces already
exist in `schema/capos.capnp`, the ordinals shown here must match the checked-in
schema; future methods must be assigned from the next free ordinal when the
schema is actually extended.

```capnp
enum PrincipalKind {
  human @0;
  operator @1;
  service @2;
  guest @3;
  anonymous @4;
  pseudonymous @5;
}

struct PrincipalInfo {
  id @0 :Data;             # Stable opaque ID, or random ephemeral ID.
  kind @1 :PrincipalKind;
  displayName @2 :Text;
}
```

`PrincipalInfo` is intentionally descriptive. Possessing a serialized
`PrincipalInfo` value must not grant authority.

Federated authentication uses a canonical external subject key:
`hash(providerKind, issuer, tenant, subject)`. For OIDC, `issuer` is `iss`,
`subject` is `sub`, and `tenant` is the normalized tenant or configured empty
tenant. `sub` alone is not unique across IdPs and must not be used directly.
Admission policy either maps that external key to an existing local principal
through an `ExternalIdentityBinding` or admits it as a pseudonymous principal
under an explicit policy/resource profile pair. `PrincipalKind` covers the resolved
local principal through `human` / `operator` / `service` / `pseudonymous`
depending on deployment intent; a federated service account is `service`, a
federated human is `human`, and a federated ephemeral identity with no stable
person behind it is `pseudonymous`. The OIDC integration details live in
[oidc-and-oauth2-proposal.md](oidc-and-oauth2-proposal.md).

### User

`user` is a user-facing category for a principal/session that represents a
human or human-adjacent actor. It is not a kernel object, not a UID, and not
an authority source. Use `principal`, `account`, `session`, or `workload`
when one of those narrower concepts is meant.

### Account

An account is a durable local record for a principal. It binds credential
references, status, roles, attributes, storage roots, quotas, and default
policy/resource profile names. Some principals deliberately have no account:
anonymous callers, some guests, and some one-shot external sessions.

Accounts do not run and do not hold capabilities. Session creation reads an
account record, manifest seed record, or external admission binding, then asks
a trusted broker to mint the actual CapSet for a live session or workload.

### Profile

A profile is a named policy template. It contains no authority by itself.

- A **policy profile** selects roles, ABAC defaults, allowed bundle fragments,
  approval paths, label defaults, and external admission constraints.
- A **resource profile** selects storage, memory, CPU share, process/thread/cap
  limits, IPC limits, log volume, network posture, and launcher posture.

Use plain `profile` only when prose intentionally covers both policy and
resource profiles.

### Session

A session is a live context derived from a principal plus authentication and
policy state. Sessions carry freshness, expiry, auth strength, audit identity,
and selected policy and resource profiles. The selected profiles influence
which caps a broker may mint and which quotas wrappers apply; the profiles are
not usable authority.

`AuthStrength` aligns with ITU-T X.1254 *Entity authentication assurance
framework* (= ISO/IEC 29115) level-of-assurance tiers. X.1254 defines
LoA 1 (little or no confidence) through LoA 4 (very high confidence) as a
composite of identity-proofing strength, credential strength, and
authentication-protocol strength. capOS uses the same tiers so that
policy decisions can be expressed as "require LoA ≥ 3 for
`ServiceSupervisor(net-stack)`" without inventing parallel terminology.

```capnp
# ITU-T X.1254 / ISO/IEC 29115 level-of-assurance tiers.
# `loa0` covers "no assertion" (`anonymous` sessions) and sits below
# the X.1254 lattice; the standard numbers LoA 1-4 only.
enum AuthStrength {
  loa0 @0;   # no authentication; anonymous
  loa1 @1;   # little/no confidence; self-asserted identity
  loa2 @2;   # some confidence; single-factor, e.g. password
  loa3 @3;   # high confidence; multi-factor, hardware-backed key
  loa4 @4;   # very high confidence; multi-factor with tamper-resistant
             # hardware and in-person or equivalent identity proofing
}

struct SessionInfo {
  sessionId @0 :Data;
  principal @1 :PrincipalInfo;
  authStrength @2 :AuthStrength;
  createdAtMs @3 :UInt64;
  expiresAtMs @4 :UInt64;
  policyProfile @5 :ProfileSummary;
  resourceProfile @6 :ProfileSummary;
  # Multi-party / delegated / federated session context. Populated when
  # the session was minted through an AuthorityBroker approval flow or a
  # federated IdP rather than direct interactive login.
  delegationChain @7 :List(Data);    # opaque session/IdP IDs
}

struct ProfileSummary {
  id @0 :Data;
  displayName @1 :Text;
  versionId @2 :Data;
  epoch @3 :UInt64;
}

struct CapabilityResultHandle {
  brokerId @0 :Data;
  grantId @1 :Data;
  interfaceId @2 :UInt64;
  issuedAtMs @3 :UInt64;
  expiresAtMs @4 :UInt64;
}

interface UserSession {
  info @0 () -> (info :SessionInfo);
  auditContext @1 () -> (sessionId :Data, principalId :Data);
  logout @2 () -> ();
  # Future result/grant metadata methods must use fresh ordinals; they are
  # intentionally not assigned in this proposal sketch.
}

interface SessionManager {
  login @0 (
    method :Text,
    selector :LoginSelector,
    proof :Data,
    source :LoginSourceMetadata
  ) -> (sessionIndex :UInt16);
  guest @1 () -> (sessionIndex :UInt16);
  anonymous @2 () -> (sessionIndex :UInt16);
  sshPublicKey @3 (
    username :Text,
    algorithm :Text,
    publicKey :Data,
    authBytes :Data,
    signature :Data,
    sourceAddr :Data
  ) -> (sessionIndex :UInt16);
  # Future renewal must use the next free ordinal in the checked-in schema,
  # currently @4, not @3.
}
```

When brokers return granted caps, `GrantedCap` should be the same
transport-level result-cap concept used by `ProcessSpawner`, not a parallel
authority encoding.

`UserSession` is the live session/profile summary surface, not the account
database and not the process invocation subject itself. In the session-bound
invocation model, the immutable kernel-installed `SessionContext` on the
process is the invocation context; `kernel/src/session_context.rs` owns that
state and the spawn-time inheritance/broker-selection rules described in
[service-architecture-proposal.md](service-architecture-proposal.md). A
`UserSession` cap may expose stable session metadata, profile summaries, audit
context, expiry, and opaque handles for cap-broker results that have already
been minted. It can also be used as trusted broker/session-manager input to
spawn a child with a matching `SessionContext`, but copying a `UserSession`
into an existing process cannot install a second session or relabel future
calls. These handles are
non-bearer metadata for audit and UI display: they cannot be redeemed into
caps unless the caller also holds the separate broker, approval, or launcher
authority required for the grant. `UserSession` must not expose mutable account
records, credential records, role bindings, storage-root records, policy
document bodies, or redeemable grant tokens. Fresh cap bundles come from
`AuthorityBroker` or a launcher/supervisor that consumes the session context;
the session cap itself is not a general account-store reader and is not the
ordinary authority-vending path.

### Session Lifecycle And Renewal

The `expiresAtMs` field is not sufficient by itself. The target model treats a
session as a revocable lease with explicit state:

```text
live | logged_out | revoked | expired | recovery_only
```

The immutable process `SessionContext` identifies the subject selected at
spawn (see [service-architecture-proposal.md](service-architecture-proposal.md)
for the kernel-owned spawn-time installation and the
`make run-session-context` proof). It should point at, or be paired with,
trusted session-manager liveness state that can change without relabeling the
process:

```text
SessionLivenessCell {
    sessionId
    sessionEpoch
    state
    notBeforeMs
    notAfterMs
    policyEpoch
    resourceProfileEpoch
    auditRecordId
}
```

The liveness cell answers whether ordinary invocation may continue. Grant
leases answer whether a particular broker-issued bundle or elevated cap remains
valid. Object/facet epochs answer whether the target live object generation
has been revoked or replaced. These checks compose; none of them is a
substitute for capability possession.

For local password-authenticated shells, fixed short wall-clock expiry should
not be the only interactive policy. A sane default is that the session remains
live until explicit logout, terminal/connection close, owner shell or
supervisor subtree exit, administrator revocation, account disablement, policy
version invalidation, or a configured idle/hard maximum. Guest, anonymous,
remote, federated, and elevated sessions may use much shorter leases.

Renewal must be a narrow session-manager or broker path. The exact Cap'n Proto
signature is future schema work; with the current checked-in `SessionManager`
ordinal map, the first renewal method would be assigned `@4` unless another
schema change lands first:

```text
interface SessionManager {
  renew @nextFree (
    session :UserSession,
    proof :Data,
    requestedDurationMs :UInt64
  ) -> (session :UserSession);
}
```

`renew` may extend the same liveness cell or mint a successor session in the
same audit family, depending on policy. It must check account status, auth
freshness, session state, policy/resource profile epochs, requested duration,
absolute maximum lifetime, and explicit revocation state. It must not make all
old grants fresh. When policy needs a new decision, the broker returns fresh
grant leases and wrapper caps; stale ordinary grants remain stale or are
explicitly revoked.

Only named recovery methods should work after expiry: logout, renew, recovery,
and narrowly scoped self-diagnostic status. Explicit revocation should block
ordinary renewal unless a separately audited recovery policy says otherwise.
Owner-shell exit and gateway disconnect should call logout for sessions they
own, then process-exit cleanup releases local hold edges.

### Workload

A workload is a process or supervision subtree started from a session,
service, or supervisor. Workloads may carry session metadata for audit and
policy, but they do not run "as" a user in the Unix sense. They run with a
CapSet.

Common workload shapes:

- interactive native shell
- agent shell
- POSIX shell compatibility session
- user-facing application
- per-user service instance
- shared service handling many user sessions
- service account process

### Capability

A capability remains the actual authority. A process can only use what is in
its local capability table. Policy services can choose to mint, attenuate,
lease, transfer, or revoke capabilities, but they do not create a second
authorization channel.

## Account and Admission Sources

capOS should have three account and admission sources. All three feed policy;
none of them bypass the capability graph.

1. **Manifest seed accounts.** Immutable or append-only bootstrap records in
   the boot package. These create first local operators, recovery identities,
   service identities, emergency guest policy, and initial policy bundles.
   Seed data must be sufficient to boot, recover, unlock storage, and create
   or repair the local account store. It must not become the ordinary mutable
   account database.
2. **Local account store.** Mutable Store/Namespace-backed records for
   accounts, credentials, roles, attributes, quotas, policy profiles, resource
   profiles, and storage roots. After initialization, disk state is
   authoritative for ordinary local accounts, with explicit versioning,
   rollback detection, and recovery import/export.
3. **External identity admission and bindings.** OIDC, passkey, cloud,
   deployment, or certificate-backed principals mapped to named policy/resource
   profiles or existing local accounts. External claims are normalized ABAC
   inputs and may select a binding; they do not grant local authority by
   themselves.

### Account Store Boundary

Mutable account state belongs in a separate account-store schema and service
slice, not in the session schema. The identity/session schema should contain
`PrincipalInfo`, `SessionInfo`, profile summaries, audit context, and opaque
broker result handles. The account-store slice owns durable account records,
credential references, local role bindings, external identity bindings,
profile bodies and versions, storage-root references, recovery/import records,
and mutation/audit metadata.

The account-store service should expose typed reads for trusted policy
services and compare-and-set mutation methods for administrative tooling.
`SessionManager` reads account-store records only while creating or refreshing
a session, then returns a `UserSession` summary. `AuthorityBroker` uses that
summary plus account-store/profile lookups to mint caps. Ordinary workloads
must not learn more than the scoped session/profile metadata and caps they
were explicitly granted.

Initial records should stay cap-shaped:

```capnp
struct AccountRecord {
  recordId @0 :Data;
  principalId @1 :Data;
  kind @2 :PrincipalKind;
  displayName @3 :Text;
  status @4 :AccountStatus;
  credentialRefs @5 :List(Data);
  roles @6 :List(Text);
  attributes @7 :List(Attribute);
  resourceProfile @8 :ProfileRef;
  policyProfile @9 :ProfileRef;
  homeRoot @10 :StorageRootRef;
  createdAtMs @11 :UInt64;
  updatedAtMs @12 :UInt64;
  schemaVersion @13 :UInt32;
  storeEpoch @14 :UInt64;
  recordVersion @15 :UInt64;
  policyEpoch @16 :UInt64;
  previousHash @17 :Data;
  contentHash @18 :Data;
}

struct ProfileRef {
  profileId @0 :Data;
  versionId @1 :Data;
  epoch @2 :UInt64;
}

struct StorageRootRef {
  storageServiceId @0 :Data;
  rootObjectId @1 :Data;
  rootKind @2 :StorageRootKind;
  schemaVersion @3 :UInt32;
  rootVersion @4 :Data;
}

enum StorageRootKind {
  namespace @0;
}

enum AccountStatus {
  active @0;
  disabled @1;
  locked @2;
  recoveryOnly @3;
}

struct ResourceProfile {
  profileId @0 :Data;
  versionId @1 :Data;
  epoch @2 :UInt64;
  homeQuotaBytes @3 :UInt64;
  tempQuotaBytes @4 :UInt64;
  processLimit @5 :UInt32;
  threadLimit @6 :UInt32;
  capLimit @7 :UInt32;
  memoryCommitLimitBytes @8 :UInt64;
  frameGrantLimitPages @9 :UInt64;
  endpointQueueLimit @10 :UInt32;
  inFlightCallLimit @11 :UInt32;
  retired12 @12 :UInt32; # was pending IPC submission quota; do not reuse
  ringScratchLimitBytes @13 :UInt64;
  logQuotaBytesPerWindow @14 :UInt64;
  networkProfile @15 :Text;
  cpuBudgetUsPerWindow @16 :UInt64;
  cpuWindowUs @17 :UInt64;
  timerWaiterLimit @18 :UInt32;
  launcherProfile @19 :Text;
}

struct ExternalIdentityBinding {
  bindingId @0 :Data;
  provider @1 :Text;
  subjectHash @2 :Data;     # hash(provider kind, issuer, tenant, subject)
  principalId @3 :Data;
  tenant @4 :Text;
  acceptedClaims @5 :List(Text);
  expiresAtMs @6 :UInt64;
  policyProfile @7 :ProfileRef;
  resourceProfile @8 :ProfileRef;
  schemaVersion @9 :UInt32;
  storeEpoch @10 :UInt64;
  recordVersion @11 :UInt64;
  policyEpoch @12 :UInt64;
  previousHash @13 :Data;
  contentHash @14 :Data;
}
```

`homeRoot` is a persistent reference that the account/storage broker resolves
into a live `Namespace` capability at session-bundle time. It is not a path,
not a raw `Directory`, and not itself a capability. Compatibility `Directory`
views are projections returned only when a workload needs file-like APIs.

Manifest seed records and local account records may name roles and profiles,
but the resulting authority is still the CapSet returned by
`AuthorityBroker`. A disabled or locked account can authenticate only to
explicit recovery flows allowed by its account state and current policy.

### Stable ID Formats

Names are display and lookup hints only. They must not be treated as authority
or as stable cross-store identity. All durable IDs used for account-store joins
should be opaque binary values with a declared version and fixed length:

- **Local principals:** `principalId` is a 32-byte opaque random value minted
  by the local account store or imported from a trusted recovery record. User
  names, display names, POSIX names, and email addresses are attributes, not
  identifiers.
- **Account records:** `recordId` is a 32-byte opaque record identity. It may
  equal `principalId` only if the store permanently enforces one account record
  per local principal; otherwise it must be separate.
- **External bindings:** `subjectHash` is a 32-byte hash over canonical
  provider kind, issuer, tenant, and external subject. `bindingId` is a
  32-byte opaque or content-derived ID over the normalized binding tuple plus
  the local principal ID. Provider display names and group strings are not
  authority.
- **Policy and resource profiles:** `profileId` is a 32-byte opaque profile
  identity. `versionId` is a 32-byte content hash of the canonical profile
  body, schema version, parent version if any, and effective constraints.
  Profile display names such as `operator` or `guest-shell` are aliases.
- **Policy versions:** policy bundles use a 32-byte `versionId` plus a
  monotonically increasing `policyEpoch`. Brokers refuse grants when the
  session/profile summary names a stale epoch.
- **Storage roots:** `storageServiceId`, `rootObjectId`, and `rootVersion` are
  storage-service-owned opaque binary identifiers. A storage root is never a
  path or user name; the storage broker resolves it into a live `Namespace`
  only after current policy permits the grant.

### Version, Rollback, and CAS Rules

Disk-backed account-store records must be rejected unless their integrity and
freshness checks pass. The minimum record header is `schemaVersion`,
`storeEpoch`, `recordVersion`, `policyEpoch`, `previousHash`, and
`contentHash`. `schemaVersion` selects the decoder and migration policy.
`storeEpoch` is a monotonic store-wide epoch advanced for every accepted
mutation batch. `recordVersion` is monotonic per record. `policyEpoch` binds
the record to the policy/profile generation used to evaluate it.
`previousHash` chains the prior accepted canonical record bytes when a previous
record exists, and `contentHash` covers the canonical bytes excluding the
hash field itself.

Mutations use compare-and-set semantics:

```text
update(recordId, expectedStoreEpoch, expectedRecordVersion, expectedHash, patch)
  -> accepted(newStoreEpoch, newRecordVersion, newHash)
  -> stale(currentStoreEpoch, currentRecordVersion, currentHash)
  -> denied(reason)
```

Administrative tools must submit the last observed epoch, version, and hash.
The store accepts an update only when those values match the current durable
record and the new record validates against the active schema and policy
epoch. Replayed records, older store epochs, lower or equal record versions,
hash-chain breaks, unknown schema versions, profile versions not recognized by
the active policy bundle, and missing rollback metadata are fail-closed
denials. A failed check may leave the account disabled for ordinary login
while allowing only explicit recovery identities to inspect or repair it.

The account store should persist a signed or sealed store checkpoint that
records the latest `storeEpoch`, account-store installation ID, accepted
policy epoch, and root hash. If the checkpoint says a later epoch existed than
the records currently on disk, the store is in recovery mode and must not let
disk account records override manifest seed data or widen authority.

### Recovery Import and Seed Repair

Manifest seed data is the recovery source when the local account store is
missing, unreadable, or rollback-damaged. Recovery records should include
first-operator or break-glass principal IDs, recovery credential references,
profile refs, storage-root repair refs, import/export record IDs, allowed
repair operations, expiry or quorum requirements, and audit requirements.
Recovery identities are not normal operators: their default session bundle is
limited to inspecting account-store state, exporting/importing records,
disabling stale bindings, and applying exact-target repairs.

Import from seed or offline export is additive and conservative:

- preserve local `principalId`, `recordId`, profile IDs, storage-root refs,
  and external `bindingId` values when their hashes and epochs validate;
- import missing seed operators, service identities, recovery identities, and
  minimum guest/anonymous profiles needed to boot and repair the system;
- disable, not delete, external bindings whose provider, tenant, subject hash,
  policy epoch, or profile version cannot be validated;
- never auto-map a new external subject to a broader local role or profile
  than the signed seed/import record names;
- never widen caps, quotas, storage roots, roles, or approval paths as a side
  effect of recovery import;
- emit audit records for import start, source identity, records accepted,
  records preserved, records disabled, denials, and the final store epoch.

If audit storage is unavailable, recovery may continue only into a bounded
emergency mode whose transcript is written to the best available append-only
sink and whose repaired accounts remain disabled for ordinary login until an
auditable store checkpoint is committed.

## Session Startup Flow

```mermaid
flowchart TD
    Input[Login, guest, or anonymous request]
    Auth[Authentication or guest policy]
    Source[Manifest seed, account store, or external binding]
    Session[UserSession cap]
    Broker[AuthorityBroker / PolicyEngine]
    Bundle[Scoped cap bundle]
    Shell[Native, agent, or POSIX shell]
    Audit[AuditLog]

    Input --> Auth
    Auth --> Source
    Source --> Session
    Session --> Broker
    Broker --> Bundle
    Bundle --> Shell
    Broker --> Audit
    Shell --> Audit
```

The shell proposal's minimal daily cap set is a session bundle:

```text
terminal        TerminalSession
self            self/session introspection
status          read-only SystemStatus
logs            read-only LogReader scoped to this principal/session
home            Directory or Namespace scoped to account storage
launcher        restricted launcher for approved user applications
approval        ApprovalClient
```

The shell still cannot mint additional authority. It can ask
`ApprovalClient` for a plan-specific grant, and a trusted broker can return
a narrow leased capability if policy and authentication allow it.
The terminal cap is the session-scoped foreground `TerminalSession`, not the
boot debug `Console`; login hands that terminal into the shell bundle only
after authentication or explicit guest/setup policy succeeds. The concrete
default-boot login/setup flow that consumes this bundle is documented in
[boot-to-shell-proposal.md](boot-to-shell-proposal.md), and the shell-side
contract for receiving and inspecting it lives in
[shell-proposal.md](shell-proposal.md).

Detailed decomposition for manifest-seeded accounts, disk-backed account storage,
default resource bundles, local roles, RBAC, ABAC, MAC/MIC labels, POSIX
profile metadata, and external identity bindings lives in
[local-users-management.md](../backlog/local-users-management.md).

## Multi-User Workloads

capOS should support two normal multi-user patterns.

### Per-Session Subtree

The session owns a shell or supervisor subtree. Every child process receives
an explicit CapSet assembled from the session bundle plus workflow-specific
grants.

Example:

- Alice's shell receives `home = Namespace("/users/alice")`.
- Bob's shell receives `home = Namespace("/users/bob")`.
- The same editor binary launched from each shell receives different `home`
  and `terminal` caps.
- The editor cannot cross from Alice's namespace into Bob's unless a broker
  deliberately grants a sharing cap.

This is the right default for interactive applications and POSIX shells.

### Shared Service With Per-Client Session Authority

A server process may handle many users in one address space. It should not
infer authority from a caller's self-reported user name, principal ID, role
name, or endpoint label. Instead, a trusted issuer binds the subject before the
service accepts it:

- authentication or admission creates a live `SessionContext`;
- a spawned process receives exactly one immutable session context, installed
  at spawn time by `kernel/src/session_context.rs` (see
  [service-architecture-proposal.md](service-architecture-proposal.md));
- `AuthorityBroker` grants service roots or narrower facets for that session;
- endpoint calls expose privacy-preserving caller-session metadata by default;
- subject details are disclosed only when the method/call explicitly requests
  disclosure and a broker/service-granted disclosure scope allows the named
  fields;
- quota donations or accounting caps may accompany service grants when
  server-side state needs explicit resource backing.

The service uses the caller session reference, disclosed subject facts, and
service-local records to select scoped storage, enforce per-client limits, emit
audit records, and return narrowed caps. Endpoint badges are not a normal
identity mechanism; any remaining badge-shaped kernel field should be treated
as internal endpoint transport state during the migration. This is the right
shape for HTTP services, databases, log services, terminals, and shared
daemons.

### Service Accounts

Service identities are principals too. They are usually non-interactive and
receive caps from init, a supervisor, or a deployment manifest rather than
from a human login flow.

Service-account policy should be explicit:

- which binary or measured package may use the identity,
- which supervisor may spawn it,
- which caps are in its base bundle,
- which caps it may request from a broker,
- which audit stream records its activity.

Service account records may be manifest seeded or stored in the local account
store, but their sessions should receive no terminal and no interactive bundle.
They launch as workloads with measured binary, supervisor, service name,
network/IPC, log, state namespace, and key-use constraints.

## Anonymous, Guest, and Pseudonymous Access

These are distinct profiles.

### Empty Cap Set

An untrusted ELF with an empty CapSet is not a user session. It is the
roadmap's "Unprivileged Stranger": code with no useful authority. It can
terminate itself and interact with the capability transport, but it cannot
reach a resource because it has no caps. The visible proof was achieved by
commit `d4016ab` at `2026-04-22 16:35 UTC`.

### Anonymous

Anonymous means unauthenticated and usually remote or programmatic. It should
receive a random ephemeral principal ID and a very small cap bundle.

Typical properties:

- no durable home namespace by default,
- strict CPU, memory, outstanding-call, and log quotas,
- short session expiry,
- no elevation path except "authenticate" or "create account",
- audit records keyed by ephemeral session ID and network/service context.

### Guest

Guest means an interactive local profile with weak or no authentication.

Typical properties:

- terminal/UI access,
- temporary namespace,
- optional ephemeral home reset on logout,
- restricted launcher,
- no administrative approval path unless policy grants one explicitly,
- clearer user-facing affordance than anonymous.

### Pseudonymous

Pseudonymous means durable identity without necessarily naming a human. A
public key, passkey, service token, or cloud identity can select the same
principal across sessions. This can receive persistent storage and quotas
while still remaining separate from a verified human account.

External pseudonymous sessions require explicit admission configuration. A
binding either maps the external subject to an existing local account or allows
auto-creation of a tenant-scoped account with named policy and resource
profiles. Durable storage is granted only through that local principal mapping
and a broker-minted storage cap.

## POSIX Compatibility

POSIX user concepts are compatibility metadata, not authority.

- `uid`, `gid`, user names, groups, `$HOME`, `/etc/passwd`, `chmod`, and
  `chown` live in `libcapos-posix`, a filesystem service, or a profile
  service.
- `open("/home/alice/file")` succeeds only if the process has a `Directory`
  or `Namespace` cap that resolves that synthetic path.
- `setuid` cannot grant new caps. At most it asks a compatibility broker to
  replace the process's POSIX profile or launch a new process with a different
  cap bundle.
- POSIX ownership bits may influence one filesystem service's policy, but
  they cannot authorize access to caps outside that service.

This lets existing programs inspect plausible user metadata without making
Unix permission bits the capOS security model.

## Policy Models

RBAC, ABAC, and mandatory access control fit capOS as grant-time and
mint-time policy. They should mostly live in ordinary userspace services:
`AuthorityBroker`, `PolicyEngine`, `SessionManager`, `RoleDirectory`,
`LabelAuthority`, `AuditLog`, and service-specific attenuators.

The kernel should keep enforcing capability ownership, generation, transfer
rules, revocation epochs, resource ledgers, and process isolation. It should
not evaluate roles, attributes, or label lattices on every capability call.

### RBAC

Role-based access control maps principals or sessions to named role sets.
Roles select cap bundles and approval eligibility.

Examples:

- `developer` can receive a launcher for development tools and read-only
  service logs.
- `net-operator` can request a leased `ServiceSupervisor(net-stack)`.
- `storage-admin` can request repair caps for selected storage volumes.

Implementation shape:

```capnp
interface RoleDirectory {
  rolesFor @0 (principal :Data) -> (roles :List(Text));
}

interface AuthorityBroker {
  request @0 (
    session :UserSession,
    plan :ActionPlan,
    requestedCaps :List(CapRequest),
    durationMs :UInt64
  ) -> (grant :ApprovalGrant);

  # Mint an ApprovalInbox for the bound session. The broker policy
  # decides whether the requesting session is allowed to triage
  # approvals and which entries are visible (own requests only,
  # role-scoped queue, multi-party reviewer queue).
  inbox @1 (
    session :UserSession
  ) -> (inbox :ApprovalInbox);
}
```

The detailed `ActionPlan`, `ActionStep`, `CapRequest`, `GrantedCap`,
`ApprovalInbox`, `ApprovalEntry`, and `ApprovalListener` schemas live
in [shell-proposal.md](shell-proposal.md) under
*Approval and Authentication*. The broker is the single producer for
both `ApprovalGrant` (the requester-side handle) and `ApprovalInbox`
(the decider-side handle); they meet only at the broker, never on a
shared transport channel.

Roles do not bypass capabilities. They only let a broker decide whether it
may mint or return particular scoped caps.

The role/attribute/decision split matches the ITU-T X.812 *Access control
framework* (= ISO/IEC 10181-3) decomposition into ADF (access-control
decision function) and AEF (access-control enforcement function). In
capOS terms:

- The **AEF** is the `CapObject::call` dispatch plus wrapper caps: the
  enforcement point that cannot be bypassed because it is the only path
  to the underlying object.
- The **ADF** is the `PolicyEngine` / `AuthorityBroker`: it evaluates
  a decision request and returns a capability (or refuses) rather than
  returning a boolean that downstream code might ignore.

The ADF/AEF split is why capOS can make `PolicyDecision` a
cap-minting input rather than a per-call allow/deny flag — the
enforcement point is already structural (you need a cap to reach the
object) and the decision point returns the cap.

### Remote Client Bundles

Remote programmatic and GUI clients consume the same identity and policy model
as shells, but they need a different bundle shape. A remote host app may
authenticate with password, public key, OIDC, passkey/WebAuthn, mTLS,
guest/anonymous admission, or a service/workload credential. After admission,
the broker returns a remote-client bundle whose entries are exported as
Cap'n Proto RPC object references by a per-session gateway worker.

Those references are live capability proxies, not bearer tokens and not local
cap-table metadata. A remote bundle may include `session`, `systemInfo`, and
specific service caps such as `chat`, `paperclips`, or command surfaces. It
should not inherit terminal, launcher, broad storage, raw network, key-vault,
credential-store, or process-spawn authority merely because an operator shell
profile would receive some of those caps. The detailed transport and lifetime
rules live in
[remote-session-capset-client-proposal.md](remote-session-capset-client-proposal.md).

### ABAC

Attribute-based access control evaluates a richer decision context:

- subject attributes: principal kind, roles, auth strength, session age,
  device posture, locality,
- action attributes: requested method, target service, destructive flag,
  requested duration,
- object attributes: service name, namespace prefix, data class, owner
  principal, sensitivity,
- environment attributes: time, boot mode, recovery mode, network location,
  cloud instance metadata, quorum state.

ABAC is useful for contextual narrowing:

- allow log read only for the caller's session unless break-glass policy is
  active,
- issue `ServiceSupervisor(net-stack)` only with fresh hardware-key auth,
- grant `Namespace("/shared/project")` read-write only during a maintenance
  window,
- deny network caps to guest sessions.

ABAC decisions should return capabilities, wrappers, or denials. They should
not create hidden ambient checks downstream.

OAuth2 scopes and OIDC claims (`acr`, `amr`, `groups`, tenant-specific
fields) are ABAC *inputs*. The broker consumes them alongside session
freshness, object attributes, and environment state to pick a cap
bundle or decline. They never authorize capability calls directly,
and they do not create a downstream check outside the broker's
decision path. See
[oidc-and-oauth2-proposal.md](oidc-and-oauth2-proposal.md).

### ABAC Policy Engine Choices

Do not invent a policy language first. The capOS-native interface should be
small and capability-shaped, while the broker implementation can start with a
mainstream engine behind that interface.

Recommended order:

1. **Cedar for runtime authorization.**
   Cedar's request shape is already close to capOS:
   `principal`, `action`, `resource`, and `context`. It supports RBAC and
   ABAC in one policy set, has schema validation, and has a Rust
   implementation. That makes it the best fit for `AuthorityBroker` and
   `MacBroker` service prototypes.

2. **OPA/Rego for host-side and deployment policy.**
   OPA is widely used for cloud, Kubernetes, infrastructure-as-code, and
   admission-control style checks. It is useful for validating manifests,
   cloud metadata deltas, package/deployment policies, and CI rules. The
   Wasm compilation path is worth tracking for later capOS-side execution,
   but OPA should not be the first low-level runtime dependency.

3. **Casbin for quick prototypes only.**
   Casbin is useful for simple RBAC/ABAC experiments and has Rust bindings,
   but its model/matcher style is less attractive as a long-term capOS policy
   substrate than Cedar's schema-validated authorization model.

4. **XACML for interoperability and compliance, not native policy.**
   XACML remains the classic enterprise ABAC standard. It is useful as a
   conceptual reference or import/export target, but it is too heavy and
   XML-centric to be the native capOS policy language.

The capOS service boundary should hide the selected engine:

```capnp
interface PolicyEngine {
  decide @0 (request :PolicyRequest) -> (decision :PolicyDecision);
}

struct PolicyRequest {
  principal @0 :PrincipalInfo;
  action @1 :Text;
  resource @2 :ResourceRef;
  context @3 :List(Attribute);
}

struct PolicyDecision {
  allowed @0 :Bool;
  reason @1 :Text;
  leaseMs @2 :UInt64;
  constraints @3 :List(Attribute);
}
```

`PolicyDecision` is still not authority. It is input to a broker that returns
actual caps, wrapper caps, leased caps, or denial.

References:

- Cedar policy language docs: <https://docs.cedarpolicy.com/>
- Amazon Verified Permissions concepts:
  <https://docs.aws.amazon.com/verifiedpermissions/latest/userguide/terminology.html>
- Open Policy Agent docs: <https://www.openpolicyagent.org/docs>
- Casbin supported models: <https://www.casbin.org/docs/supported-models>
- OASIS XACML technical committee:
  <https://www.oasis-open.org/committees/xacml/>
- ITU-T Rec. X.812 (11/95) — Information technology - Open Systems
  Interconnection - Security frameworks for open systems: Access control
  framework. ADF/AEF terminology.
- ITU-T Rec. X.741 (10/95) — Systems Management: Objects and attributes
  for access control. Concrete managed-object attributes for ACLs, ACIs,
  default access, and access-decision inputs.

### Mandatory Access Control

Mandatory access control is non-bypassable policy set by the system owner or
deployment, not discretionary sharing by ordinary users. In capOS, MAC should
be implemented as mandatory constraints on cap minting, attenuation,
transfer, and service wrappers.

Examples:

- a `Secret` cap labeled `high` cannot be transferred to a workload labeled
  `low`,
- a `LogReader` for security logs cannot be granted to a guest session even
  if an application asks,
- a recovery shell can inspect storage read-only but cannot write without a
  separate exact-target repair cap,
- cloud user-data can add application services but cannot grant
  `FrameAllocator`, `DeviceManager`, or raw networking authority.

Implementation components:

```capnp
enum Sensitivity {
  public @0;
  internal @1;
  confidential @2;
  secret @3;
}

struct SecurityLabel {
  domain @0 :Text;
  sensitivity @1 :Sensitivity;
  compartments @2 :List(Text);
}

interface LabelAuthority {
  labelOfPrincipal @0 (principal :Data) -> (label :SecurityLabel);
  labelOfObject @1 (object :Data) -> (label :SecurityLabel);
  canTransfer @2 (
    from :SecurityLabel,
    to :SecurityLabel,
    capInterface :UInt64
  ) -> (allowed :Bool, reason :Text);
}
```

For ordinary services, MAC can be enforced by brokers and wrapper caps. For
high-assurance boundaries, the remaining question is whether transfer labels
need kernel-visible hold-edge metadata. That should be added only for a
concrete mandatory policy that cannot be enforced by controlling all grant
paths through trusted services.

The attribute model borrows from ITU-T X.741, which enumerates the
managed-object attributes a directory-based access-control system
tracks: ACL entries, access-control information (ACI), default access,
initiator ACI, target ACI, and access-decision outcome. X.741 targets
the X.500 directory, so the schema does not port directly, but the
attribute taxonomy is a good completeness check for what
`LabelAuthority` and `PolicyEngine` requests should expose to a
decision engine.

### GOST-Style MAC and MIC

Russian GOST framing is stricter than the generic "MAC means labels" summary.
The relevant standards split at least two policies that capOS should keep
separate:

- **Mandatory access control** for confidentiality.
  ГОСТ Р 59383-2021 describes mandatory access control as classification
  labels on resources and clearances for subjects. ГОСТ Р 59453.1-2021 goes
  further: a formal model that includes users, subjects, objects, containers,
  access levels, confidentiality levels, subject-control relations, and
  information flows. The safety goal is preventing unauthorized flow from an
  object at a higher or incomparable confidentiality level to a lower one.

- **Mandatory integrity control** for integrity.
  ГОСТ Р 59453.1-2021 treats this separately from confidentiality MAC. The
  integrity model constrains subject integrity levels, object/container
  integrity levels, subject-control relationships, and information flows so
  lower-integrity subjects cannot control or corrupt higher-integrity subjects.

For capOS, this should map to labels on sessions, objects, wrapper caps, and
eventually hold edges:

```capnp
struct ConfidentialityLabel {
  level @0 :Text;              # e.g. public, internal, secret.
  compartments @1 :List(Text);
}

struct IntegrityLabel {
  level @0 :Text;              # ordered by deployment policy.
  domains @1 :List(Text);
}

struct MandatoryLabel {
  confidentiality @0 :ConfidentialityLabel;
  integrity @1 :IntegrityLabel;
}
```

Capability methods need a declared flow class. capOS cannot rely on generic
`read` and `write` syscalls:

- read-like: `File.read`, `Secret.read`, `LogReader.read`;
- write-like: `File.write`, `Namespace.bind`, `ManifestUpdater.apply`;
- control-like: `ProcessSpawner.spawn`, `ServiceSupervisor.restart`;
- transfer-like: `CAP_OP_CALL`, `CAP_OP_RETURN`, and result-cap insertion
  when they carry caps or data across labeled domains.

Initial rules can be expressed as broker/wrapper checks:

```text
read data-bearing cap:
  subject.clearance dominates object.classification

write data-bearing cap:
  target.classification dominates source.classification
  # no write down

control process or supervisor:
  controlling subject is same label, or is an explicitly trusted subject

integrity write/control:
  writer.integrity >= target.integrity
```

This is not enough for a GOST-style formal claim, because uncontrolled cap
transfer can bypass the broker. A higher-assurance design needs:

- kernel object identity for every labeled object,
- label metadata on kernel objects or per-process hold edges,
- transfer-time checks for copy, move, result caps, and endpoint delivery,
- explicit trusted-subject/declassifier caps,
- an audit trail for every label-changing or declassifying operation,
- a formal state model covering users, subjects, objects, containers, access
  rights, accesses, and memory/time information flows.

The proposal therefore has two levels:

- **Pragmatic capOS MAC/MIC:** userspace brokers and wrapper caps enforce
  labels on grants and method calls.
- **GOST-style MAC/MIC:** a formal information-flow model plus kernel-visible
  labels/hold-edge constraints for transfers that cannot be forced through
  trusted wrappers. See
  [formal-mac-mic-proposal.md](formal-mac-mic-proposal.md) for the dedicated
  abstract-automaton and proof track.

References:

- ГОСТ Р 59383-2021, access-control foundations:
  <https://lepton.ru/GOST/Data/752/75200.pdf>
- ГОСТ Р 59453.1-2021, formal access-control model:
  <https://meganorm.ru/Data/750/75046.pdf>

### Composition Order

When policies compose, use this order:

1. Mandatory policy defines the maximum possible authority.
2. RBAC selects coarse eligibility and default bundles.
3. ABAC narrows the decision for context, freshness, object attributes, and
   requested duration.
4. The broker returns specific capabilities or denies the request.
5. Audit records the plan, decision, grant, use, release, and revocation.

The composition result is still a CapSet, leased cap, wrapper cap, or denial.

## Service Architecture

The policy stack should be decomposed into ordinary capOS services. Init or a
trusted supervisor grants broad authority only to the small services that need
to mint narrower caps.

### SessionManager

Creates and manages session metadata/control caps:

- `guest()` for local guest sessions,
- `anonymous(purpose)` for ephemeral unauthenticated callers,
- `login(method, proof)` for authenticated users,
- `renew(session, proof, requestedDurationMs)` for narrow continuation or
  recovery when policy allows it,
- `logout(session)` through the `UserSession` control cap.

The first implementation can be manifest-seed backed. It does not need a
persistent account database, but its seed records must use the same principal,
account, policy-profile, and resource-profile vocabulary as the later local
account store. `UserSession` should describe the principal, session ID, policy
profile, resource profile, auth strength, expiry, and audit context. It should
not be a general-purpose authority vending machine unless it was itself minted
as a narrow wrapper around a fixed cap bundle. Session IDs should come from the
same dedicated entropy source that the bootstrap login/setup flow in
[boot-to-shell-proposal.md](boot-to-shell-proposal.md) uses for credential
salts and setup tokens; if fresh randomness is unavailable, authenticated
session creation should fail closed instead of recycling predictable IDs.

SessionManager should own the mutable liveness cell for sessions it mints. The
kernel-installed process `SessionContext` (owned by
`kernel/src/session_context.rs`; see
[service-architecture-proposal.md](service-architecture-proposal.md)) remains
immutable; renewal changes the cell or produces a successor session, not a new
subject label inside the same process. This is the mechanism that makes
long-running shells usable without treating fixed short wall-clock expiry as
the only safety boundary.

Safer first split:

```text
SessionManager -> UserSession metadata cap
AuthorityBroker(session, policyProfile, resourceProfile) -> base cap bundle
Supervisor/Launcher -> spawn shell with that bundle
```

### AuthorityBroker

The broker owns or receives powerful caps from init/supervisors and returns
narrow caps after RBAC, ABAC, and mandatory checks.

Examples:

- broad `ProcessSpawner` -> `RestrictedLauncher(allowed = ["shell", "editor"])`,
- broad `NamespaceRoot` -> `Namespace("/users/alice")`,
- broad `ServiceSupervisor` -> `LeasedSupervisor("net-stack", expires = 60s)`,
- broad `BootPackage` -> `BinaryProvider(allowed = ["shell", "editor"])`.

The broker is the normal policy decision and cap minting point.

### AuditLog

Append-only audit interface. Initially this can write to serial or a bounded
log buffer; later it should be Store-backed.

Record at least:

- session creation,
- cap request,
- policy input summary,
- policy decision,
- cap grant,
- cap release or revocation,
- denial,
- declassification or relabel operation.

Audit entries must not contain raw auth proofs, private keys, bearer tokens,
or broad environment dumps. For auth/session flows, the initial backend should
record opaque credential/token record IDs, volatility flags, and policy/result
codes rather than secret-bearing payloads. Failed pre-auth attempts should log
only a terminal-local event ID and generic failure class; do not emit
principal-identifying fields to the serial-backed path before authentication
actually succeeds.

### RoleDirectory

Role lookup should start static and boot-config backed:

```text
guest -> guest-shell
alice -> developer
ops -> net-operator
net-stack -> service:network
```

This is enough for early RBAC bundles. Dynamic role assignment moves into the
local account store once persistent storage and administrative tooling exist.
Provider groups are not imported as roles automatically; a binding rule may
map a provider group to a local role only for a named provider/tenant, expiry,
and policy version.

### LabelAuthority

Owns the label lattice and dominance checks. In the pragmatic phase, it is a
userspace dependency of brokers and wrappers. In a GOST-style phase, the
same lattice needs a kernel-visible representation for transfer checks.

### Wrapper Caps

Wrappers are the main mechanism. Prefer them over per-call ACL checks in a
central service:

- `RestrictedLauncher` wraps `ProcessSpawner`.
- `ScopedNamespace` wraps a broader namespace/store.
- `ScopedLogReader` filters by session ID or service subtree.
- `LeasedSupervisor` wraps a broader supervisor with expiry and target binding.
- `ApplicationManifestUpdater` rejects kernel/device/service-manager grants.
- `LabelledEndpoint` enforces declared data-flow and control-flow constraints.

This keeps authority visible in the capability graph.

### Bootstrap Sequence

Early boot can be static:

```text
init
  -> starts AuditLog
  -> starts SessionManager
  -> starts AuthorityBroker with broad caps
  -> asks broker for a system, guest, or operator shell bundle
  -> spawns shell through a restricted launcher
```

Before durable storage exists, policy config comes from `BootPackage` /
manifest config. Early authentication may still use bootstrap verifier or
public-credential records plus guest/anonymous/local-presence profiles, but it
must keep fresh-entropy requirements fail-closed and treat any RAM-only
credential or disable-state changes as volatile.

## Revocation, Audit, and Quotas

User/session policy depends on the Stage 6 authority graph work:

- one-session-per-process plus privacy-preserving endpoint caller-session
  metadata lets shared services distinguish session/client relations; receiver
  selectors are only routing metadata,
- mutable session liveness cells distinguish live, logged-out, revoked,
  expired, and recovery-only sessions without relabeling running processes,
- resource ledgers and session quotas prevent denial-of-service through
  session creation,
- `CAP_OP_RELEASE` and process-exit cleanup reclaim local hold edges,
- epoch revocation lets a broker invalidate leased or compromised caps,
- renewal mints or refreshes session/grant leases under policy; it must not
  revive stale ordinary grants by accident,
- audit logs record the cap grant and release lifecycle.

The cross-cutting quota model lives in
[resource-accounting-proposal.md](resource-accounting-proposal.md). Account
and session resource profiles are templates; brokers, supervisors, and resource
owners translate them into concrete ledgers and wrapper caps.

Audit should record identity and policy metadata, but it should not contain
secrets, raw authentication proofs, or broad environment dumps.

## Implementation Plan

1. **Document the model.**
   Keep user identity out of the kernel architecture, publish the
   principal/user/account/profile/session/role/workload vocabulary, and link
   this proposal from the shell, service, storage, and roadmap docs.

2. **Manifest-seeded account and profile schema.**
   Define boot-package seed records for first operators, recovery identities,
   service identities, guest policy, policy profiles, resource profiles, and
   initial role bindings. Validate that seed data names policy inputs only and
   does not grant ordinary accounts privileged kernel caps directly.

3. **Session-aware native shell profile.**
   Treat the shell proposal's minimal daily cap set as a session bundle.
   Add `self/session` introspection and scoped `logs`/`home` caps once the
   underlying services exist.

4. **Authority broker and audit log.**
   Add `ActionPlan`, `ActionStep`, `CapRequest`, `ApprovalClient`,
   `ApprovalInbox`, `ApprovalEntry`, leased grant records, and an
   append-only audit path. The shell-proposal *Approval and
   Authentication* section defines the schemas; the broker is the
   single producer for both the requester-side `ApprovalGrant` and
   the decider-side `ApprovalInbox`. Start with RBAC-style
   policy/resource profile bundles and explicit local authentication.

5. **Local account store and external bindings.**
   Add a Store/Namespace-backed `AccountStore` for account records, credential
   references, role bindings, external identity bindings, policy versions,
   resource profiles, and storage-root references. Include version and rollback
   checks before treating disk-backed account mutation as durable.

6. **ABAC policy engine.**
   Extend the broker decision with session freshness, auth strength, object
   attributes, requested duration, and environment state. Prefer Cedar for
   the runtime broker interface; use OPA/Rego for host-side manifest and
   deployment checks. Keep decisions visible in audit records.

7. **Mandatory policy labels.**
   Add pragmatic labels to policy-managed services and wrappers. Keep
   confidentiality and integrity separate. Defer kernel-visible labels until
   a specific MAC/MIC policy cannot be enforced by trusted grant paths.

8. **Guest and anonymous demos.**
   Show a guest shell with `terminal`, `tmp`, and restricted `launcher`, and
   show an anonymous workload with strict quotas and no persistent storage.

9. **POSIX profile adapter.**
   Provide synthetic `uid/gid`, `$HOME`, `/etc/passwd`, and cwd behavior from
   session policy/resource profiles and granted namespace caps.

10. **GOST-style formalization checkpoint.**
   If capOS later claims GOST-style MAC/MIC, write the abstract state model
   before implementation: users, subjects, objects, containers, access rights,
   accesses, labels, control relations, and information flows. Then decide
   which labels must become kernel-visible.

## Non-Goals

- No kernel `uid/gid`.
- No ambient `root`.
- No global login namespace in the kernel.
- No authorization from serialized identity structs.
- No model-visible authentication secrets.
- No POSIX permission bits as system-wide authority.
- No per-call role/attribute/label interpreter in the kernel fast path.
- No claim of GOST-style MAC/MIC until the formal model and transfer
  enforcement story exist.

## Open Questions

- Which session interfaces are needed before persistent storage exists?
- Which audit store is acceptable before durable storage and replay exist?
- Which MAC policies, if any, justify kernel-visible hold-edge labels?
- How should remote capnp-rpc or future OCapN/CapTP-style identities map into
  local principals? Transport identity, locator hints, and routing metadata are
  not local user/session identity by themselves; remote peers should enter
  through broker/session policy rather than raw protocol fields. See
  [Cloudflare, Cap'n Proto, Workers RPC, and Cap'n Web](../research/cloudflare-capnproto-workers.md)
  and
  [Spritely, OCapN, and CapTP](../research/spritely-captp-ocapn.md).
- Should the first broker prototype embed Cedar directly, or use a simpler
  hand-written evaluator until the policy surface stabilizes?
