Conversation
geoffjay
left a comment
There was a problem hiding this comment.
Review: feat(core): scaffold agentd-core service
Clean scaffold that correctly follows the established service patterns across the workspace. No issues found.
Stack context
issue-212 branches directly off main with no needs restack marker — the branch is current. No parent PR. This is the v0.3.3 critical path entry point; landing it unblocks #240 → #241 → #237 → {#238, #239} → #242 and ultimately resolves the architectural placement question on PR #1057.
Correctness checks
Bind address (127.0.0.1): Consistent with the majority of services in the workspace — orchestrator, communicate, memory, notify, wrap, and index all bind to 127.0.0.1. The pattern is correct. ✅
uuid pinned version: uuid is not in the workspace [dependencies] table, so uuid = { version = "1.11", features = ["v4", "serde"] } is the correct form. Matches orchestrator, notify, memory, and every other crate that uses uuid. ✅
tower-http: Uses workspace = true correctly since it is a workspace dependency. ✅
Port 17007: The doc comment correctly explains the divergence from the issue spec (17010 was already taken by communicate). Good transparency. ✅
Structure
lib.rs + main.rs split: Correct Rust binary+library crate pattern. api.rs is compiled as part of the library (for tests) and independently in the binary. The #[cfg(test)] tests in api.rs run under the library compilation unit, which is the right setup. ✅
init_metrics(): The .expect("failed to install metrics recorder") is acceptable — metrics recorder installation is a fatal startup error in every other service that uses the same pattern. ✅
Metrics in main.rs rather than api::create_router(): Correct separation — the metrics endpoint requires PrometheusHandle state that's initialized at the binary level, not something create_router() should own. Tests correctly cover /health without needing a metrics recorder. ✅
Middleware order (metrics_layer → trace_layer → cors_layer): Consistent with orchestrator and other services. ✅
Graceful shutdown: axum::serve(...).with_graceful_shutdown(shutdown_signal()).into_future().await? is the correct axum 0.7 pattern. ✅
Tests
Both tests use tower::ServiceExt::oneshot — the idiomatic axum handler test approach:
health_returns_200: verifies status code ✅health_returns_json_body: verifies bothstatusandservicefields ✅
Coverage is appropriate for a scaffold PR. ✅
Summary
This PR delivers exactly what #212 specified: health endpoint, Prometheus metrics, graceful shutdown, shared middleware, two integration tests. The implementation is consistent with the rest of the workspace. Approving.
…metheus metrics Adds the crates/core workspace member: binary agentd-core listening on port 17007 (AGENTD_PORT), GET /health endpoint, Prometheus /metrics, graceful Ctrl+C shutdown, and shared middleware (trace, CORS, metrics). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…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>
…nd tests OrganizationStorage (organization_storage.rs): - create, get_by_id, get_by_slug, list (ordered by name), update, delete - 8 unit tests: CRUD round-trips, unique name + slug constraints, not-found paths MembershipStorage (membership_storage.rs): - add_member, remove_member, list_members(org_id), list_user_organizations(user_id), get_membership, update_role - list_user_organizations uses inner_join on organization::Entity filtered by user_id - 8 unit tests: add/get, remove, list in both directions, update_role, duplicate rejection Storage updated with organizations() and memberships() accessors sharing the DatabaseConnection. Both storage types declared in lib.rs as public modules. 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
feat(core): add entity module stubs, migration runner, and Storage wrapper
Adds the
crates/coreworkspace member implementing the central auth/API gateway scaffold described in issue #212.agentd-corelistening on port 17007 (configurable viaAGENTD_PORT)GET /healthendpoint returning{"status":"ok","service":"core"}GET /metricsPrometheus endpoint withservice_infogaugeCloses #212