Files
satellite-provider/_docs/LESSONS.md
T
Oleksandr Bezdieniezhnykh ca0ca9f2a4
ci/woodpecker/push/01-test Pipeline was successful
ci/woodpecker/push/02-build-push Pipeline was successful
[AZ-491] [AZ-492] [AZ-493] [AZ-494] [AZ-495] [AZ-496] Cycle 3 Step 17: retrospective + close cycle
Cycle-3 retrospective:
  - 6 tasks (AZ-491..AZ-496), 5 batches, 18 SP delivered.
  - 100% code review pass rate (5/5 PASS_WITH_WARNINGS, 0 FAIL).
  - 0 Critical/High/Medium review findings; 7 distinct Low.
  - Security audit PASS_WITH_WARNINGS: 0 new Medium, 3 Low (all
    test-only or operator-CLI), 2 Informational, 1 False Positive.
  - Net Architecture delta: **-3** (F-AUTH-2 + D1 + D3 RESOLVED;
    only new findings are Low test-side surfaces). First
    net-negative cycle on record.
  - 5 of 6 tasks completed first attempt (no post-review fix
    commits). Cycle-2's 2 prior-retro actions all translated to
    closed work (AZ-491 from Action 1, AZ-492 from Action 2,
    AZ-493 from Action 3).

Top 3 cycle-4 improvement actions surfaced:
  1. Execute the perf harness to capture PT-07/PT-08 baseline.
  2. Bump TestSupport JWT pins 7.0.3 → 7.1.2+ (D4 NU1902 cleanup).
  3. Add `workspace:` tag to cross-repo ACs in task-spec writing
     and render them separately in the traceability matrix.

3 new ring-buffer lessons appended to _docs/LESSONS.md:
  - [process] Option-B forcing functions for cross-team blockers.
  - [process] ACs prescribing a measurement should also prescribe
    the collection path.
  - [process] Cross-repo-write ACs need workspace tags.

Structural snapshot at structure_2026-05-12_cycle3.md records the
new SatelliteProvider.TestSupport project (+2 ProjectReference edges
into it; no production-layer dependents) and the AZ-496 package
bumps (8.0.21 → 8.0.25).

Cycle 3 COMPLETE. State advanced to Step 9 (New Task) for cycle 4
per existing-code flow Re-Entry After Completion.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-12 03:46:41 +03:00

6.3 KiB

Engineering Lessons

Recurring bugs, surprising library behaviors, and process insights extracted from completed cycles. Newest at the top. Keep entries short — this is for fast scanning at the start of new cycles, not exhaustive history.

This file has two layers:

  • Deep engineering lessons (L-NNN): library bugs, architectural insights, multi-paragraph context. Persist forever.
  • Ring buffer at the bottom: most recent 15 single-sentence lessons emitted by the retrospective skill, consumed by new-task / plan / decompose / autodev Step 0. Oldest entries drop off the top.

Categories: estimation · architecture · testing · dependencies · tooling · process


L-001 — Dapper TypeHandler<T> is bypassed for enum types during read deserialization

Cycle: 1 (AZ-484) Discovered by: integration test failure (Error parsing column 12 (source=google_maps - String)); root-caused via web search to long-standing Dapper issue #259. Affects: Dapper 2.1.35 (and most other versions until the proposed Settings.PreferTypeHandlersForEnums opt-in in PR #2200, not yet merged).

