# Phase 1: Authority Module Conversion **Sprint:** 2 **Duration:** 1 sprint **Status:** DONE (2025-12-03) **Dependencies:** Phase 0 (Foundations) --- ## Objectives 1. Create `StellaOps.Authority.Storage.Postgres` project 2. Implement full Authority schema in PostgreSQL 3. Implement all repository interfaces 4. (Retired) Dual-write mode used during cutover; removed in cleanup 5. Switch Authority to PostgreSQL-only after verification **2025-12-03 Update** - Dual-write wrappers removed post-cutover; Authority now runs Postgres-only with direct repositories. - Backfill harness retired; staging/production operate solely on PostgreSQL. - Sprint PG-T1.9–PG-T1.12 closed; no remaining dual-write dependencies. --- ## Execution Log | Date (UTC) | Update | Owner | | --- | --- | --- | | 2025-12-03 | Cutover to PostgreSQL-only; dual-write removed; verification completed. | Authority | | 2025-12-04 | Synced task status and linked verification report (`docs/db/reports/authority-verification-2025-12-03.md`). | PM | ## Deliverables | Deliverable | Acceptance Criteria | |-------------|---------------------| | Authority schema | All tables created with indexes | | Repository implementations | All 9 interfaces implemented | | Dual-write wrapper | Optional, for safe rollout | | Integration tests | 100% coverage of CRUD operations | | Verification report | MongoDB vs PostgreSQL comparison passed | --- ## Schema Reference See [SPECIFICATION.md](../SPECIFICATION.md) Section 5.1 for complete Authority schema. **Tables:** - `authority.tenants` - `authority.users` - `authority.roles` - `authority.user_roles` - `authority.service_accounts` - `authority.clients` - `authority.scopes` - `authority.tokens` - `authority.revocations` - `authority.login_attempts` - `authority.licenses` - `authority.license_usage` --- ## Task Breakdown ### T1.1: Create Authority.Storage.Postgres Project **Status:** TODO **Assignee:** TBD **Estimate:** 0.5 days **Description:** Create the PostgreSQL storage project for Authority module. **Subtasks:** - [x] T1.1.1: Create project `src/Authority/__Libraries/StellaOps.Authority.Storage.Postgres/` - [x] T1.1.2: Add reference to `StellaOps.Infrastructure.Postgres` - [x] T1.1.3: Add reference to `StellaOps.Authority.Core` - [x] T1.1.4: Create `AuthorityDataSource` class - [x] T1.1.5: Create `AuthorityPostgresOptions` class - [x] T1.1.6: Create `ServiceCollectionExtensions.cs` **Project Structure:** ``` src/Authority/__Libraries/StellaOps.Authority.Storage.Postgres/ ├── StellaOps.Authority.Storage.Postgres.csproj ├── AuthorityDataSource.cs ├── AuthorityPostgresOptions.cs ├── Repositories/ │ ├── PostgresUserRepository.cs │ ├── PostgresRoleRepository.cs │ ├── PostgresServiceAccountRepository.cs │ ├── PostgresClientRepository.cs │ ├── PostgresScopeRepository.cs │ ├── PostgresTokenRepository.cs │ ├── PostgresRevocationRepository.cs │ ├── PostgresLoginAttemptRepository.cs │ └── PostgresLicenseRepository.cs ├── Migrations/ │ └── V001_CreateAuthoritySchema.cs └── ServiceCollectionExtensions.cs ``` **Verification:** - [ ] Project builds without errors - [ ] Can be referenced from Authority.WebService --- ### T1.2: Implement Schema Migrations **Status:** TODO **Assignee:** TBD **Estimate:** 1 day **Description:** Create PostgreSQL schema migration for Authority tables. **Subtasks:** - [x] T1.2.1: Create `V001_CreateAuthoritySchema` migration - [x] T1.2.2: Include all tables from SPECIFICATION.md - [x] T1.2.3: Include all indexes - [x] T1.2.4: Add seed data for system roles/permissions - [x] T1.2.5: Test migration idempotency **Migration Implementation:** ```csharp public sealed class V001_CreateAuthoritySchema : IPostgresMigration { public string Id => "V001_CreateAuthoritySchema"; public string Description => "Create Authority schema with all tables and indexes"; public async Task UpAsync(NpgsqlConnection connection, CancellationToken ct) { await using var cmd = connection.CreateCommand(); cmd.CommandText = AuthoritySchemaSql; await cmd.ExecuteNonQueryAsync(ct); } public Task DownAsync(NpgsqlConnection connection, CancellationToken ct) => throw new NotSupportedException("Rollback not supported for schema creation"); private const string AuthoritySchemaSql = """ CREATE SCHEMA IF NOT EXISTS authority; CREATE TABLE IF NOT EXISTS authority.tenants ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), code TEXT NOT NULL UNIQUE, display_name TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'suspended', 'trial', 'terminated')), settings JSONB DEFAULT '{}', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- ... rest of schema from SPECIFICATION.md """; } ``` **Verification:** - [ ] Migration creates all tables - [ ] Migration is idempotent - [ ] Indexes created correctly --- ### T1.3: Implement User Repository **Status:** TODO **Assignee:** TBD **Estimate:** 1 day **Description:** Implement `IUserRepository` for PostgreSQL. **Subtasks:** - [x] T1.3.1: Implement `GetByIdAsync` - [x] T1.3.2: Implement `GetByUsernameAsync` - [x] T1.3.3: Implement `GetBySubjectIdAsync` - [x] T1.3.4: Implement `ListAsync` with pagination - [x] T1.3.5: Implement `CreateAsync` - [x] T1.3.6: Implement `UpdateAsync` - [x] T1.3.7: Implement `DeleteAsync` - [x] T1.3.8: Implement `GetRolesAsync` - [x] T1.3.9: Implement `AssignRoleAsync` - [x] T1.3.10: Implement `RevokeRoleAsync` - [x] T1.3.11: Write integration tests **Interface Reference:** ```csharp public interface IUserRepository { Task GetByIdAsync(string tenantId, Guid userId, CancellationToken ct); Task GetByUsernameAsync(string tenantId, string username, CancellationToken ct); Task GetBySubjectIdAsync(Guid subjectId, CancellationToken ct); Task> ListAsync(string tenantId, UserQuery query, CancellationToken ct); Task CreateAsync(User user, CancellationToken ct); Task UpdateAsync(User user, CancellationToken ct); Task DeleteAsync(string tenantId, Guid userId, CancellationToken ct); Task> GetRolesAsync(string tenantId, Guid userId, CancellationToken ct); Task AssignRoleAsync(string tenantId, Guid userId, Guid roleId, CancellationToken ct); Task RevokeRoleAsync(string tenantId, Guid userId, Guid roleId, CancellationToken ct); } ``` **Verification:** - [ ] All methods implemented - [ ] Integration tests pass - [ ] Tenant isolation verified --- ### T1.4: Implement Service Account Repository **Status:** TODO **Assignee:** TBD **Estimate:** 0.5 days **Description:** Implement `IServiceAccountRepository` for PostgreSQL. **Subtasks:** - [x] T1.4.1: Implement `GetByIdAsync` - [x] T1.4.2: Implement `GetByAccountIdAsync` - [x] T1.4.3: Implement `ListAsync` - [x] T1.4.4: Implement `CreateAsync` - [x] T1.4.5: Implement `UpdateAsync` - [x] T1.4.6: Implement `DeleteAsync` - [x] T1.4.7: Write integration tests **Verification:** - [ ] All methods implemented - [ ] Integration tests pass --- ### T1.5: Implement Client Repository **Status:** TODO **Assignee:** TBD **Estimate:** 0.5 days **Description:** Implement `IClientRepository` for PostgreSQL (OpenIddict compatible). **Subtasks:** - [x] T1.5.1: Implement `GetByIdAsync` - [x] T1.5.2: Implement `GetByClientIdAsync` - [x] T1.5.3: Implement `ListAsync` - [x] T1.5.4: Implement `CreateAsync` - [x] T1.5.5: Implement `UpdateAsync` - [x] T1.5.6: Implement `DeleteAsync` - [x] T1.5.7: Write integration tests **Verification:** - [ ] All methods implemented - [ ] Integration tests pass --- ### T1.6: Implement Token Repository **Status:** TODO **Assignee:** TBD **Estimate:** 1 day **Description:** Implement `ITokenRepository` for PostgreSQL. **Subtasks:** - [x] T1.6.1: Implement `GetByIdAsync` - [x] T1.6.2: Implement `GetByHashAsync` - [x] T1.6.3: Implement `CreateAsync` - [x] T1.6.4: Implement `RevokeAsync` - [x] T1.6.5: Implement `PruneExpiredAsync` - [x] T1.6.6: Implement `GetActiveTokensAsync` - [x] T1.6.7: Write integration tests **Verification:** - [ ] All methods implemented - [ ] Token lookup by hash is fast - [ ] Expired token pruning works --- ### T1.7: Implement Remaining Repositories **Status:** TODO **Assignee:** TBD **Estimate:** 1.5 days **Description:** Implement remaining repository interfaces. **Subtasks:** - [x] T1.7.1: Implement `IRoleRepository` - [x] T1.7.2: Implement `IScopeRepository` - [x] T1.7.3: Implement `IRevocationRepository` - [x] T1.7.4: Implement `ILoginAttemptRepository` - [x] T1.7.5: Implement `ILicenseRepository` - [x] T1.7.6: Write integration tests for all **Verification:** - [ ] All repositories implemented - [ ] All integration tests pass --- ### T1.8: Add Configuration Switch **Status:** TODO **Assignee:** TBD **Estimate:** 0.5 days **Description:** Add configuration-based backend selection for Authority. **Subtasks:** - [x] T1.8.1: Update `ServiceCollectionExtensions` in Authority.WebService - [x] T1.8.2: Add conditional registration based on `Persistence:Authority` - [x] T1.8.3: Test switching between Mongo and Postgres - [x] T1.8.4: Document configuration options **Implementation:** ```csharp public static IServiceCollection AddAuthorityStorage( this IServiceCollection services, IConfiguration configuration) { var backend = configuration.GetValue("Persistence:Authority") ?? "Mongo"; return backend.ToLowerInvariant() switch { "postgres" => services.AddAuthorityPostgresStorage(configuration), "mongo" => services.AddAuthorityMongoStorage(configuration), _ => throw new ArgumentException($"Unknown Authority backend: {backend}") }; } ``` **Verification:** - [ ] Can switch between backends via configuration - [ ] Invalid configuration throws clear error --- ### T1.9: Dual-Write Wrapper (Retired) **Status:** N/A (Removed 2025-12-03 post-cutover) **Notes:** Dual-write path served migration; code and configuration removed in cleanup. --- ### T1.10: Run Verification Tests **Status:** TODO **Assignee:** TBD **Estimate:** 1 day **Description:** Verify PostgreSQL implementation matches MongoDB behavior. **Subtasks:** - [x] T1.10.1: Run comparison tests for User repository - [x] T1.10.2: Run comparison tests for Token repository - [x] T1.10.3: Verify token issuance/verification flow - [x] T1.10.4: Verify login flow - [x] T1.10.5: Document any differences found - [x] T1.10.6: Generate verification report **Verification Tests:** ```csharp [Fact] public async Task Users_Should_Match_Between_Mongo_And_Postgres() { var tenantIds = await GetSampleTenantIds(10); foreach (var tenantId in tenantIds) { var mongoUsers = await _mongoRepo.ListAsync(tenantId, new UserQuery()); var postgresUsers = await _postgresRepo.ListAsync(tenantId, new UserQuery()); postgresUsers.Items.Should().BeEquivalentTo(mongoUsers.Items, options => options.Excluding(u => u.Id)); } } ``` **Verification:** - [ ] All comparison tests pass - [ ] No data discrepancies found - [ ] Verification report approved --- ### T1.11: Backfill Data (If Required) **Status:** TODO **Assignee:** TBD **Estimate:** 0.5 days **Description:** Backfill existing MongoDB data to PostgreSQL. **Subtasks:** - [x] T1.11.1: Create backfill script for tenants - [x] T1.11.2: Create backfill script for users - [x] T1.11.3: Create backfill script for service accounts - [x] T1.11.4: Create backfill script for clients/scopes - [x] T1.11.5: Create backfill script for active tokens - [x] T1.11.6: Verify record counts match - [x] T1.11.7: Verify sample records match **Verification:** - [ ] All Tier A data backfilled - [ ] Record counts match - [ ] Sample verification passed --- ### T1.12: Switch to PostgreSQL-Only **Status:** TODO **Assignee:** TBD **Estimate:** 0.5 days **Description:** Switch Authority to PostgreSQL-only mode. **Subtasks:** - [x] T1.12.1: Update configuration to `"Authority": "Postgres"` - [x] T1.12.2: Deploy to staging - [x] T1.12.3: Run full integration test suite - [x] T1.12.4: Monitor for errors/issues - [x] T1.12.5: Deploy to production - [x] T1.12.6: Monitor production metrics **Verification:** - [ ] All tests pass in staging - [ ] No errors in production - [ ] Performance metrics acceptable --- ## Exit Criteria - [x] All repository interfaces implemented for PostgreSQL - [x] All integration tests pass - [x] Verification tests pass (MongoDB vs PostgreSQL comparison) - [x] Configuration switch working - [x] Authority running on PostgreSQL in production - [x] MongoDB Authority collections archived --- ## Risks & Mitigations | Risk | Likelihood | Impact | Mitigation | |------|------------|--------|------------| | Token verification regression | Low | High | Extensive testing, dual-write | | OAuth flow breakage | Low | High | Test all OAuth flows | | Performance regression | Medium | Medium | Load testing before switch | --- ## Rollback Plan 1. Change configuration: `"Authority": "Mongo"` 2. Deploy configuration change 3. MongoDB still has all data (dual-write period) 4. Investigate and fix PostgreSQL issues 5. Re-attempt conversion --- *Phase Version: 1.0.0* *Last Updated: 2025-11-28*