Status: done
As a a developer, I want a dedicated read-only data access layer for legacy Access-derived entities using fixed join keys, so that legacy data is available to all features without any risk of schema mutation or direct table coupling.
Campaign_Tracker.Server/campaign-tracker-client/_bmad-output/implementation-artifacts/_bmad-output/planning-artifacts/epics.md (Epic 1 / Story 1.6)_bmad-output/planning-artifacts/architecture.md_bmad-output/planning-artifacts/ux-design-specification.mdGPT-5 Codex
ILegacyDataAccess, InMemoryLegacyDataAccess, ReadOnlyCommandGuard, and 4 read-only domain records (LegacyJurisdiction, LegacyContact, LegacyKit, LegacyKitLabel).ILegacyDataAccess → InMemoryLegacyDataAccess singleton in Program.cs.LegacyDataAccessTests (17 tests across all 4 ACs).LegacyDomainModels_AreReadOnlySealedRecords_AC4 flagged record positional init setters as “public mutation”. Updated the test to permit init-only setters (compiler-generated; construction-time only) while still rejecting plain public setters. AC #4 intent (no post-construction mutation) preserved.ILegacyDataAccess with query methods keyed exclusively by ID, JCode/JurisCode, and KitID. InMemoryLegacyDataAccess returns deterministic seeded records for development/testing without an Access database.Get*Async). A reflection-based test (ILegacyDataAccess_HasNoWriteMethods_AC2) asserts no Insert/Update/Delete/Remove/Modify/Write/Save/Create/Upsert methods exist on the contract. ReadOnlyCommandGuard.Validate(string sql) provides a defense-in-depth runtime check for any future raw-ADO.NET implementation: rejects empty SQL, non-SELECT statements, and any SQL containing INSERT/UPDATE/DELETE/DROP/CREATE/ALTER/TRUNCATE/EXEC/EXECUTE/MERGE/REPLACE keywords (word-boundary match).record types in LegacyData/Models/. Tests confirm strong typing of nullable fields, boolean bit columns, and DateTime? columns.Campaign_Tracker.Server/LegacyData/ references Jurisdiction, JurisCode, JCode, KitID, or KitId. All consumers must obtain legacy data via ILegacyDataAccess from DI. Records are sealed and have no public mutable setters.Program.cs registers ILegacyDataAccess → InMemoryLegacyDataAccess as a singleton. Comment notes that a real Access-backed implementation can be swapped in via configuration when LegacyDatabase:ConnectionString is provided.LegacyDomainModels_AreReadOnlySealedRecords_AC4 to recognize that C# positional records emit init-only setters via the IsExternalInit modifier; these are construction-only and do not violate AC #4. Plain public setters remain forbidden.OleDbLegacyDataAccess, register it when LegacyDatabase:ConnectionString is configured, prevent non-development fallback to seeded data, made join keys non-nullable in domain records, and hardened ReadOnlyCommandGuard for multi-statement/write-keyword edge cases while allowing literals/comments/bracketed identifiers.dotnet test .\Campaign_Tracker.Server.Tests\Campaign_Tracker.Server.Tests.csproj /p:UseAppHost=false passed (86 tests).dotnet build .\campaign-tracker.sln /p:UseAppHost=false passed with 0 warnings and 0 errors.Campaign_Tracker.Server/LegacyData/ILegacyDataAccess.csCampaign_Tracker.Server/LegacyData/InMemoryLegacyDataAccess.csCampaign_Tracker.Server/LegacyData/OleDbLegacyDataAccess.csCampaign_Tracker.Server/LegacyData/LegacyDataAccessException.csCampaign_Tracker.Server/LegacyData/ReadOnlyCommandGuard.csCampaign_Tracker.Server/LegacyData/Models/LegacyJurisdiction.csCampaign_Tracker.Server/LegacyData/Models/LegacyContact.csCampaign_Tracker.Server/LegacyData/Models/LegacyKit.csCampaign_Tracker.Server/LegacyData/Models/LegacyKitLabel.csCampaign_Tracker.Server/Program.csCampaign_Tracker.Server.Tests/LegacyDataAccessTests.csCampaign_Tracker.Server/Campaign_Tracker.Server.csproj_bmad-output/implementation-artifacts/1-6-legacy-anti-corruption-data-access-layer.md_bmad-output/implementation-artifacts/sprint-status.yaml| Date | Version | Description | Author |
|---|---|---|---|
| 2026-05-05 | 1.0 | Implemented anti-corruption data access layer: ILegacyDataAccess, InMemoryLegacyDataAccess, ReadOnlyCommandGuard, 4 sealed read-only domain records, DI registration, 17 dedicated unit tests covering all 4 ACs. |
GPT-5 Codex |
| 2026-05-06 | 1.1 | Adjusted LegacyDomainModels_AreReadOnlySealedRecords_AC4 test to permit init-only setters (record construction-time only) while still rejecting plain public setters. 50/50 backend tests passing. Story moved to review. |
claude-sonnet-4-6 |
| 2026-05-06 | 1.2 | Applied code-review fixes: real OleDb provider, production DI guard, required join keys, and hardened read-only SQL validation. 86/86 backend tests passing. | GPT-5 Codex |
Powered by TurnKey Linux.