4.8 KiB
Code review — NEO-136 (E7M3-04)
Date: 2026-06-15
Scope: Branch NEO-136-e7m3-reputation-operations-apply-delta — commits e9abc8b … 536f635 vs main
Base: main
Verdict
Approve with nits
Summary
This branch lands E7M3-04: server-internal ReputationOperations.TryApplyDelta orchestrates standing mutation via IFactionStandingStore, append-only audit via IReputationDeltaStore, and compensating standing rollback when audit append fails. Supporting types ReputationApplyOutcome and ReputationApplyReasonCodes cover ops-level pre-flight denies and store passthrough. Ten AAA unit tests plus host DI smoke exercise happy path, clamp, unknown faction, invalid delta/id/source, audit-append rollback, non-writable player, and factory resolution. 780 server tests pass locally. Risk is low — infrastructure-only, no HTTP or Godot wiring (NEO-138+). One non-blocking correctness gap: compensating rollback uses -deltaAmount rather than the actual applied delta when clamping occurred, which can leave wrong standing on rare audit-append failure after a clamped apply.
Documentation checked
| Path | Result |
|---|---|
docs/plans/NEO-136-implementation-plan.md |
Matches — shipped ops, types, tests, README, module/alignment updates reconcile with plan; AC checklist marked complete. Plan flow step 1 cites explicit FactionStandingIds normalization at ops layer; implementation delegates player/faction normalization to stores (see Suggestions). |
docs/plans/E7M3-pre-production-backlog.md |
Matches — E7M3-04 scope (auditable apply orchestration, no client counterpart, no HTTP) aligns with implementation. |
docs/decomposition/modules/E7_M3_FactionReputationLedger.md |
Matches — Status line notes E7M3-04 ReputationOperations landed (NEO-136). |
docs/decomposition/modules/documentation_and_implementation_alignment.md |
Matches — E7.M3 row updated for E7M3-04 / NEO-136. |
docs/decomposition/modules/module_dependency_register.md |
N/A change — register row already lists FactionStanding / ReputationDelta contracts. |
server/README.md |
Matches — new ReputationOperations section documents commit order, rollback policy, pre-flight denies, and “do not call standing store alone.” |
Blocking issues
None.
Suggestions
- Compensating rollback delta when clamping — On audit append failure, rollback uses
-deltaAmount:
_ = standingStore.TryApplyStandingDelta(playerId, factionId, -deltaAmount);
When the standing apply was clamped, the stored change is mutation.NewStanding - mutation.PreviousStanding, not deltaAmount. Example: standing 95, request +50 → 100; rollback -50 yields 50, not 95. Use the applied delta instead:
var appliedDelta = mutation.NewStanding - mutation.PreviousStanding;
if (appliedDelta != 0)
{
_ = standingStore.TryApplyStandingDelta(playerId, factionId, -appliedDelta);
}
Add a test: seed standing near max, apply clamped delta with pre-seeded duplicate audit id, assert standing restored to pre-apply value. Prototype duplicate-id retries are unlikely via NEO-138 delivery idempotency, but this keeps the ops invariant sound.
- Ops-layer id normalization (plan alignment) — Plan §2 step 1 says normalize via
FactionStandingIdsbefore store calls. Stores already normalize internally (andInMemoryReputationDeltaStore.TryAppendnormalizes audit rows on persist), so behavior is correct for prototype callers. For consistency withRewardRouterOperationsand predictableAuditRowin outcomes, consider normalizingplayerId/factionIdonce at the ops boundary and building the audit row from normalized values.
Nits
- Nit:
TryApplyDelta_ShouldApplyStandingAndAppendAudit_WhenHappyPath—auditStore.GetDeltasForPlayer(PlayerId)runs in the Act phase; per csharp-style, verification reads belong in Assert. - Nit:
ReputationApplyOutcomeon store-boundary deny returns standings from the mutation outcome (good); pre-flightDeny()zeroes standings — callers must not treat0as “neutral read” oninvalid_delta/invalid_sourcedenies (documented implicitly by reason codes). - Nit: Telemetry hook comment for NEO-141 is correctly placed on the success path only — no action needed.
Verification
Commands run during review (passed):
dotnet test NeonSprawl.sln --filter "FullyQualifiedName~ReputationOperationsTests"
dotnet test NeonSprawl.sln
Author should confirm PR CI green before merge. No manual QA doc required (server engine only per plan). Intentional follow-on: reward bundle rep wiring (NEO-138), quest accept gate eval (NEO-137), HTTP GET standing (NEO-139), Godot HUD (NEO-142+).