neon-sprawl/docs/reviews/2026-06-15-NEO-137.md

57 lines
5.9 KiB
Markdown

# Code review — NEO-137 (E7M3-05)
**Date:** 2026-06-15
**Scope:** Branch `NEO-137-e7m3-faction-gate-operations-tryaccept` — commits `1c3d075``0241cc3` vs `main`
**Base:** `main`
**Follow-up:** Review findings addressed in follow-up commits (Bruno CI fix via quest fixture API, multi-rule test, README, nits).
## Verdict
~~**Request changes**~~ **Approved after follow-up**
## Summary
This branch lands **E7M3-05**: **`FactionGateOperations.TryEvaluate`** evaluates quest **`FactionGateRule`** rows with vacuous-AND semantics (`standing >= minStanding`), wired into **`QuestStateOperations.TryAccept`** after prerequisite checks and before activation. Quest-level denies surface **`faction_gate_blocked`** via **`QuestStateReasonCodes.FactionGateBlocked`**; ops-level **`FactionGateEvaluateOutcome`** retains failing-rule context for future telemetry/HUD. **`IFactionStandingStore`** threads through **`QuestAcceptApi`** and test helpers. Coverage is solid: five unit tests, three integration tests on grid contract (deny at 0/14, success at 15), HTTP deny test, and host DI smoke. **`790`** server tests pass locally. Core server logic matches the plan and NEO-136 ops precedent. ~~One **blocking** gap: the new Bruno deny smoke runs in CI without completing **`prototype_quest_operator_chain`**, so the collection will assert the wrong **`reasonCode`** (`prerequisite_incomplete` instead of **`faction_gate_blocked`**).~~ **Done.** Bruno pre-request calls **`POST …/__dev/quest-fixture`** to mark operator chain completed without rep delivery; CI enables **`Game:EnableQuestFixtureApi`**.
## Documentation checked
| Path | Result |
|------|--------|
| `docs/plans/NEO-137-implementation-plan.md` | **Matches** — ops, types, wire site, reason code, tests, README, module/alignment updates; AC checklist marked complete; success Bruno deferred to NEO-138 per kickoff. |
| `docs/plans/E7M3-pre-production-backlog.md` | **Matches** — E7M3-05 scope (quest accept gate eval, no HTTP standing read, no client counterpart). |
| `docs/decomposition/modules/E7_M3_FactionReputationLedger.md` | **Matches** — Status line notes E7M3-05 `FactionGateOperations` landed (NEO-137). |
| `docs/decomposition/modules/documentation_and_implementation_alignment.md` | **Matches** — E7.M3 row updated for E7M3-05 / NEO-137. |
| `docs/decomposition/modules/module_dependency_register.md` | **N/A change** — register row already lists `FactionGateRule` contract. |
| `docs/decomposition/modules/E7_M1_QuestStateMachine.md` | **Matches**`TryAccept` gate hook site behavior aligns with module responsibilities. |
| `server/README.md` | **Matches** — new FactionGateOperations section + quest accept POST table includes `faction_gate_blocked`. |
## Blocking issues
1. ~~**Bruno CI will fail on faction-gate deny smoke.** `bruno/neon-sprawl-server/quest-progress/Accept grid contract faction gate deny.bru` (seq **10**) asserts `reasonCode === "faction_gate_blocked"`, but the quest-progress collection never completes **`prototype_quest_operator_chain`** before that request (prior steps only accept/deny gather/refine through seq **8**). **`TryAccept`** checks prerequisites before gate eval, so a fresh dev player receives **`prerequisite_incomplete`**, not **`faction_gate_blocked`**. `.github/workflows/dotnet.yml` runs the full Bruno collection with **`--bail`** on every push touching `bruno/neon-sprawl-server/**`. Fix options (pick one): add prerequisite Bruno steps (accept + complete operator chain, or a dev fixture seed if/when available), add a **`script:pre-request`** that marks the chain completed the way integration tests do, or defer/remove this request from the CI collection until NEO-138 makes the spine reachable. The `.bru` **docs** block acknowledges the prerequisite but the collection does not implement it.~~ **Done.** Added **`QuestFixtureApi`** (`POST …/__dev/quest-fixture`) + Bruno pre-request; CI **`Game:EnableQuestFixtureApi`**.
## Suggestions
1. ~~**Multi-rule AND unit test.** Prototype grid contract has one gate today; add a **`TryEvaluate`** test with two rules (one pass, one fail) to lock AND semantics if content adds multi-faction gates before E4.M1 travel gates reuse the ops.~~ **Done.** `TryEvaluate_ShouldDeny_WhenSecondRuleFailsInAndList` in `FactionGateOperationsTests.cs`.
2. ~~**Bruno seq documentation in README.** `server/README.md` notes success Bruno is deferred to NEO-138; add one line that deny Bruno requires operator-chain-complete setup (or list the intended seq after prerequisite steps land) so manual runs do not surprise operators.~~ **Done.** FactionGateOperations README section documents seq **10** + quest-fixture pre-request.
## Nits
- ~~Nit: **`TryAccept`** maps all gate failures to **`faction_gate_blocked`**, discarding ops-level **`unknown_faction`** from the standing store — intentional per plan (quest-level code only in HTTP), but worth a one-line XML remark on the gate deny branch if NEO-141 telemetry needs to distinguish store vs threshold denies later.~~ **Done.** Comment on gate deny branch in `QuestStateOperations.cs`.
- ~~Nit: **`FactionGateOperationsTests`** host DI smoke asserts **`gate_blocked`** at standing 0 without seeding standing — correct behavior, but a brief comment that missing standing reads as **0** would aid future readers.~~ **Done.** Arrange comment in host DI smoke test.
## Verification
```bash
# Full server suite (author reported 790 green)
dotnet test NeonSprawl.sln
# Faction-gate-focused filter
dotnet test NeonSprawl.sln --filter "FullyQualifiedName~FactionGate|FullyQualifiedName~GridContract"
# Bruno (will fail on seq 10 until prerequisite setup is added)
cd bruno/neon-sprawl-server
npx --yes @usebruno/cli@3.3.0 run quest-progress --env Local --sandbox=developer --bail
```
Manual: after fixing Bruno prerequisites, run **`Accept grid contract faction gate deny.bru`** only when **`prototype_quest_operator_chain`** is **completed** and Grid Operators standing is **< 15**.