25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

7.8KB


epic: 1 title: Foundation, Auth, Municipality Profiles & Admin Seed date: 2026-05-07 facilitator: Bob (Scrum Master) participant: Daniel (Project Lead, solo dev with rotating AI agents) status: completed

Epic 1 Retrospective

Epic Summary

Stories delivered 13 of 13 (100%)
Test growth Backend 4 → 155, Frontend 18 → 45, no regressions
Course corrections absorbed mid-epic 2 (Logout SCP, Spreadsheet-Source SCP)
Stories that landed clean (≤1 review patch) 1-1, 1-2, 1-12, 1-13
Stories with heavy review cycles (6+ patches) 1-3, 1-7, 1-8, 1-9, 1-10, 1-11 (14 patches)
Reusable patterns hardened IAuditService (append-only), ILegacyLinkedRecord(Provider), AuthIntegrationTestFactory
New CI gate .gitea/workflows/release-gates.yml

What Went Well

  • Reusable infrastructure paid off fast. ILegacyLinkedRecord from Story 1.8 became the integration seam every later municipality story (1-10, 1-12) plugged into automatically.
  • Review gate caught real defects. Two SCPs (logout flow, late spreadsheet source) landed mid-epic with zero rework — review surfaced gaps before done.
  • Append-only audit contract was the right call. The 1-3 → 1-5 cascade where the OIDC review motivated IAuditService having no update/delete methods is now the de-facto pattern.
  • TDD-first discipline correlated cleanly with quality. Stories 1-12 and 1-13, which explicitly noted “added failing tests before implementation,” combined for 1 review patch.

What Struggled

  • “Contract-shape over behavior” pattern. Stories 1-7, 1-8, 1-9, and 1-11 each shipped non-functional production wiring on first submission while passing happy-path tests. Examples: 1-7 compared schema baseline to itself, 1-9's SeedAsync() was a placeholder and not DI-registered, 1-11 wouldn't compile.
  • “Manual endpoint shipped, scheduler/pipeline integration deferred” pattern. Release gate (1-7), nightly integrity check (1-8), and seed (1-9) all shipped admin/manual triggers without the pipeline integration that was the actual deliverable.
  • Validation gaps on inputs that look valid. OIDC state never validated (1-3), ReadOnlyCommandGuard rejecting valid SELECTs (1-6), zero/negative IDs accepted (1-8), raw-string rules with no scope metadata (1-9).
  • Test-isolation rediscovery. AuthIntegrationTestFactory had to override IAuditService (1-5) and again ILegacyDataAccess (1-10). Pattern emerged twice — now a guardrail.

Key Insight (Driving Action Items)

Routing the work to the right model matters. Local-hosted AI handled bounded CRUD/UI-binding work (1-1, 1-2, 1-12, 1-13) cleanly. The same model struggled where the AC was satisfied by a stub but the actual deliverable was system-level wiring — DI registration, scheduler hookup, pipeline integration, real implementation behind an interface. That's exactly the heavy-rework set: 1-7, 1-8, 1-9, 1-11.

