refactor: rename ScadaLink → ZB.MOM.WW.ScadaBridge (code + projects + namespaces)
Solution + 23 src projects + 26 test projects renamed; folders, csproj, namespaces, and ScadaLinkDbContext/ScadaBridgeDbContext class updated. ActorSystem "scadalink" → "scadabridge", Akka seed-node URLs migrated. SQL roles/logins, LDAP domains, CLI command name, and CLI config dir (~/.scadalink → ~/.scadabridge) also renamed. Build green; 5 Host.Tests fail awaiting SQL login rename in next commit. Pre-existing StaleTagMonitor timing flakes unchanged. Rename script committed at tools/rename-to-scadabridge.sh.
This commit is contained in:
@@ -2,9 +2,9 @@
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers-extended-cc:subagent-driven-development to implement this plan task-by-task (bundled cadence per `feedback_subagent_cadence`).
|
||||
|
||||
**Goal:** Land the `AuditLog` table (monthly-partitioned) plus DB roles in MS SQL, and add the Commons types + EF repo + new `ScadaLink.AuditLog` project skeleton that every later milestone depends on. After M1 the database is ready, the new project is wired into the solution, and `dotnet build && dotnet test` are both green.
|
||||
**Goal:** Land the `AuditLog` table (monthly-partitioned) plus DB roles in MS SQL, and add the Commons types + EF repo + new `ZB.MOM.WW.ScadaBridge.AuditLog` project skeleton that every later milestone depends on. After M1 the database is ready, the new project is wired into the solution, and `dotnet build && dotnet test` are both green.
|
||||
|
||||
**Architecture:** New `AuditEvent` record + audit enums + writer interfaces in Commons. New EF entity configuration + EF Core migration creating `AuditLog` table aligned to `ps_AuditLog_Month` partition scheme on `OccurredAtUtc`, plus `scadalink_audit_writer` and `scadalink_audit_purger` SQL roles. New `IAuditLogRepository` with append-only surface (no Update, no row-delete). New `src/ScadaLink.AuditLog/` project skeleton + `AuditLogOptions`.
|
||||
**Architecture:** New `AuditEvent` record + audit enums + writer interfaces in Commons. New EF entity configuration + EF Core migration creating `AuditLog` table aligned to `ps_AuditLog_Month` partition scheme on `OccurredAtUtc`, plus `scadabridge_audit_writer` and `scadabridge_audit_purger` SQL roles. New `IAuditLogRepository` with append-only surface (no Update, no row-delete). New `src/ZB.MOM.WW.ScadaBridge.AuditLog/` project skeleton + `AuditLogOptions`.
|
||||
|
||||
**Tech Stack:** .NET 10 / EF Core 10.0.7 / Microsoft.Data.SqlClient 6.0.2 / xUnit 2.9.3 / running `infra/mssql` container for integration tests.
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
**Pre-existing reality:**
|
||||
- `Entities/Audit/AuditLogEntry.cs` (config-audit, 9 cols) **coexists with** new `AuditEvent` — no rename, no removal.
|
||||
- `IAuditService` (config-audit) is distinct from new `IAuditWriter` / `ICentralAuditWriter`.
|
||||
- `tests/ScadaLink.IntegrationTests/` uses EF in-memory — NOT usable for partition/role tests.
|
||||
- `tests/ZB.MOM.WW.ScadaBridge.IntegrationTests/` uses EF in-memory — NOT usable for partition/role tests.
|
||||
- Roadmap M1-T10 (project skeleton) must run before M1-T9 (options class). **Swapped in this plan.**
|
||||
|
||||
---
|
||||
@@ -30,7 +30,7 @@ Tasks 1–11 from the roadmap are grouped into 6 bundles. Each bundle = one impl
|
||||
- **Bundle B — EF entity mapping** (T5): DbSet + IEntityTypeConfiguration<AuditEvent> + indexes.
|
||||
- **Bundle C — Migration with partitioning + DB roles** (T6+T7 merged — one migration file).
|
||||
- **Bundle D — Repository** (T8): IAuditLogRepository + EF implementation + DI registration.
|
||||
- **Bundle E — AuditLog project skeleton + options** (T10 then T9): new `src/ScadaLink.AuditLog/` project + `AuditLogOptions`.
|
||||
- **Bundle E — AuditLog project skeleton + options** (T10 then T9): new `src/ZB.MOM.WW.ScadaBridge.AuditLog/` project + `AuditLogOptions`.
|
||||
- **Bundle F — Docs paper trail** (T11): controller-direct edit; no subagent needed for a 1–3 line update.
|
||||
|
||||
---
|
||||
@@ -40,11 +40,11 @@ Tasks 1–11 from the roadmap are grouped into 6 bundles. Each bundle = one impl
|
||||
### Task 1: Add audit enums to Commons
|
||||
|
||||
**Files:**
|
||||
- Create: `src/ScadaLink.Commons/Types/Enums/AuditChannel.cs`
|
||||
- Create: `src/ScadaLink.Commons/Types/Enums/AuditKind.cs`
|
||||
- Create: `src/ScadaLink.Commons/Types/Enums/AuditStatus.cs`
|
||||
- Create: `src/ScadaLink.Commons/Types/Enums/AuditForwardState.cs`
|
||||
- Create: `tests/ScadaLink.Commons.Tests/Types/Enums/AuditEnumTests.cs`
|
||||
- Create: `src/ZB.MOM.WW.ScadaBridge.Commons/Types/Enums/AuditChannel.cs`
|
||||
- Create: `src/ZB.MOM.WW.ScadaBridge.Commons/Types/Enums/AuditKind.cs`
|
||||
- Create: `src/ZB.MOM.WW.ScadaBridge.Commons/Types/Enums/AuditStatus.cs`
|
||||
- Create: `src/ZB.MOM.WW.ScadaBridge.Commons/Types/Enums/AuditForwardState.cs`
|
||||
- Create: `tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Types/Enums/AuditEnumTests.cs`
|
||||
|
||||
**AuditChannel members** (4): `ApiOutbound`, `DbOutbound`, `Notification`, `ApiInbound`.
|
||||
|
||||
@@ -64,8 +64,8 @@ Tasks 1–11 from the roadmap are grouped into 6 bundles. Each bundle = one impl
|
||||
### Task 2: Add AuditEvent record
|
||||
|
||||
**Files:**
|
||||
- Create: `src/ScadaLink.Commons/Entities/Audit/AuditEvent.cs` — `public sealed record AuditEvent` with the 20 central columns per alog.md §4, plus nullable `AuditForwardState? ForwardState` and nullable `DateTime? IngestedAtUtc`.
|
||||
- Create: `tests/ScadaLink.Commons.Tests/Entities/Audit/AuditEventTests.cs`.
|
||||
- Create: `src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Audit/AuditEvent.cs` — `public sealed record AuditEvent` with the 20 central columns per alog.md §4, plus nullable `AuditForwardState? ForwardState` and nullable `DateTime? IngestedAtUtc`.
|
||||
- Create: `tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Entities/Audit/AuditEventTests.cs`.
|
||||
|
||||
Properties (in alog.md §4 order):
|
||||
`Guid EventId`, `DateTime OccurredAtUtc`, `DateTime? IngestedAtUtc`, `AuditChannel Channel`, `AuditKind Kind`, `Guid? CorrelationId`, `string? SourceSiteId`, `string? SourceInstanceId`, `string? SourceScript`, `string? Actor`, `string? Target`, `AuditStatus Status`, `int? HttpStatus`, `int? DurationMs`, `string? ErrorMessage`, `string? ErrorDetail`, `string? RequestSummary`, `string? ResponseSummary`, `bool PayloadTruncated`, `string? Extra`, `AuditForwardState? ForwardState`.
|
||||
@@ -80,9 +80,9 @@ Properties (in alog.md §4 order):
|
||||
### Task 3: Add IAuditWriter and ICentralAuditWriter
|
||||
|
||||
**Files:**
|
||||
- Create: `src/ScadaLink.Commons/Interfaces/Services/IAuditWriter.cs`
|
||||
- Create: `src/ScadaLink.Commons/Interfaces/Services/ICentralAuditWriter.cs`
|
||||
- Create: `tests/ScadaLink.Commons.Tests/Interfaces/Services/AuditWriterContractTests.cs`
|
||||
- Create: `src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Services/IAuditWriter.cs`
|
||||
- Create: `src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Services/ICentralAuditWriter.cs`
|
||||
- Create: `tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Interfaces/Services/AuditWriterContractTests.cs`
|
||||
|
||||
Both interfaces expose `Task WriteAsync(AuditEvent evt, CancellationToken ct = default)`. XML doc comments name Audit Log #23 as the owner; `IAuditWriter` is the abstraction the boundary code calls, `ICentralAuditWriter` is the central-only flavor (used by direct-write paths in M2+).
|
||||
|
||||
@@ -96,10 +96,10 @@ Both interfaces expose `Task WriteAsync(AuditEvent evt, CancellationToken ct = d
|
||||
### Task 4: Add audit telemetry + pull message DTOs
|
||||
|
||||
**Files:**
|
||||
- Create: `src/ScadaLink.Commons/Messages/Integration/AuditTelemetryEnvelope.cs` — `public sealed record AuditTelemetryEnvelope(Guid EnvelopeId, string SourceSiteId, IReadOnlyList<AuditEvent> Events)`.
|
||||
- Create: `src/ScadaLink.Commons/Messages/Integration/PullAuditEventsRequest.cs` — `public sealed record PullAuditEventsRequest(string SourceSiteId, DateTime SinceUtc, int BatchSize)`.
|
||||
- Create: `src/ScadaLink.Commons/Messages/Integration/PullAuditEventsResponse.cs` — `public sealed record PullAuditEventsResponse(IReadOnlyList<AuditEvent> Events, bool MoreAvailable)`.
|
||||
- Create: `tests/ScadaLink.Commons.Tests/Messages/Integration/AuditTelemetryMessagesTests.cs`.
|
||||
- Create: `src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Integration/AuditTelemetryEnvelope.cs` — `public sealed record AuditTelemetryEnvelope(Guid EnvelopeId, string SourceSiteId, IReadOnlyList<AuditEvent> Events)`.
|
||||
- Create: `src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Integration/PullAuditEventsRequest.cs` — `public sealed record PullAuditEventsRequest(string SourceSiteId, DateTime SinceUtc, int BatchSize)`.
|
||||
- Create: `src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Integration/PullAuditEventsResponse.cs` — `public sealed record PullAuditEventsResponse(IReadOnlyList<AuditEvent> Events, bool MoreAvailable)`.
|
||||
- Create: `tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Messages/Integration/AuditTelemetryMessagesTests.cs`.
|
||||
|
||||
**Steps:**
|
||||
1. Failing test constructs envelope with 3 events and asserts immutability and enumerability.
|
||||
@@ -115,11 +115,11 @@ Both interfaces expose `Task WriteAsync(AuditEvent evt, CancellationToken ct = d
|
||||
|
||||
## Bundle B — EF entity mapping
|
||||
|
||||
### Task 5: Extend ScadaLinkDbContext + add IEntityTypeConfiguration<AuditEvent>
|
||||
### Task 5: Extend ScadaBridgeDbContext + add IEntityTypeConfiguration<AuditEvent>
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/ScadaLink.ConfigurationDatabase/ScadaLinkDbContext.cs` — add `public DbSet<AuditEvent> AuditLogs => Set<AuditEvent>();` in the existing `// Audit` section, **directly after** the existing `AuditLogEntries` DbSet. Do not remove or modify `AuditLogEntries`.
|
||||
- Create: `src/ScadaLink.ConfigurationDatabase/Configurations/AuditLogEntityTypeConfiguration.cs` — `IEntityTypeConfiguration<AuditEvent>` mapping to table `AuditLog`, columns per alog.md §4 (with max lengths), PK on `EventId`, enum columns stored as `varchar(32)` via `HasConversion<string>().HasMaxLength(32)`. **No partition function declared here** — that goes in the migration's raw SQL.
|
||||
- Modify: `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/ScadaBridgeDbContext.cs` — add `public DbSet<AuditEvent> AuditLogs => Set<AuditEvent>();` in the existing `// Audit` section, **directly after** the existing `AuditLogEntries` DbSet. Do not remove or modify `AuditLogEntries`.
|
||||
- Create: `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Configurations/AuditLogEntityTypeConfiguration.cs` — `IEntityTypeConfiguration<AuditEvent>` mapping to table `AuditLog`, columns per alog.md §4 (with max lengths), PK on `EventId`, enum columns stored as `varchar(32)` via `HasConversion<string>().HasMaxLength(32)`. **No partition function declared here** — that goes in the migration's raw SQL.
|
||||
|
||||
Five indexes with explicit names:
|
||||
- `IX_AuditLog_OccurredAtUtc` on (`OccurredAtUtc` desc)
|
||||
@@ -129,7 +129,7 @@ Five indexes with explicit names:
|
||||
- `IX_AuditLog_Target_Occurred` on (`Target`, `OccurredAtUtc` desc) where `Target IS NOT NULL`
|
||||
|
||||
- Modify: `OnModelCreating` — apply via `modelBuilder.ApplyConfiguration(new AuditLogEntityTypeConfiguration())`.
|
||||
- Create: `tests/ScadaLink.ConfigurationDatabase.Tests/Configurations/AuditLogEntityTypeConfigurationTests.cs` — use a `ModelBuilder` directly (no DbContext required) and assert:
|
||||
- Create: `tests/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests/Configurations/AuditLogEntityTypeConfigurationTests.cs` — use a `ModelBuilder` directly (no DbContext required) and assert:
|
||||
- mapped table name is `AuditLog`,
|
||||
- PK is `EventId`,
|
||||
- exactly 21 properties are mapped (20 + ForwardState; IngestedAtUtc is one of the 20 per spec; but ForwardState is the +1),
|
||||
@@ -152,20 +152,20 @@ Five indexes with explicit names:
|
||||
### Task 6+7 (merged): Create migration with partition function/scheme/table + DB roles
|
||||
|
||||
**Files:**
|
||||
- Generate: `src/ScadaLink.ConfigurationDatabase/Migrations/<yyyyMMddHHmmss>_AddAuditLogTable.cs` via:
|
||||
- Generate: `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Migrations/<yyyyMMddHHmmss>_AddAuditLogTable.cs` via:
|
||||
```
|
||||
dotnet ef migrations add AddAuditLogTable --project src/ScadaLink.ConfigurationDatabase \
|
||||
--startup-project src/ScadaLink.Host --output-dir Migrations
|
||||
dotnet ef migrations add AddAuditLogTable --project src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase \
|
||||
--startup-project src/ZB.MOM.WW.ScadaBridge.Host --output-dir Migrations
|
||||
```
|
||||
- Customize the migration's `Up()`:
|
||||
1. Raw SQL: create partition function `pf_AuditLog_Month` (RANGE RIGHT FOR VALUES with month-boundaries from `2026-01-01` through `2027-12-01` UTC), and partition scheme `ps_AuditLog_Month` ALL TO ([PRIMARY]).
|
||||
2. Drop EF's auto-generated `CREATE TABLE` and replace with raw SQL that creates `AuditLog` ON `ps_AuditLog_Month(OccurredAtUtc)`. (Or: let EF generate the table, then `ALTER TABLE … ADD CONSTRAINT … PK … ON ps_AuditLog_Month(OccurredAtUtc)` — whichever EF 10 supports cleanly.)
|
||||
3. Create the five named indexes via `migrationBuilder.CreateIndex(...)`, partition-aligned on `ps_AuditLog_Month(OccurredAtUtc)` where appropriate.
|
||||
4. Raw SQL roles, idempotent (`IF NOT EXISTS … CREATE ROLE`):
|
||||
- `scadalink_audit_writer`: GRANT INSERT ON AuditLog; GRANT SELECT ON AuditLog. (No UPDATE, no DELETE.)
|
||||
- `scadalink_audit_purger`: GRANT ALTER ON SCHEMA::dbo; GRANT SELECT ON AuditLog. (Enables ALTER PARTITION FUNCTION SWITCH and SWITCH PARTITION.)
|
||||
- `scadabridge_audit_writer`: GRANT INSERT ON AuditLog; GRANT SELECT ON AuditLog. (No UPDATE, no DELETE.)
|
||||
- `scadabridge_audit_purger`: GRANT ALTER ON SCHEMA::dbo; GRANT SELECT ON AuditLog. (Enables ALTER PARTITION FUNCTION SWITCH and SWITCH PARTITION.)
|
||||
- `Down()` drops indexes, table, scheme, function, then both roles.
|
||||
- Create: `tests/ScadaLink.ConfigurationDatabase.Tests/Migrations/AddAuditLogTableMigrationTests.cs` — uses a fixture connecting to the running `infra/mssql` container via the connection string in `infra/mssql/.env` (or skips with `Skip.If` when the env var `SCADALINK_MSSQL_TEST_CONN` is unset, so CI without the container still passes).
|
||||
- Create: `tests/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests/Migrations/AddAuditLogTableMigrationTests.cs` — uses a fixture connecting to the running `infra/mssql` container via the connection string in `infra/mssql/.env` (or skips with `Skip.If` when the env var `SCADALINK_MSSQL_TEST_CONN` is unset, so CI without the container still passes).
|
||||
|
||||
Integration test assertions:
|
||||
- `sys.partition_functions` contains `pf_AuditLog_Month`.
|
||||
@@ -173,7 +173,7 @@ Integration test assertions:
|
||||
- `INFORMATION_SCHEMA.TABLES` contains `AuditLog` aligned to the partition scheme.
|
||||
- `sys.indexes` contains the five expected named indexes.
|
||||
- `sys.database_principals` contains both roles.
|
||||
- Smoke test: log in as a user mapped to `scadalink_audit_writer`, attempt `UPDATE AuditLog …`, expect `SqlException` with permission error.
|
||||
- Smoke test: log in as a user mapped to `scadabridge_audit_writer`, attempt `UPDATE AuditLog …`, expect `SqlException` with permission error.
|
||||
|
||||
**Steps:**
|
||||
1. Generate the migration; let EF auto-fill the body.
|
||||
@@ -192,7 +192,7 @@ Integration test assertions:
|
||||
**Notes for the implementer:**
|
||||
- Use `Microsoft.Data.SqlClient` directly in the test fixture (not EF) to issue raw SQL for grant assertions.
|
||||
- `Skip.If(string.IsNullOrEmpty(Environment.GetEnvironmentVariable("SCADALINK_MSSQL_TEST_CONN")), "MSSQL not available")` — keeps tests CI-safe.
|
||||
- Test database name: `ScadaLinkAuditMigrationTest_<guid>` (created per fixture, dropped on dispose).
|
||||
- Test database name: `ScadaBridgeAuditMigrationTest_<guid>` (created per fixture, dropped on dispose).
|
||||
|
||||
**Bundle C acceptance:** Migration applied to a fresh test DB on the `infra/mssql` container creates the partition function/scheme/table/indexes/roles. Smoke test confirms UPDATE is denied for the writer role. All migration tests pass when `SCADALINK_MSSQL_TEST_CONN` is set; skip cleanly when unset.
|
||||
|
||||
@@ -203,7 +203,7 @@ Integration test assertions:
|
||||
### Task 8: IAuditLogRepository + EF implementation + DI
|
||||
|
||||
**Files:**
|
||||
- Create: `src/ScadaLink.Commons/Interfaces/Repositories/IAuditLogRepository.cs` — three methods:
|
||||
- Create: `src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/IAuditLogRepository.cs` — three methods:
|
||||
- `Task InsertIfNotExistsAsync(AuditEvent evt, CancellationToken ct = default);`
|
||||
- `Task<IReadOnlyList<AuditEvent>> QueryAsync(AuditLogQueryFilter filter, AuditLogPaging paging, CancellationToken ct = default);`
|
||||
- `Task SwitchOutPartitionAsync(DateTime monthBoundary, CancellationToken ct = default);`
|
||||
@@ -212,14 +212,14 @@ Integration test assertions:
|
||||
- `AuditLogQueryFilter` record: nullable `AuditChannel?`, `AuditKind?`, `AuditStatus?`, `string? SourceSiteId`, `string? Target`, `string? Actor`, `Guid? CorrelationId`, `DateTime? FromUtc`, `DateTime? ToUtc`.
|
||||
- `AuditLogPaging` record: `int PageSize`, `Guid? AfterEventId`, `DateTime? AfterOccurredAtUtc` (keyset).
|
||||
|
||||
- Create: `src/ScadaLink.ConfigurationDatabase/Repositories/AuditLogRepository.cs` — implements all three methods:
|
||||
- Create: `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/AuditLogRepository.cs` — implements all three methods:
|
||||
- `InsertIfNotExistsAsync` uses raw SQL `IF NOT EXISTS (SELECT 1 FROM AuditLog WHERE EventId = @id) INSERT INTO AuditLog …` via `DbContext.Database.ExecuteSqlInterpolatedAsync` (bypasses change tracker).
|
||||
- `QueryAsync` builds an `IQueryable<AuditEvent>`, applies filters, projects, paged by keyset on `(OccurredAtUtc desc, EventId desc)`.
|
||||
- `SwitchOutPartitionAsync` builds a unique staging table name, runs `CREATE TABLE … <staging>` with identical schema and ON `[PRIMARY]`, runs `ALTER TABLE AuditLog SWITCH PARTITION <n> TO <staging>`, then `DROP TABLE <staging>`. All inside a single transaction. Computes partition number from `monthBoundary` via `$partition.pf_AuditLog_Month(@boundary)`.
|
||||
|
||||
- Modify: `src/ScadaLink.ConfigurationDatabase/ServiceCollectionExtensions.cs` — add `services.AddScoped<IAuditLogRepository, AuditLogRepository>();` after `INotificationOutboxRepository` line.
|
||||
- Modify: `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/ServiceCollectionExtensions.cs` — add `services.AddScoped<IAuditLogRepository, AuditLogRepository>();` after `INotificationOutboxRepository` line.
|
||||
|
||||
- Create: `tests/ScadaLink.ConfigurationDatabase.Tests/Repositories/AuditLogRepositoryTests.cs` — uses the same MSSQL fixture from Bundle C (skipped when env var unset) since `InsertIfNotExistsAsync` uses raw SQL that won't run on EF in-memory.
|
||||
- Create: `tests/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests/Repositories/AuditLogRepositoryTests.cs` — uses the same MSSQL fixture from Bundle C (skipped when env var unset) since `InsertIfNotExistsAsync` uses raw SQL that won't run on EF in-memory.
|
||||
|
||||
Tests:
|
||||
- Insert for fresh `EventId` writes one row.
|
||||
@@ -246,38 +246,38 @@ Tests:
|
||||
|
||||
## Bundle E — AuditLog project skeleton + options
|
||||
|
||||
### Task 10 (first): Scaffold `src/ScadaLink.AuditLog/` project
|
||||
### Task 10 (first): Scaffold `src/ZB.MOM.WW.ScadaBridge.AuditLog/` project
|
||||
|
||||
**Files:**
|
||||
- Create: `src/ScadaLink.AuditLog/ScadaLink.AuditLog.csproj` — TargetFramework `net10.0` (matches solution), references `ScadaLink.Commons` + `ScadaLink.ConfigurationDatabase`.
|
||||
- Create: `src/ScadaLink.AuditLog/ServiceCollectionExtensions.cs` — `public static class ServiceCollectionExtensions { public static IServiceCollection AddAuditLog(this IServiceCollection services, IConfiguration config) { … } }` registering `AuditLogOptions` (from Task 9) and forwarding to `services.AddScoped<IAuditLogRepository, AuditLogRepository>()` (already registered by ConfigurationDatabase, so this is a no-op but documents the dependency).
|
||||
- Create: `tests/ScadaLink.AuditLog.Tests/ScadaLink.AuditLog.Tests.csproj` with one smoke test.
|
||||
- Create: `tests/ScadaLink.AuditLog.Tests/AddAuditLogTests.cs` — smoke test: `services.AddAuditLog(config); var p = services.BuildServiceProvider(); Assert.NotNull(p.GetService<IOptions<AuditLogOptions>>());`.
|
||||
- Modify: `ScadaLink.slnx` — add both projects.
|
||||
- Create: `src/ZB.MOM.WW.ScadaBridge.AuditLog/ZB.MOM.WW.ScadaBridge.AuditLog.csproj` — TargetFramework `net10.0` (matches solution), references `ZB.MOM.WW.ScadaBridge.Commons` + `ZB.MOM.WW.ScadaBridge.ConfigurationDatabase`.
|
||||
- Create: `src/ZB.MOM.WW.ScadaBridge.AuditLog/ServiceCollectionExtensions.cs` — `public static class ServiceCollectionExtensions { public static IServiceCollection AddAuditLog(this IServiceCollection services, IConfiguration config) { … } }` registering `AuditLogOptions` (from Task 9) and forwarding to `services.AddScoped<IAuditLogRepository, AuditLogRepository>()` (already registered by ConfigurationDatabase, so this is a no-op but documents the dependency).
|
||||
- Create: `tests/ZB.MOM.WW.ScadaBridge.AuditLog.Tests/ZB.MOM.WW.ScadaBridge.AuditLog.Tests.csproj` with one smoke test.
|
||||
- Create: `tests/ZB.MOM.WW.ScadaBridge.AuditLog.Tests/AddAuditLogTests.cs` — smoke test: `services.AddAuditLog(config); var p = services.BuildServiceProvider(); Assert.NotNull(p.GetService<IOptions<AuditLogOptions>>());`.
|
||||
- Modify: `ZB.MOM.WW.ScadaBridge.slnx` — add both projects.
|
||||
|
||||
**Steps:**
|
||||
1. `dotnet new classlib -n ScadaLink.AuditLog -o src/ScadaLink.AuditLog --framework net10.0` (then delete the default `Class1.cs`).
|
||||
2. `dotnet new xunit -n ScadaLink.AuditLog.Tests -o tests/ScadaLink.AuditLog.Tests --framework net10.0`.
|
||||
3. Add `<ProjectReference>` to Commons + ConfigurationDatabase in the src csproj; add reference to ScadaLink.AuditLog in the test csproj.
|
||||
4. Add both projects to `ScadaLink.slnx` (inside the existing `/src/` and `/tests/` folders).
|
||||
1. `dotnet new classlib -n ZB.MOM.WW.ScadaBridge.AuditLog -o src/ZB.MOM.WW.ScadaBridge.AuditLog --framework net10.0` (then delete the default `Class1.cs`).
|
||||
2. `dotnet new xunit -n ZB.MOM.WW.ScadaBridge.AuditLog.Tests -o tests/ZB.MOM.WW.ScadaBridge.AuditLog.Tests --framework net10.0`.
|
||||
3. Add `<ProjectReference>` to Commons + ConfigurationDatabase in the src csproj; add reference to ZB.MOM.WW.ScadaBridge.AuditLog in the test csproj.
|
||||
4. Add both projects to `ZB.MOM.WW.ScadaBridge.slnx` (inside the existing `/src/` and `/tests/` folders).
|
||||
5. Add `<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />` to the src csproj (already in `Directory.Packages.props`).
|
||||
6. Create stub `ServiceCollectionExtensions.AddAuditLog` (just registers options; writer impl comes in M2).
|
||||
7. Commit: `feat(auditlog): scaffold ScadaLink.AuditLog project (#23)`.
|
||||
7. Commit: `feat(auditlog): scaffold ZB.MOM.WW.ScadaBridge.AuditLog project (#23)`.
|
||||
|
||||
### Task 9: AuditLogOptions
|
||||
|
||||
**Files:**
|
||||
- Create: `src/ScadaLink.AuditLog/Configuration/AuditLogOptions.cs` — class with:
|
||||
- Create: `src/ZB.MOM.WW.ScadaBridge.AuditLog/Configuration/AuditLogOptions.cs` — class with:
|
||||
- `int DefaultCapBytes` (default 8192)
|
||||
- `int ErrorCapBytes` (default 65536)
|
||||
- `List<string> HeaderRedactList` (default: `[ "Authorization", "X-Api-Key", "Cookie", "Set-Cookie" ]`)
|
||||
- `List<string> GlobalBodyRedactors` (default: empty)
|
||||
- `Dictionary<string, PerTargetRedactionOverride> PerTargetOverrides` (default empty)
|
||||
- `int RetentionDays` (default 365; range [30, 3650])
|
||||
- Create: `src/ScadaLink.AuditLog/Configuration/PerTargetRedactionOverride.cs` — minimal: `int? CapBytes`, `List<string>? AdditionalBodyRedactors`.
|
||||
- Create: `src/ScadaLink.AuditLog/Configuration/AuditLogOptionsValidator.cs` — `IValidateOptions<AuditLogOptions>` checking `DefaultCapBytes > 0`, `ErrorCapBytes >= DefaultCapBytes`, `RetentionDays` in `[30, 3650]`.
|
||||
- Modify: `src/ScadaLink.AuditLog/ServiceCollectionExtensions.AddAuditLog` to `services.AddOptions<AuditLogOptions>().Bind(config.GetSection("AuditLog")).ValidateOnStart(); services.AddSingleton<IValidateOptions<AuditLogOptions>, AuditLogOptionsValidator>();`.
|
||||
- Add: `tests/ScadaLink.AuditLog.Tests/Configuration/AuditLogOptionsTests.cs`:
|
||||
- Create: `src/ZB.MOM.WW.ScadaBridge.AuditLog/Configuration/PerTargetRedactionOverride.cs` — minimal: `int? CapBytes`, `List<string>? AdditionalBodyRedactors`.
|
||||
- Create: `src/ZB.MOM.WW.ScadaBridge.AuditLog/Configuration/AuditLogOptionsValidator.cs` — `IValidateOptions<AuditLogOptions>` checking `DefaultCapBytes > 0`, `ErrorCapBytes >= DefaultCapBytes`, `RetentionDays` in `[30, 3650]`.
|
||||
- Modify: `src/ZB.MOM.WW.ScadaBridge.AuditLog/ServiceCollectionExtensions.AddAuditLog` to `services.AddOptions<AuditLogOptions>().Bind(config.GetSection("AuditLog")).ValidateOnStart(); services.AddSingleton<IValidateOptions<AuditLogOptions>, AuditLogOptionsValidator>();`.
|
||||
- Add: `tests/ZB.MOM.WW.ScadaBridge.AuditLog.Tests/Configuration/AuditLogOptionsTests.cs`:
|
||||
- Bind valid section → values present.
|
||||
- Bind invalid `RetentionDays = 0` → validator rejects.
|
||||
- Bind invalid `ErrorCapBytes < DefaultCapBytes` → validator rejects.
|
||||
@@ -292,7 +292,7 @@ Tests:
|
||||
7. Run: pass.
|
||||
8. Commit: `feat(auditlog): add AuditLogOptions + validator (#23)`.
|
||||
|
||||
**Bundle E acceptance:** New `src/ScadaLink.AuditLog/` project builds. Solution still builds. Smoke + options tests green. `ScadaLink.slnx` includes both new entries.
|
||||
**Bundle E acceptance:** New `src/ZB.MOM.WW.ScadaBridge.AuditLog/` project builds. Solution still builds. Smoke + options tests green. `ZB.MOM.WW.ScadaBridge.slnx` includes both new entries.
|
||||
|
||||
---
|
||||
|
||||
@@ -301,12 +301,12 @@ Tests:
|
||||
### Task 11: Register AuditLog project in Component-Host.md and confirm README
|
||||
|
||||
**Files:**
|
||||
- Modify: `docs/requirements/Component-Host.md` — list `ScadaLink.AuditLog` in the central role's registration set.
|
||||
- Modify: `docs/requirements/Component-Host.md` — list `ZB.MOM.WW.ScadaBridge.AuditLog` in the central role's registration set.
|
||||
- Modify: `README.md` — confirm row #23 link reflects the new project (no functional change unless missing).
|
||||
|
||||
This is a 1–3 line edit. Per the cadence memory, controller does it directly without a subagent.
|
||||
|
||||
**Commit:** `docs(audit): register ScadaLink.AuditLog project in Host role (#23)`.
|
||||
**Commit:** `docs(audit): register ZB.MOM.WW.ScadaBridge.AuditLog project in Host role (#23)`.
|
||||
|
||||
---
|
||||
|
||||
@@ -316,7 +316,7 @@ After all bundles ship:
|
||||
|
||||
- Dispatch a final code-reviewer subagent over the whole M1 branch.
|
||||
- Acceptance gate (from goal prompt step E):
|
||||
- `dotnet test ScadaLink.slnx` green (full solution).
|
||||
- `dotnet test ZB.MOM.WW.ScadaBridge.slnx` green (full solution).
|
||||
- All M1 roadmap acceptance criteria met; each cited by name to the proving test.
|
||||
- If green, merge to main `--no-ff` with summary message (step F).
|
||||
- Update M2–M8 sections of the roadmap with realities learned (step G), commit.
|
||||
|
||||
Reference in New Issue
Block a user