Skip to content

feat(identity): Identity entity — substrate's universal actor identity (Slice 1 of #142)#1521

Merged
joelteply merged 1 commit into
canaryfrom
8528f413/slice-1-of-142-identity-becomes-an-orm-e
Jun 4, 2026
Merged

feat(identity): Identity entity — substrate's universal actor identity (Slice 1 of #142)#1521
joelteply merged 1 commit into
canaryfrom
8528f413/slice-1-of-142-identity-becomes-an-orm-e

Conversation

@joelteply
Copy link
Copy Markdown
Contributor

Summary

Slice 1 of #142 (BaseUser/Context hierarchy) — promotes the substrate's actor identity into an ORM-backed Identity entity using the #[derive(Entity)] macro landed in #1519.

Per Joel's morning escalation (2026-06-04):

  • "Each UNIQUE identity per persona, per you per me. Not shared."
  • "Yes airc is CORE LEVEL. this is the session etc."
  • "This is like Android context and must be fixed."
  • "We agreed our base data type for anything storable would be rust base entity."

The doctrine: airc identity IS the session abstraction. Every actor instance — persona, Claude Code session, Codex session, Joel terminal, jtag CLI invocation, web user — has its own UNIQUE airc identity. Not shared. The universal handle is Context (Android-Context analogue: ubiquitous, mandatory, carries identity + services + captures).

This PR ships the foundational data type. The Context struct that wraps it is Slice 2.

What changes

