5.7 KiB
Code review — NEO-129 (E7M2-06)
Date: 2026-06-07
Scope: Branch NEO-129-quest-progress-completion-reward-summary vs main — commits 2b06eb2 … 78e805e
Base: main
Follow-up: Review suggestions and nits addressed in a subsequent commit on the same branch.
Verdict
Approve with nits — additive completionRewardSummary on completed quest-progress rows, sourced from IRewardDeliveryStore, matches the adopted plan and NEO-108 encounter precedent; mapper threading through QuestAcceptApi is correct; tests and Bruno cover primary AC paths; risk is low (server-only until NEO-131 client parse).
Summary
NEO-129 extends GET /game/players/{id}/quest-progress with optional completionRewardSummary on completed rows: nested itemGrants + skillXpGrants mapped from RewardDeliveryEvent snapshots via IRewardDeliveryStore.TryGet, omitted for not_started / active and when no delivery record exists (kickoff). QuestProgressApi.BuildSnapshot / MapQuestProgressRow accept playerId + deliveryStore; QuestAcceptApi.MapResponse uses the same mapper so embedded accept rows stay consistent. DTOs in QuestProgressListDtos.cs use JsonIgnore(WhenWritingNull) and preserve commit-time grant order (XML on mappers mirrors NEO-108). Eight AAA integration tests in QuestProgressApiTests assert omit on default/active, gather-intro salvage 25, operator-chain item + XP, and idempotent GET replay; Bruno adds gather-intro complete spine plus default GET omit assertion. Docs (plan reconciliation, E7.M2 anchor, alignment register, E7M2-06 backlog AC, server/README.md) are updated.
Documentation checked
| Path | Result |
|---|---|
docs/plans/NEO-129-implementation-plan.md |
Matches — kickoff nested shape and omit-on-miss adopted; reconciliation accurate; AC checked. |
docs/plans/E7M2-prototype-backlog.md (E7M2-06) |
Matches — AC checkboxes complete; client counterpart NEO-131 correctly deferred. |
docs/decomposition/modules/E7_M2_RewardAndUnlockRouter.md |
Matches — NEO-129 HTTP read anchor added; separated from NEO-128 wiring note. |
docs/decomposition/modules/documentation_and_implementation_alignment.md |
Matches — E7.M2 row notes E7M2-06 HTTP read; row correctly stays Planned until NEO-132 capstone. |
docs/decomposition/modules/module_dependency_register.md |
Matches — no register change required. |
server/README.md |
Matches — quest-progress section documents completionRewardSummary projection source and gather-intro sample. |
| Full-stack epic decomposition | N/A — E7M2-06 is server HTTP read; client counterpart NEO-131 explicitly out of scope. |
Register / tracking: E7.M2 correctly remains Planned until capstone NEO-132; alignment update is appropriate.
Blocking issues
None.
Suggestions
-
GET-refresh chain completion summary —Done.GetQuestProgress_ShouldCompleteChainTerminal_WhenTokenHeldAndInventoryRefreshRunsOnGetassertscompletedvia inventory refresh wiring but does not assertCompletionRewardSummary(unlike gather and operator-chain complete tests). That path exercises deliver-then-mark; adding item + skill XP asserts would lock summary projection on the GET side-effect completion path.AssertOperatorChainCompletionRewardSummaryon GET-refresh chain terminal test. -
Bruno idempotent second GET — The implementation plan Bruno AC calls for a second GET unchanged;Done. Pre-request first GET +Get quest progress after gather intro complete.bruperforms only one GET. Optional follow-up: duplicate GET in pre-request or a second request file to match plan and integration test coverage.bru.setVar; main GET test assertseqlwith stored summary. -
Completed without delivery record — Kickoff adopts omit-on-miss (softer than NEO-108 fail-fast). No test forcesDone.status: completedwith a store miss (e.g. directIPlayerQuestStateStoreseed withoutIRewardDeliveryStorerecord). Low priority given NEO-128 deliver-then-mark invariant in normal paths; a single defensive test would document the contract.GetQuestProgress_ShouldOmitCompletionRewardSummary_WhenCompletedWithoutDeliveryRecord.
Nits
-
Nit: Idempotent replay test (Done.GetQuestProgress_ShouldReturnSameCompletionRewardSummary_WhenGatherIntroCompletedTwice) compares only the firstskillXpGrantsline, not fullcompletionRewardSummaryobject equality (Bruno useseqlon arrays). Fine for single-line gather intro; operator-chain idempotent replay is untested.AssertCompletionRewardSummariesEqualhelper; added operator-chain idempotent replay test. -
Nit:Done. Null on active accept; summary on already-completed deny row.QuestAcceptApiTestsdo not assertCompletionRewardSummarynull on active accept embedded rows. Mapper consistency is covered indirectly via sharedMapQuestProgressRow; a one-line assert on existing accept-success tests would mirror GET omit coverage. -
Nit: Grant-order XML is present on
MapItemGrants/MapSkillXpGrants; C# operator-chain test asserts fields viaSingle/ index 0 (order-agnostic for single grants). Acceptable for prototype freeze table; Bruno gather test asserts orderedskillXpGrantsarray.
Verification
cd server && dotnet test NeonSprawl.Server.Tests/NeonSprawl.Server.Tests.csproj --filter "FullyQualifiedName~QuestProgressApiTests"
Optional: run Bruno folder bruno/neon-sprawl-server/quest-progress/ against a running dev server (especially Get quest progress after gather intro complete.bru).