# Identity and admission

Identity in Witbitz is not "there is an account in our database." A Space is self-authenticated: its link carries the
public material needed to verify who belongs, and members hold the corresponding private credentials.

This page is the short reader path. The canonical deep dive remains [The Verified Room](./room-link-auth.md).

---

## Base membership: hold the room key

Every Space has a room key `mk`. The client mints it and stores only a commitment on the server:

```text
commit(mk) = base64url(sha256(mk))
```

When a member acts, the render checks that the presented key matches the stored commitment. A right key proves the
caller holds the Space link. A wrong key opens nothing.

This proves "a member is present." It does not by itself prove *which* member.

## Per-member identity: signed entries

To attribute entries to a specific person, each member can hold a signing key. The room's public invite key verifies
the member grant, and the member's public signing key verifies the entry.

The signed core is conceptually:

```json
{
  "room": "sp-example",
  "author": "ada@example.com",
  "ts": 1785350400000,
  "kind": "text",
  "id": "c:...",
  "text": "I approve this."
}
```

Because the author is inside the signed core, a rename or forged attribution fails verification.

## Admission families

Witbitz inherits the credential model from Kibitz and carries it to async Spaces.

| Family | What it proves |
|---|---|
| **Key possession** | The caller holds the Space key. |
| **Signed invite** | A creator or room authority granted this member access. |
| **OIDC / Google** | The member authenticated as an allow-listed identity. |
| **Agent key** | This agent key is allowed by the room. |
| **Capability grant** | This participant may perceive or act in specific ways. |
| **Owner policy** | The app owner mandated admission tiers for the room. |

## Gated Spaces

On an email-gated Space, the link alone is not enough. Reads and writes both require an allow-listed sign-in. That is
why the verification page starts with token-less `poll` and `turn` requests that return `403`.

The allow-list itself lives in the sealed config, so the platform does not read the membership list at rest.

Run the check: [Verify it yourself](./verify.md)

## Who sets admission

There are two layers:

| Layer | Decides |
|---|---|
| **The Space creator** | Ordinary open, invite, or email-gated rooms. |
| **The app owner** | Owner-governed rooms where every room must obey a signed App Policy. |

The owner rule is the enterprise path: an organization can require its own identity provider and tiered capabilities
without giving itself access to room content at rest.

Read: [Owner-governed rooms](./the-owner-rule.md)

## Live vs designed

| Piece | Status |
|---|---|
| Key-possession membership | Shipped |
| Solo signed identity | Live and verified |
| Email-gated reads and writes | Live and checkable |
| Agent-key admission | Deployed |
| Scoped agent reads | Deployed |
| 2-of-2 per-member signed path | Not yet |
| Crypto-strong single-Space read revocation by epoch re-key | Designed; Bridge has epoch re-key |

For the complete status table, see [The Verified Room](./room-link-auth.md#12-status).

## Code map

| Concern | File |
|---|---|
| Room key commit and envelope | `agent/envelope.mjs` |
| Client key/link creation | `spaces/public/spaceConfig.js` |
| Signed entry/grant | `agent/spaceEntrySig.mjs`, `spaces/public/entrySig.js` |
| OIDC verification | `agent/spaceOidc.mjs` |
| Admission enforcement | `agent/spaceService.mjs`, `agent/spaceHandler.mjs` |
