neon-sprawl/docs/reviews/2026-06-07-NEO-126.md

4.0 KiB

Code review — NEO-126 (E7M2-03)

Date: 2026-06-07
Scope: Branch NEO-126-e7m2-reward-delivery-store vs origin/main — commits b20d9c12a8fe31
Base: origin/main

Verdict

Approve with nits — store contract, threading model, and tests mirror the NEO-107 encounter-event precedent; plan and backlog AC are satisfied; risk is low (infrastructure-only, no router or HTTP surface yet).

Summary

NEO-126 adds IRewardDeliveryStore and InMemoryRewardDeliveryStore under Game/Rewards/ for idempotent quest-completion reward delivery records. RewardDeliveryEvent carries item and skill XP grant snapshots plus a stable {playerId}:quest_complete:{questId} idempotency key; RewardDeliveryIds handles normalization and composite store keys. DI registers an in-memory singleton via AddRewardDeliveryStore() in Program.cs. Five AAA unit tests cover idempotency, fail-closed empty ids, grant summary readability, and host DI resolution; Bruno adds a health smoke for Program.cs pre-commit. Docs (implementation plan, E7M2-03 backlog AC, alignment register, server/README.md) are updated. No player-visible behavior until NEO-127 wires the router.

Documentation checked

Path Result
docs/plans/NEO-126-implementation-plan.md Matches — kickoff (in-memory only), acceptance criteria checked, reconciliation accurate.
docs/plans/E7M2-prototype-backlog.md (E7M2-03) Matches — AC checkboxes complete; infrastructure-only client counterpart noted.
docs/decomposition/modules/E7_M2_RewardAndUnlockRouter.md Partially matches — freeze idempotency key and RewardDeliveryEvent contract align; no NEO-126 implementation anchor yet (NEO-125 added a server-load paragraph; NEO-107 has encounter-store anchor).
docs/decomposition/modules/documentation_and_implementation_alignment.md Matches — E7.M2 row notes E7M2-03 delivery store; row correctly stays Planned until NEO-132 capstone.
docs/decomposition/modules/module_dependency_register.md Matches — no register change required for this slice.
server/README.md Matches — reward delivery store section documents interface, event fields, idempotency key, NEO-127/NEO-129 pointers.
Full-stack epic decomposition N/A — E7M2-03 is infrastructure-only; no client counterpart required.

Register / tracking: E7.M2 correctly remains Planned until capstone NEO-132; alignment update is appropriate.

Blocking issues

None.

Suggestions

  1. E7.M2 module anchor — Add a short Implementation anchor (server) bullet for NEO-126 in docs/decomposition/modules/E7_M2_RewardAndUnlockRouter.md (parity with the NEO-125 server-load and NEO-107 encounter-store paragraphs) so decomposition readers do not rely only on the alignment register and README.

  2. Case-normalization idempotency testMakeStoreKey lowercases ids, but no test proves a second TryRecord with different casing of the same player/quest is rejected. A one-liner extension of TryRecord_ShouldBeIdempotent_AtStoreLayer (e.g. replay with PlayerId = "DEV-LOCAL-1") would lock that behavior for NEO-127 replays.

Nits

  • Nit: TryRecord does not validate that deliveryEvent.IdempotencyKey matches RewardDeliveryIds.MakeIdempotencyKey(...) — same as InMemoryEncounterCompleteEventStore; NEO-127 router should populate both consistently.
  • Nit: TryClear is untested; acceptable for dev-only API (encounter store also omits a TryClear test).
  • Nit: Stored PlayerId / QuestId on the event are not rewritten to normalized form at record time; lookup normalizes via MakeStoreKey. Router (NEO-127) should pass normalized ids per plan table.

Verification

dotnet test server/NeonSprawl.Server.Tests --filter InMemoryRewardDeliveryStoreTests

Optional before merge:

  • Boot dev server; confirm /health 200 after AddRewardDeliveryStore() wiring (Bruno: bruno/neon-sprawl-server/quest-progress/Health after reward delivery store load.bru).