You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

8.5KB

Story 1.6: Legacy Anti-Corruption Data Access Layer

Status: done

Story

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.

Acceptance Criteria

  1. Given a feature requests municipality or jurisdiction data When the anti-corruption layer is called Then it returns data joined by ID, JCode/JurisCode, or KitID without modifying any legacy table structure or content (NFR12)
  2. Given the anti-corruption layer executes any query When the operation is inspected Then only SELECT operations are permitted — INSERT, UPDATE, and DELETE on legacy entities are blocked at the layer boundary
  3. Given legacy data is returned through the layer When mapped to the application domain Then it is converted to strongly-typed domain model objects before being returned to any calling feature
  4. Given any application code outside the layer attempts to query legacy tables directly When reviewed in code Then no such direct access exists — the anti-corruption layer is the sole access point for legacy data

Tasks / Subtasks

  • Implement story behavior in aligned backend/frontend modules (AC: #1)
    • Add or update API/service/UI components required by the story scope
    • Keep legacy Access entities read-only and route writes to extension-layer structures
  • Cover acceptance criteria #2 in implementation and tests (AC: #2)
    • Add validation/error handling and UX state updates as needed
  • Cover acceptance criteria #3 in implementation and tests (AC: #3)
    • Add validation/error handling and UX state updates as needed
  • Cover acceptance criteria #4 in implementation and tests (AC: #4)
    • Add validation/error handling and UX state updates as needed
  • Validate and document completion evidence
    • Verify build/tests for touched modules
    • Capture changed files and any migration/config implications

Review Findings

  • [Review][Patch] Add a real Access/OleDb-backed legacy provider and register it when LegacyDatabase:ConnectionString is configured [Campaign_Tracker.Server/Program.cs:42]
  • [Review][Patch] Required legacy join keys are nullable in returned domain records [Campaign_Tracker.Server/LegacyData/Models/LegacyContact.cs:7]
  • [Review][Patch] ReadOnlyCommandGuard can miss later write keywords after a benign first occurrence [Campaign_Tracker.Server/LegacyData/ReadOnlyCommandGuard.cs:38]
  • [Review][Patch] ReadOnlyCommandGuard can reject valid SELECT statements when write words appear in literals or identifiers [Campaign_Tracker.Server/LegacyData/ReadOnlyCommandGuard.cs:38]

Dev Notes

  • Follow Epic 1 architecture constraints: ASP.NET Core + React separation, RBAC-aware patterns, and immutable legacy tables.
  • Reuse shared component and workflow patterns defined in UX and architecture docs; avoid parallel custom implementations.
  • Keep changes scoped to this story; do not pull forward Epic 2+ features.

Project Structure Notes

  • Backend: Campaign_Tracker.Server/
  • Frontend: campaign-tracker-client/
  • Story artifacts: _bmad-output/implementation-artifacts/

References

  • Story source: _bmad-output/planning-artifacts/epics.md (Epic 1 / Story 1.6)
  • Architecture constraints: _bmad-output/planning-artifacts/architecture.md
  • UX patterns: _bmad-output/planning-artifacts/ux-design-specification.md

Dev Agent Record

Agent Model Used

GPT-5 Codex

Debug Log References

  • Story generated from epic source and architecture/UX planning artifacts.
  • 2026-05-05: Created ILegacyDataAccess, InMemoryLegacyDataAccess, ReadOnlyCommandGuard, and 4 read-only domain records (LegacyJurisdiction, LegacyContact, LegacyKit, LegacyKitLabel).
  • 2026-05-05: Registered ILegacyDataAccess → InMemoryLegacyDataAccess singleton in Program.cs.
  • 2026-05-05: Wrote LegacyDataAccessTests (17 tests across all 4 ACs).
  • 2026-05-06: Initial test run showed 1 failure — 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.
  • 2026-05-06: dotnet test passed — 50/50 tests.

Completion Notes List

  • Story context created and marked ready-for-dev.
  • AC #1 — Created ILegacyDataAccess with query methods keyed exclusively by ID, JCode/JurisCode, and KitID. InMemoryLegacyDataAccess returns deterministic seeded records for development/testing without an Access database.
  • AC #2 — Interface exposes only read methods (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).
  • AC #3 — All return types are sealed record types in LegacyData/Models/. Tests confirm strong typing of nullable fields, boolean bit columns, and DateTime? columns.
  • AC #4 — Verified by static repo scan: no code outside 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.
  • DIProgram.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.
  • Test Adjustment — Updated 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.
  • All 50 backend tests pass.
  • Applied review fixes: added 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.
  • 2026-05-06: dotnet test .\Campaign_Tracker.Server.Tests\Campaign_Tracker.Server.Tests.csproj /p:UseAppHost=false passed (86 tests).
  • 2026-05-06: dotnet build .\campaign-tracker.sln /p:UseAppHost=false passed with 0 warnings and 0 errors.

File List

  • Campaign_Tracker.Server/LegacyData/ILegacyDataAccess.cs
  • Campaign_Tracker.Server/LegacyData/InMemoryLegacyDataAccess.cs
  • Campaign_Tracker.Server/LegacyData/OleDbLegacyDataAccess.cs
  • Campaign_Tracker.Server/LegacyData/LegacyDataAccessException.cs
  • Campaign_Tracker.Server/LegacyData/ReadOnlyCommandGuard.cs
  • Campaign_Tracker.Server/LegacyData/Models/LegacyJurisdiction.cs
  • Campaign_Tracker.Server/LegacyData/Models/LegacyContact.cs
  • Campaign_Tracker.Server/LegacyData/Models/LegacyKit.cs
  • Campaign_Tracker.Server/LegacyData/Models/LegacyKitLabel.cs
  • Campaign_Tracker.Server/Program.cs
  • Campaign_Tracker.Server.Tests/LegacyDataAccessTests.cs
  • Campaign_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

Change Log

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.