feat(identity): Identity entity — substrate's universal actor identity (Slice 1 of #142)#1521
Merged
joelteply merged 1 commit intoJun 4, 2026
Conversation
…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>
This was referenced Jun 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Slice 1 of #142 (BaseUser/Context hierarchy) — promotes the substrate's actor identity into an ORM-backed
Identityentity using the#[derive(Entity)]macro landed in #1519.Per Joel's morning escalation (2026-06-04):
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.rsIdentityKind— 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 fromPersonaIdentitySource(same enum, universal kind shape).Identity— ORM entity.id == airc peer_idper[[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.rspub mod identity;registered.Modified:
src/workers/continuum-core/src/orm/store.rsLifted
fresh_adapterout of#[cfg(test)] mod teststo 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 tosuper::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 renameidentity::tests::identity_round_trips_through_orm— save + find_by_id + find_all + cross-kind filterexport_bindings_*tests for Identity / IdentityKind / IdentitySource — TS bindings generate cleanlyorm::*family — 95/95 pass (fresh_adapter lift is non-breaking)What this does NOT do (out of scope — sequenced as follow-up slices)
Contextstruct wrapping Identity + services + captures&ctxubiquitous refactor across substrate APIsPersonaInstanceInfocallers 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 fixtureParent
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