13 KiB
Phase 1: Authority Module Conversion
Sprint: 2 Duration: 1 sprint Status: TODO Dependencies: Phase 0 (Foundations)
Objectives
- Create
StellaOps.Authority.Storage.Postgresproject - Implement full Authority schema in PostgreSQL
- Implement all repository interfaces
- Enable dual-write mode for validation
- Switch Authority to PostgreSQL-only after verification
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 Section 5.1 for complete Authority schema.
Tables:
authority.tenantsauthority.usersauthority.rolesauthority.user_rolesauthority.service_accountsauthority.clientsauthority.scopesauthority.tokensauthority.revocationsauthority.login_attemptsauthority.licensesauthority.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:
- T1.1.1: Create project
src/Authority/__Libraries/StellaOps.Authority.Storage.Postgres/ - T1.1.2: Add reference to
StellaOps.Infrastructure.Postgres - T1.1.3: Add reference to
StellaOps.Authority.Core - T1.1.4: Create
AuthorityDataSourceclass - T1.1.5: Create
AuthorityPostgresOptionsclass - 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:
- T1.2.1: Create
V001_CreateAuthoritySchemamigration - T1.2.2: Include all tables from SPECIFICATION.md
- T1.2.3: Include all indexes
- T1.2.4: Add seed data for system roles/permissions
- T1.2.5: Test migration idempotency
Migration Implementation:
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:
- T1.3.1: Implement
GetByIdAsync - T1.3.2: Implement
GetByUsernameAsync - T1.3.3: Implement
GetBySubjectIdAsync - T1.3.4: Implement
ListAsyncwith pagination - T1.3.5: Implement
CreateAsync - T1.3.6: Implement
UpdateAsync - T1.3.7: Implement
DeleteAsync - T1.3.8: Implement
GetRolesAsync - T1.3.9: Implement
AssignRoleAsync - T1.3.10: Implement
RevokeRoleAsync - T1.3.11: Write integration tests
Interface Reference:
public interface IUserRepository
{
Task<User?> GetByIdAsync(string tenantId, Guid userId, CancellationToken ct);
Task<User?> GetByUsernameAsync(string tenantId, string username, CancellationToken ct);
Task<User?> GetBySubjectIdAsync(Guid subjectId, CancellationToken ct);
Task<PagedResult<User>> ListAsync(string tenantId, UserQuery query, CancellationToken ct);
Task<User> CreateAsync(User user, CancellationToken ct);
Task<User> UpdateAsync(User user, CancellationToken ct);
Task<bool> DeleteAsync(string tenantId, Guid userId, CancellationToken ct);
Task<IReadOnlyList<Role>> 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:
- T1.4.1: Implement
GetByIdAsync - T1.4.2: Implement
GetByAccountIdAsync - T1.4.3: Implement
ListAsync - T1.4.4: Implement
CreateAsync - T1.4.5: Implement
UpdateAsync - T1.4.6: Implement
DeleteAsync - 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:
- T1.5.1: Implement
GetByIdAsync - T1.5.2: Implement
GetByClientIdAsync - T1.5.3: Implement
ListAsync - T1.5.4: Implement
CreateAsync - T1.5.5: Implement
UpdateAsync - T1.5.6: Implement
DeleteAsync - 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:
- T1.6.1: Implement
GetByIdAsync - T1.6.2: Implement
GetByHashAsync - T1.6.3: Implement
CreateAsync - T1.6.4: Implement
RevokeAsync - T1.6.5: Implement
PruneExpiredAsync - T1.6.6: Implement
GetActiveTokensAsync - 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:
- T1.7.1: Implement
IRoleRepository - T1.7.2: Implement
IScopeRepository - T1.7.3: Implement
IRevocationRepository - T1.7.4: Implement
ILoginAttemptRepository - T1.7.5: Implement
ILicenseRepository - 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:
- T1.8.1: Update
ServiceCollectionExtensionsin Authority.WebService - T1.8.2: Add conditional registration based on
Persistence:Authority - T1.8.3: Test switching between Mongo and Postgres
- T1.8.4: Document configuration options
Implementation:
public static IServiceCollection AddAuthorityStorage(
this IServiceCollection services,
IConfiguration configuration)
{
var backend = configuration.GetValue<string>("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: Implement Dual-Write Wrapper (Optional)
Status: TODO Assignee: TBD Estimate: 1 day
Description: Implement dual-write repository wrapper for safe migration.
Subtasks:
- T1.9.1: Create
DualWriteUserRepository - T1.9.2: Implement write-to-both logic
- T1.9.3: Implement read-from-primary-with-fallback logic
- T1.9.4: Add metrics for dual-write operations
- T1.9.5: Add logging for inconsistencies
- T1.9.6: Create similar wrappers for other critical repositories
Configuration Options:
public sealed class DualWriteOptions
{
public string PrimaryBackend { get; set; } = "Postgres";
public bool WriteToBoth { get; set; } = true;
public bool FallbackToSecondary { get; set; } = true;
public bool ConvertOnRead { get; set; } = true;
}
Verification:
- Writes go to both backends
- Reads work with fallback
- Inconsistencies are logged
T1.10: Run Verification Tests
Status: TODO Assignee: TBD Estimate: 1 day
Description: Verify PostgreSQL implementation matches MongoDB behavior.
Subtasks:
- T1.10.1: Run comparison tests for User repository
- T1.10.2: Run comparison tests for Token repository
- T1.10.3: Verify token issuance/verification flow
- T1.10.4: Verify login flow
- T1.10.5: Document any differences found
- T1.10.6: Generate verification report
Verification Tests:
[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:
- T1.11.1: Create backfill script for tenants
- T1.11.2: Create backfill script for users
- T1.11.3: Create backfill script for service accounts
- T1.11.4: Create backfill script for clients/scopes
- T1.11.5: Create backfill script for active tokens
- T1.11.6: Verify record counts match
- 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:
- T1.12.1: Update configuration to
"Authority": "Postgres" - T1.12.2: Deploy to staging
- T1.12.3: Run full integration test suite
- T1.12.4: Monitor for errors/issues
- T1.12.5: Deploy to production
- T1.12.6: Monitor production metrics
Verification:
- All tests pass in staging
- No errors in production
- Performance metrics acceptable
Exit Criteria
- All repository interfaces implemented for PostgreSQL
- All integration tests pass
- Verification tests pass (MongoDB vs PostgreSQL comparison)
- Configuration switch working
- Authority running on PostgreSQL in production
- 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
- Change configuration:
"Authority": "Mongo" - Deploy configuration change
- MongoDB still has all data (dual-write period)
- Investigate and fix PostgreSQL issues
- Re-attempt conversion
Phase Version: 1.0.0 Last Updated: 2025-11-28