Conversation
geoffjay
left a comment
There was a problem hiding this comment.
Review: feat(core): add entity module stubs, migration runner, and Storage wrapper
Clean scaffold. All four entity stubs, the migration runner, and the Storage wrapper follow the orchestrator's established patterns exactly. No correctness issues.
Stack context
issue-241 (#1059) is stacked on issue-212 (#1058), which was just approved and is in merge-queue. This branch cannot merge until #1058 lands. Once #1058 merges to main, this branch will need a restack to update its base — adding needs-restack accordingly.
Ordering note for the #240 worker: The triage plan defined the chain as #212 → #240 → #241, but this PR implements #241 directly on top of #212, ahead of #240. That's fine as a scaffold-first approach, but it changes what #240 needs to do: instead of creating migration/mod.rs from scratch, the #240 worker must stack on top of this branch and update the existing migration/mod.rs to register the actual migration file(s). Creating a fresh migration/mod.rs in a branch off main would conflict with this PR.
Entity stubs — correct
All four stubs follow the same minimal pattern:
#[sea_orm(table_name = "...")]uses the correct plural snake_case table names:users,organizations,memberships,sessions✅auto_increment = falseon the UUIDidPK ✅- Empty
Relationenum andimpl ActiveModelBehavior for ActiveModel {}ready for #237/#238/#239 to fill in ✅ - Doc comments reference the downstream issues that will complete each entity ✅
migration/mod.rs — correct scaffold
pub use sea_orm_migration::prelude::* + empty vec![] migration list matches the orchestrator's migration/mod.rs pattern exactly. The doc example showing Migrator::up(&db, None).await? is accurate. ✅
storage.rs — correct
Matches AgentStorage pattern:
CloneviaDatabaseConnection(pool handle, cheap clone) ✅- Runs
Migrator::upon construction before returning ✅ db()accessor for entity-specific operations ✅anyhow::Resultfor error propagation ✅
The doc example references agentd_common::storage::{get_db_path, create_connection} — consistent with how AgentStorage is initialized. ✅
Dependencies — correct
sea-orm = { workspace = true }andsea-orm-migration = { workspace = true }✅async-trait = "0.1"matches orchestrator's direct pinned dep (it's not in the workspace table) ✅
No tests
Acceptable for this scaffold — Storage::new has no logic beyond Migrator::up (which returns immediately with an empty migration list), and the entity stubs have no fields to exercise. Tests belong in the downstream issues (#237, #238, #239, #242).
Summary
Correct scaffold, consistent with existing patterns. Approving. Parent PR #1058 must merge first; #240 worker should stack on top of this branch.
…apper
Sets up the structural scaffold that all core entity issues depend on:
- crates/core/src/entity/{mod,user,organization,membership,session}.rs — minimal
DeriveEntityModel stubs for the four core tables
- crates/core/src/migration/mod.rs — empty Migrator ready for future migrations
- crates/core/src/storage.rs — Storage wrapping DatabaseConnection, runs
Migrator::up on construction
- Adds sea-orm, sea-orm-migration, and async-trait to Cargo.toml
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the stub entity/user.rs with the full DeriveEntityModel implementation matching the users table schema from the initial migration: id, email (unique), password_hash, display_name, role, timestamps. Declares has_many relations to memberships and sessions, with Related<T> impls for both. To satisfy the has_many bound (R: Related<Self>), the membership and session stubs gain a user_id column and belongs_to User relation — the minimum required for compilation before #238 and #239 fill in their full schemas. Adds tempfile dev-dependency for the test helper. Test: insert a user via ActiveModel and retrieve it by primary key. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds crates/core/src/migration/m20260305_000001_create_core_tables.rs creating four tables in a single migration: - users (id, email unique, password_hash, display_name, role, timestamps) - organizations (id, name unique, slug unique, timestamps) - memberships (id, user_id, organization_id, role, timestamps) with composite unique index on (user_id, organization_id) - sessions (id, user_id, token_hash, expires_at, created_at) with indexes on token_hash and expires_at All tables use if_not_exists; down() drops in reverse dependency order. Registers the migration in Migrator and calls Storage::new() at startup to apply pending migrations before the HTTP server begins accepting requests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…many relations Replaces entity stubs with full DeriveEntityModel implementations: organization.rs — id, name (unique), slug (unique), timestamps; has_many Memberships; Related<user::Entity> via membership junction (via() pattern). membership.rs — id, user_id, organization_id, role, timestamps; belongs_to User and Organization; Related impls for both. user.rs — adds Related<organization::Entity> with via() completing the bidirectional many-to-many between User and Organization through Membership. Four tests in organization.rs: - test_org_insert_and_find: basic CRUD - test_membership_links_user_and_org: junction row creation - test_find_users_with_related_organizations: find_with_related(Org) from user side — one user, two orgs via two memberships - test_find_org_with_related_users: find_with_related(User) from org side — one org, two member users Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the stub entity/session.rs with the full DeriveEntityModel implementation matching the sessions table schema: id, user_id, token_hash, expires_at, created_at. Declares belongs_to User relation and Related<user::Entity> impl. Three tests covering the acceptance criteria: - test_session_creation: insert via ActiveModel, retrieve by PK - test_session_validation_with_related_user: find non-expired session by token_hash using find_also_related(user::Entity) for single-query auth - test_expired_session_cleanup: delete_many with ExpiresAt.lt filter Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds crates/core/tests/entity_relations.rs with 9 tests covering all acceptance criteria: - test_crud_user: insert/find/update/delete via ActiveModel + delete_by_id - test_user_has_many_memberships: find_related(membership::Entity) from a loaded user model - test_many_to_many_user_organizations_from_user_side: find_with_related traversal user → organizations through membership junction - test_many_to_many_organization_users_from_org_side: find_with_related traversal org → users through membership junction - test_session_belongs_to_user: find_also_related(user::Entity) for single-query session validation with eager-loaded user - test_unique_email_constraint: duplicate email insert returns Err - test_unique_membership_constraint: duplicate (user_id, org_id) returns Err - test_migration_idempotent: Migrator::up twice does not error - test_migration_down_and_up: full rollback + re-apply yields empty tables Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… migration Adds UserStorage with full CRUD, argon2id password hashing, get_by_username/email, set_active_organization, and paginated listing. Adds migration to extend the users table with nullable username (unique) and active_organization_id columns. Updates all entity helpers and integration tests to include the new fields.
…oint Adds TenantContext extractor (session + active org required, 403 if no active org) and PUT /users/me/active-organization endpoint with membership validation. Also adds api/users.rs module with 4 endpoint tests.
Implements POST /auth/register, POST /auth/login, POST /auth/logout, and GET /auth/me. Adds SessionStorage with 256-bit random token generation, expiry cleanup, and AuthUser extractor middleware. Registration creates a default personal organization and sets it as the active organization.
Adds `agent auth register/login/logout/status` and `agent org list/create/switch` commands communicating with the agentd-core service (port 17007). Session token stored at ~/.config/agentd/session with 0600 permissions. Adds `dirs` dependency for cross-platform config path resolution. Registers core service in status check.
Adds GET/PUT /api/v1/users/me, PUT /api/v1/users/me/password, GET /api/v1/users/me/organizations, and full CRUD for organizations with member management (add, remove, list). Enforces owner-only mutations, last-owner protection, and clears active_organization_id on org deletion. Adds update_email, update_password, and clear_active_organization_for_org to UserStorage. 21 new endpoint tests covering auth and authorization checks.
Implements HTTP reverse proxy in crates/core that forwards /api/v1/{service}/*
requests to downstream agentd services with X-Tenant-ID and X-Request-ID header
injection. Includes concurrent health aggregation at /api/v1/health.
feat(core): add API gateway proxy for downstream service routing
feat(cli): add auth and org commands for core service
feat(core): add user and organization management API endpoints
feat(core): add TenantContext middleware and active organization endpoint
feat(core): add authentication endpoints with SQLite-backed sessions
feat(core): add UserStorage with argon2 password hashing and username migration
feat(core): add OrganizationStorage and MembershipStorage
feat(core): add SeaORM integration tests for entity relations
feat(core): define Organization and Membership entities with many-to-many relations
feat(core): define SeaORM Session entity with belongs_to User
feat(core): define SeaORM User entity with has_many relations
feat(core): implement initial sea-orm migration for core service schema
Sets up the entity/migration/storage scaffold in
crates/corethat all downstream entity issues (#237, #238, #239) depend on.entity/mod.rs— declares four sub-modules:user,organization,membership,sessionentity/{user,organization,membership,session}.rs— minimalDeriveEntityModelstubs (id-only, correct table names) that compile and are ready to be filled inmigration/mod.rs— emptyMigratorimplementingMigratorTrait, ready for future migration filesstorage.rs—Storagestruct wrappingDatabaseConnection; runsMigrator::upon construction;Clone + Send + SyncCargo.toml— addssea-orm,sea-orm-migration,async-traitworkspace/direct depslib.rs— declaresentity,migration,storagemodulesCloses #241