What happens Registering SqlMapper.AddTypeHandler(new MyEnumHandler()) for an enum type — even via SqlMapper.TypeHandler<TEnum> — works for writes (the handler's SetValue is invoked for parameter binding) but is silently bypassed for reads. Dapper's IL-emitted deserializer checks IsEnum first and falls back to Enum.TryParse(string, ignoreCase: true).

Why this is dangerous If the enum's wire string happens to match a member name case-insensitively (e.g., RegionStatus.Failed"failed"), the bypass goes unnoticed and round-trip works accidentally. The bug only surfaces when the wire format diverges from the C# member name (e.g., TileSource.GoogleMaps"google_maps"Enum.TryParse("google_maps") does not match GoogleMaps because of the underscore).

Recommended approach

  • Do not rely on SqlMapper.TypeHandler<TEnum> for read-side enum mapping unless the wire values match the enum member names case-insensitively.
  • For enums whose wire format diverges (snake_case, kebab-case, custom IDs), store the entity field as string and provide an explicit converter (*Converter.ToWireValue / FromWireValue) for use at the service-layer boundary. This is what AZ-484 does for TileEntity.SourceTileSourceConverter.
  • Unit-test the converter directly. Do not assume that round-tripping through Dapper proves anything for enums.

Detection

  • Unit tests of the type handler in isolation will pass even when the handler is bypassed at runtime.
  • Failure surfaces only at integration-test time when the actual SELECT runs.
  • If you must keep an enum-typed field, write at minimum one integration test that reads the enum back through Dapper from a real database row.

Ring buffer (last 15 entries — newest at top)

  • [2026-05-12] [process] For cross-team blockers (admin team must supply config values, etc.), prefer an Option-B forcing function (ship the validation/scaffolding with prod-empty config that fails-fast at deploy) over deferring the entire task — the fail-fast contract makes the cross-team conversation impossible to skip and ships the in-workspace work in the current cycle (cycle 3: AZ-494 shipped iss/aud validation with empty prod appsettings so deploy must supply real values). Source: _docs/06_metrics/retro_2026-05-12_cycle3.md
  • [2026-05-12] [process] ACs that prescribe a specific measurement or sentinel mechanism (e.g. "per-item latency < 50ms", "guard fires when DB name contains _test") should also prescribe — or explicitly defer — the path for collecting / enforcing it, or implementations will substitute proxies / equivalents that look like spec drift in review (cycle 3: AZ-492 PT-08 per-item gate cost became a derived proxy; AZ-493 DB-name guard became Host-allowlist). Source: _docs/06_metrics/retro_2026-05-12_cycle3.md
  • [2026-05-12] [process] ACs that require cross-repo writes should be tagged with the target workspace and rendered separately in the traceability matrix — mixing them with in-workspace ACs makes "correctly deferred" indistinguishable from "incomplete work" (cycle 3: AZ-494 AC-7 deferred for the suite-repo write; matrix renders as ◐ deferred which is ambiguous). Source: _docs/06_metrics/retro_2026-05-12_cycle3.md
  • [2026-05-11] [testing] Test helpers shared across unit + integration projects must live in one consolidated location — duplicate near-identical copies will diverge and require parallel fixes (cycle 2: JwtTokenFactory.cs and JwtTestHelpers.cs had the same Expires < NotBefore bug fixed in separate commits). Source: _docs/06_metrics/retro_2026-05-11_cycle2.md
  • [2026-05-11] [process] Deferred-status NFR entries are allowed at most ONCE per NFR — if a Deferred NFR has not landed by the end of the cycle that follows the one in which it was deferred, the harness work must be promoted to a real PBI before any new NFR is accepted as Deferred (cycle 2 inherited cycle 1's PT-07 + added PT-08 + JWT-attach script-rot). Source: _docs/06_metrics/retro_2026-05-11_cycle2.md
  • [2026-05-11] [testing] Integration tests must explicitly reset DB state at startup — relying on wallclock seeds or "tests probably won't collide" is a workaround, not isolation; the persistent Postgres volume in docker-compose makes test data accumulation the default state (cycle 2: UavUploadTests._coordinateCounter collision was patched with a wallclock seed instead of a real DB-reset hook). Source: _docs/06_metrics/retro_2026-05-11_cycle2.md
  • [2026-05-11] [testing] Persisted enums need a Dapper read-roundtrip integration test — unit-testing the type handler in isolation does not prove read-side behavior (see L-001). Source: _docs/06_metrics/retro_2026-05-11.md
  • [2026-05-11] [process] NFR test-spec additions must include the runner-script implementation in the same step, or be tagged "Deferred — harness work tracked in "; otherwise scenarios accumulate as Unverified across cycles. Source: _docs/06_metrics/retro_2026-05-11.md
  • [2026-05-11] [estimation] Task-spec test-site-count estimates must be backed by an explicit grep evidence block, not pattern-matched against neighboring code (AZ-484 spec said ~3 sites in RegionServiceTests; actual = 0). Source: _docs/06_metrics/retro_2026-05-11.md