New: src/workers/continuum-core/src/identity/mod.rs

  • IdentityKind — Persona | Claude | Codex | Human | Jtag | Web. Every kind is a first-class substrate citizen per [[airc-is-the-session-not-a-feature]].
  • IdentitySource — ResumedFromDisk | FreshlyMinted. Renamed from PersonaIdentitySource (same enum, universal kind shape).
  • Identity — ORM entity. id == airc peer_id per [[persona-identity-derives-from-source-id]] (your cryptographic identity IS your substrate identity). Carries kind, agent_name, home_path, default_room, source. BaseEntity columns auto-injected via Pattern A (#[entity(primary_key)] id: Uuid).

Modified: src/workers/continuum-core/src/lib.rs

pub mod identity; registered.

Modified: src/workers/continuum-core/src/orm/store.rs

Lifted fresh_adapter out of #[cfg(test)] mod tests to module scope (still #[cfg(test)] gated, pub(crate)) so cross-module tests can lease the same fixture per [[test-fixtures-are-system-primitives]]. In-mod test callers rewritten to super::fresh_adapter(). No behavior change to existing orm tests (95 pass).

Test plan

  • identity::tests::identity_schema_is_derived — schema collection name, BaseEntity columns, camelCase field naming via serde rename
  • identity::tests::identity_round_trips_through_orm — save + find_by_id + find_all + cross-kind filter
  • 3 ts-rs export_bindings_* tests for Identity / IdentityKind / IdentitySource — TS bindings generate cleanly
  • orm::* family — 95/95 pass (fresh_adapter lift is non-breaking)
  • CI green (will verify in PR checks)

What this does NOT do (out of scope — sequenced as follow-up slices)

  • Slice 2 — Context struct wrapping Identity + services + captures
  • Slice 3 — Bootstrap paths per IdentityKind (Claude Code session minting; jtag CLI minting; etc.)
  • Slice 4 — &ctx ubiquitous refactor across substrate APIs
  • Slice 1B — Migration of PersonaInstanceInfo callers to read from Identity table (kept separate to make THIS PR reviewable; PersonaInstanceInfo continues working unchanged)

Doctrine

  • [[airc-is-the-session-not-a-feature]] — the morning's new memory
  • [[no-sql-everything-through-orm-entities]] — entity, not JSON file
  • [[persona-identity-derives-from-source-id]] — peer_id IS the id
  • [[test-fixtures-are-system-primitives]] — fresh_adapter promoted to substrate fixture

Parent

Task #142 (Substrate BaseUser/Context hierarchy — persona/human/web all derive). The "BaseUser" naming in #142 predates this morning's "this is like Android context" framing; functionally Context = BaseUser. Future slices will land under #142.

Targets canary.

🤖 Generated with Claude Code

…y, ORM-backed (Slice 1 of #142)

Joel 2026-06-04 morning, in a sequence of escalations:
- "The symmetry is important. Airc identities were supposed to be
  built into context for each persona each a different user."
- "Each UNIQUE identity per persona, per you per me. Not shared."
- "Yes airc is CORE LEVEL. this is the session etc."
- "What differentiates each persons their own airc workspaces like
  you and codex is airc identity. This is like Android context and
  must be fixed."
- "We agreed our base data type for anything storable would be rust
  base entity."

The doctrine: airc identity IS the session abstraction. Every actor
instance — persona, Claude Code session, Codex session, Joel
terminal, jtag CLI invocation, web user — has its own UNIQUE airc
identity (peer_id + keypair + home). Not shared. The substrate's
universal handle is `Context` (Android-Context analogue): ubiquitous,
mandatory, carries identity + services + captures.

This commit lands the foundational data type: `Identity` as an
ORM-backed entity, using the `#[derive(Entity)]` macro from #1519.
Pattern A (canonical): `#[entity(primary_key)] id: Uuid` pulls in
BaseEntity columns automatically. `id == airc peer_id` per
[[persona-identity-derives-from-source-id]] — your airc cryptographic
identity IS your substrate identity, not a separate continuum-side
surrogate.

## What lands

### `continuum-core/src/identity/mod.rs` (new)

- `IdentityKind` enum: Persona | Claude | Codex | Human | Jtag | Web.
  Every kind is a first-class substrate citizen per
  [[airc-is-the-session-not-a-feature]]; the tag lets downstream code
  branch when actor type matters.
- `IdentitySource` enum: ResumedFromDisk | FreshlyMinted. Renamed
  from `PersonaIdentitySource` because the same enum now applies
  to every IdentityKind, not just Persona.
- `Identity` struct: ORM entity carrying id (= peer_id), kind,
  agent_name, home_path, default_room, source. Foreign-keyable from
  every other entity that needs to record "which citizen did this."
  Derived via `#[derive(Entity)]`; schema IS the struct.

### `continuum-core/src/lib.rs`

- `pub mod identity;` registered.

### `continuum-core/src/orm/store.rs`

- Lifted `fresh_adapter` out of `#[cfg(test)] mod tests` to
  module-scope (still `#[cfg(test)]` gated, `pub(crate)`) so
  cross-module tests can lease the same fixture per
  [[test-fixtures-are-system-primitives]]. In-mod test callers
  rewritten to `super::fresh_adapter()`.

## Tests

8 identity tests pass:
- `identity_schema_is_derived` — schema introspection: collection
  name, BaseEntity columns (`id`, `createdAt`, `updatedAt`),
  declared fields (camelCase via serde rename).
- `identity_round_trips_through_orm` — save + find_by_id + find_all.
  Cross-kind: Persona + Claude rows persist, are decodable, can be
  manually filtered by kind. Foundation for query-by-room when the
  predicate-pushdown layer lands.
- 3 ts-rs `export_bindings_*` tests for Identity / IdentityKind /
  IdentitySource — TS bindings generate cleanly.

ORM family unchanged: 95 tests pass (the `fresh_adapter` lift
doesn't regress anything).

## What this slice does NOT do (out of scope)

- `Context` struct wrapping Identity + services + captures
  (Slice 2 of #142)
- Bootstrap paths per IdentityKind — fresh Claude Code session
  minting its own Identity row + airc home; jtag CLI invocation
  minting ephemeral; etc. (Slice 3)
- `&ctx` ubiquitous refactor across substrate APIs (Slice 4)
- Migration of `PersonaInstanceInfo` callers to read from Identity
  table (Slice 1B, focused follow-up to keep this PR reviewable)

## Doctrine

- [[airc-is-the-session-not-a-feature]] — Identity IS the session
- [[no-sql-everything-through-orm-entities]] — entity, not JSON file
- [[persona-identity-derives-from-source-id]] — peer_id IS the id
- [[organization-purity-as-we-migrate]] — same enum across kinds
- [[test-fixtures-are-system-primitives]] — fresh_adapter promoted

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant