5.3 KiB
Code review — NEO-149 contract completion encounter wiring
Date: 2026-06-27
Scope: Branch NEO-149-e7m4-06-contract-completion-encounter-wiring — commits 8d9d02f..bcf6fd2 vs origin/main
Base: origin/main
Verdict
Approve with nits
Summary
NEO-149 adds ContractCompletionOperations.TryCompleteOnEncounterClear — deliver-then-mark contract completion on encounter clear, following the NEO-128 quest precedent and NEO-148 router. The orchestrator resolves the active instance, matches encounterTemplateId, calls TryDeliverContractCompletion, marks the instance complete, and appends an outcome audit row. Encounter wiring invokes the hook after encounter loot and quest objective wiring; contract payout failure does not block encounter success (result discarded). Contract store/registry dependencies are plumbed through EncounterCombatWiring and AbilityCastApi. QuestFixtureRequest.resetContractInstanceIds clears instance, delivery, and outcome rows for Bruno/dev reset loops. Five unit tests, one DI integration test, and one encounter-completion wiring test cover match/mismatch, delivery deny, idempotent replay, and combined encounter + contract loot. Implementation plan reconciliation, server/README.md, and E7.M4 module anchor are updated. Server-only slice; client work explicitly deferred to NEO-153/NEO-154 after NEO-151 HTTP. Full suite: 904 tests passed locally.
Documentation checked
| Path | Result |
|---|---|
docs/plans/NEO-149-implementation-plan.md |
Matches — AC checklist complete; shipped reconciliation aligns with diff |
docs/plans/E7M4-pre-production-backlog.md (E7M4-06) |
Matches — orchestrator + encounter wiring in scope; HTTP Bruno loop deferred to NEO-151 |
docs/decomposition/modules/E7_M4_ContractMissionGenerator.md |
Matches — NEO-149 completion wiring anchor added |
docs/decomposition/modules/documentation_and_implementation_alignment.md |
Matches — E7.M4 row includes E7M4-06 / NEO-149 (orchestrator + encounter wiring + quest-fixture reset) |
server/README.md |
Matches — contract completion operations section, wiring order, reason codes, fixture reset |
| Client counterpart (NEO-153, NEO-154) | N/A — explicitly out of scope; no prototype-complete claim |
Register/tracking table updated for E7.M4 (NEO-149).
Blocking issues
None.
Suggestions
-
Update implementation alignment register — Add E7M4-06 / NEO-149 landed to the E7.M4 row inDone. E7.M4 row + plan link updated.docs/decomposition/modules/documentation_and_implementation_alignment.md(orchestrator + encounter wiring + quest-fixture reset), consistent with NEO-144–NEO-148 entries. -
Quest-fixture test for contract reset —Done.QuestFixtureOperationsnow clears contract instance, delivery, and outcome rows viaresetContractInstanceIds, butQuestFixtureApiTestshas no parallel toPostQuestFixture_ShouldClearRewardDeliveryAndAudit_WhenResetQuestIdsProvided. A test that issues a contract, completes it, resets via fixture, and asserts stores are empty would lock the NEO-148 follow-on reset path.PostQuestFixture_ShouldClearContractStores_WhenResetContractInstanceIdsProvided. -
Done. Final deny usesTryMarkCompletefailure reason code — When delivery succeeds butTryMarkCompletefails without leaving a completed snapshot, the orchestrator returnsinvalid_ids(line 122 inContractCompletionOperations.cs).QuestStateOperations.TryMarkCompleteuses richer fallback codes (NotActive,UnknownPlayer). Consider aligning deny semantics in a follow-on if store-failure paths become testable; not blocking for prototype (same deliver-then-mark stuck-state risk as quest path when mark loses after delivery).player_not_writable,instance_not_active, orinstance_not_foundinstead ofinvalid_ids.
Nits
- Nit: Idempotent replay after successful completion returns
no_active_contract(instance no longer active) rather than hitting the defensivecompletedsnapshot branch at lines 59–61. Behavior is correct — no double grant — but the reason code differs from the plan’s “completed replay” wording. Nit:Done.TryCompleteOnEncounterClear_ShouldBeIdempotent_WhenReplayedperforms success assertions on the first completion inside Arrange; prefer moving first-call verification to Assert after Act for strict AAA, or extract a shared setup helper.IssueAndCompleteOncesetup helper; replay test Act/Assert only.- Nit: Plan lists optional
EncounterCombatWiringend-to-end issue → cast → contract completed; only compile-fix call-site updates landed. Acceptable given integration + encounter completion tests cover the path. - Nit: Bruno
Reset contract instance via quest fixture.bruuses placeholderci_000…id — fine until NEO-151 wires issue → clear → GET; helperresetContractInstancesinbruno-dev-fixture-helper.jsis the useful piece for that story.
Verification
dotnet test NeonSprawl.sln
dotnet test NeonSprawl.sln --filter "FullyQualifiedName~ContractCompletionOperations|FullyQualifiedName~EncounterCompletionOperationsTests.TryCompleteAndGrant_ShouldApplyContractBundle"
Manual Godot QA: none (server engine per plan). Bruno issue → clear → GET loop: NEO-151.