Story 1-11 (confirmed local AI, 14 review patches, didn't compile on first pass) is the canonical signature of the mismatch.

Action Items

1. Tag stories with ai_routing field at creation time

Add to story frontmatter:

ai_routing: local-ok    # CRUD shape, bounded surface, contract == behavior
ai_routing: cloud-only  # infrastructure, wiring, multi-component integration

Heuristics:

  • local-ok: single-table CRUD, UI binding to existing API, read-only views, isolated test additions.
  • cloud-only: introduces a new cross-cutting contract, requires DI registration in Program.cs, requires scheduler/pipeline hookup, or has a “real implementation behind interface” requirement.

Routing applied to Epic 2:

Story Routing
2.1 Municipality-to-Cycle Kanban Entry Point cloud-only
2.2 Create Election-Cycle Job local-ok
2.3 Election-Cycle Key Dates local-ok
2.4 Prior-Cycle Defaults Application cloud-only
2.5 Readiness Status & SafeCommitRail cloud-only
2.6 Spreadsheet Import & Column Mapping cloud-only

Owner: Daniel Trigger: Apply during bmad-create-story for next Epic 2 story Success criteria: Every Epic 2 story has ai_routing set before development begins

2. “Wiring Proven” acceptance criterion for cloud-only stories

Add a standard AC to any cloud-only story:

“The production caller invokes this on the real path — verified by an integration test that fails if DI registration, scheduler hookup, or pipeline gate is missing.”

Why: Would have caught 1-7, 1-8, 1-9, 1-11 on first pass. Closes the “contract satisfies AC but production wiring is absent” gap. Owner: Daniel (apply at story creation) Success criteria: No cloud-only story merged without this AC verified

3. Tests-first requirement on cloud-only stories

Make TDD non-negotiable on cloud-only stories: failing test added and committed before implementation. Optional on local-ok stories.

Why: 1-12 and 1-13 used this discipline and combined for 1 review patch. 1-11 (no tests-first) took 14. Owner: Daniel (enforce via dev-story workflow) Success criteria: Cloud-only story records explicitly note “tests added before implementation”

Carried-Forward Items (Deferred from Epic 1)

Daniel chose to carry these forward rather than schedule a hardening pass. Track and address opportunistically inside Epic 2 stories where they intersect.

HIGH severity

  • AuthorizationProbeController ships canned operational routes in production controllers (from 1-4)
  • Audit log JSONL files committed to repo with actor identities, trace IDs (from 1-7)
  • OIDC workspacePath not validated as relative path → latent open-redirect (from 1-3)

MEDIUM severity

  • JCode embedded-whitespace mismatch between GetAllJurisdictionsAsync and GetByJCode (from 1-10) — relevant to Story 2.2 join paths
  • Hardcoded audit Outcome="updated display name" becomes misleading once profile gains fields (from 1-10)
  • pendingCallbackSequence not scoped per callback invocation; double-mount of useOidcSession would skip CSRF (from 1-3)

LOW severity

  • ProfileId uses Guid.ToString("N") (no hyphens) — cross-system UUID format risk
  • CreatedAt stored but absent from API DTO (1-10)
  • MunicipalityAddress.State is free-text with no validation (1-11)
  • MunicipalityId existence not validated pre-insert; surfaces as DbUpdateException (1-11)
  • Post-create refresh() doesn't reload jurisdiction list (1-10)

Readiness Assessment

Dimension Status
Testing & Quality Solid — 155 backend / 45 frontend, monotonic growth
Deployment Local-only (no production deploy planned for Epic 1 alone)
Stakeholder Acceptance Deferred — no external review until Epic 2+ adds visible value
Technical Health Feels solid (Daniel's gut-check)
Unresolved Blockers None — deferred items carried forward, not blocking

Next Epic Preview — Epic 2: Election-Cycle Job Creation & Planning

6 stories, all already at ready-for-dev. Direct dependencies on Epic 1 work are intact:

  • Legacy join keys (1-6, 1-8) → every Epic 2 story
  • Seed required-field rules (1-9) → Story 2.5 readiness/SafeCommitRail
  • Audit service (1-5) → all Epic 2 commits
  • Workspace shell + auth (1-2, 1-3, 1-4) → Story 2.1 kanban entry point
  • Prior-cycle defaults view (1-13) → Story 2.4 applies those defaults

No epic update required. Nothing from Epic 1 invalidates Epic 2's current plan. Story 2.6 (spreadsheet import) was added via SCP and is already accounted for in the story list.

Critical Path Before Epic 2 Kickoff

None. Daniel can begin bmad-create-story for Story 2.1 in a fresh context window.

Significant Discoveries

None requiring epic plan updates.

Commitments Recap

  • 3 action items (story routing tagging, wiring-proven AC, tests-first on cloud-only)
  • 0 critical-path items
  • 11 deferred items carried forward into Epic 2 work

Retrospective facilitated 2026-05-07.

Powered by TurnKey Linux.