From ef75111b2ac3080f5708959a3a0105661970f6e6 Mon Sep 17 00:00:00 2001 From: VinPropane Date: Wed, 17 Jun 2026 19:21:48 -0400 Subject: [PATCH 1/6] NEO-140: remove server-only manual QA doc after story end Manual QA checklists are for client-facing stories; Bruno and dotnet test cover this HTTP projection slice. --- docs/manual-qa/NEO-140.md | 40 ----------------------- docs/plans/NEO-140-implementation-plan.md | 4 +-- docs/reviews/2026-06-17-NEO-140.md | 4 +-- 3 files changed, 3 insertions(+), 45 deletions(-) delete mode 100644 docs/manual-qa/NEO-140.md diff --git a/docs/manual-qa/NEO-140.md b/docs/manual-qa/NEO-140.md deleted file mode 100644 index 6421cce..0000000 --- a/docs/manual-qa/NEO-140.md +++ /dev/null @@ -1,40 +0,0 @@ -# NEO-140 — Manual QA checklist - -| Field | Value | -|-------|-------| -| Key | NEO-140 | -| Title | E7M3-08: Extend quest HTTP projections for rep + gates | -| Linear | https://linear.app/neon-sprawl/issue/NEO-140/e7m3-08-extend-quest-http-projections-for-rep-gates | -| Plan | `docs/plans/NEO-140-implementation-plan.md` | -| Branch | `NEO-140-e7m3-quest-http-rep-gate-projections` | - -## Preconditions - -- Server running: `cd server/NeonSprawl.Server && dotnet run` (in-memory dev player `dev-local-1`). -- Bruno collection `bruno/neon-sprawl-server` loaded with `baseUrl` = `http://localhost:5253`, `playerId` = `dev-local-1`. -- NEO-138 reward router rep grants and NEO-139 faction-standing GET landed on `main`. - -## Setup sanity - -- [ ] `curl -sS http://localhost:5253/health` returns `"status":"ok"`. -- [ ] `dotnet test NeonSprawl.sln` passes (821+ tests). - -## Quest definitions — factionGateRules - -- [ ] Run Bruno **`quest-definitions/Get quest definitions`**. Response **200**; `prototype_quest_grid_contract.factionGateRules` = `[{ factionId: prototype_faction_grid_operators, minStanding: 15 }]`. -- [ ] Same response: `prototype_quest_gather_intro` and `prototype_quest_operator_chain` have **no** `factionGateRules` key. -- [ ] `curl -sS http://localhost:5253/game/world/quest-definitions | jq '.quests[] | select(.id=="prototype_quest_grid_contract") | .factionGateRules'` matches the Bruno assertion. - -## Quest progress — completionRewardSummary.reputationGrants - -- [ ] Run Bruno **`quest-progress/Get quest progress after gather intro complete`**. Gather row `completionRewardSummary.reputationGrants` = `[]`. -- [ ] Run Bruno **`quest-progress/Get quest progress after operator chain complete`**. Operator-chain row `completionRewardSummary.reputationGrants` = `[{ factionId: prototype_faction_grid_operators, amount: 15 }]`; item and skill XP lines unchanged from NEO-129 freeze. -- [ ] Second GET in the operator-chain bru matches the first (idempotent summary). - -## No regressions - -- [ ] Bruno **`faction-standing/Get faction standing after operator chain`** still shows Grid Operators standing **15** after operator-chain flow. -- [ ] Bruno **`quest-progress/Accept grid contract after operator chain`** accepts grid contract when rep gate met and embedded row **`completionRewardSummary.reputationGrants`** = `[{ factionId: prototype_faction_rust_collective, amount: 10 }]`. -- [ ] NEO-129 gather-intro **`completionRewardSummary`** shape unchanged except new empty **`reputationGrants`** array. - -**Client HUD verification** deferred to [NEO-142 (Godot HUD)](https://linear.app/neon-sprawl/issue/NEO-142/e7m3-10-client-faction-standing-gate-feedback-hud-godot) — this story is server HTTP projection only. diff --git a/docs/plans/NEO-140-implementation-plan.md b/docs/plans/NEO-140-implementation-plan.md index 641d413..be56640 100644 --- a/docs/plans/NEO-140-implementation-plan.md +++ b/docs/plans/NEO-140-implementation-plan.md @@ -63,9 +63,7 @@ Client can see gate requirements and completion rep summary without local math. - **Quest progress GET:** `QuestProgressApi.MapCompletionRewardSummary` maps `RewardDeliveryEvent.GrantedReputation` → `reputationGrants` (`QuestReputationGrantJson`: `factionId`, `amount`); required array when summary present. - **Accept POST:** inherits updated mapper via `QuestProgressApi.MapQuestProgressRow` — no accept-specific changes. - **Tests:** `QuestDefinitionsWorldApiTests` grid-contract gate row + wire-level `factionGateRules` omission; `QuestProgressApiTests` operator-chain +15 rep, grid-contract +10 rep, gather-intro empty rep; **823** tests green. -- **Bruno:** extended `Get quest definitions.bru`; new `Get quest progress after operator chain complete.bru`; gather-intro bru asserts empty `reputationGrants`. -- **Docs:** `server/README.md` quest-definitions + quest-progress sections; E7.M3 module + alignment register updated. -- **Manual QA:** [`docs/manual-qa/NEO-140.md`](../manual-qa/NEO-140.md). +- **Manual QA:** none — server-only HTTP projections; Bruno + `dotnet test` cover verification (manual QA checklists reserved for client-facing stories per project convention). ## Technical approach diff --git a/docs/reviews/2026-06-17-NEO-140.md b/docs/reviews/2026-06-17-NEO-140.md index c9f38d4..5657a8f 100644 --- a/docs/reviews/2026-06-17-NEO-140.md +++ b/docs/reviews/2026-06-17-NEO-140.md @@ -23,7 +23,7 @@ This branch lands **E7M3-08**: additive HTTP projections so clients can read fac | `docs/decomposition/modules/E7_M3_FactionReputationLedger.md` | **Matches** — Status line notes E7M3-08 landed (NEO-140). | | `docs/decomposition/modules/documentation_and_implementation_alignment.md` | **Matches** — E7.M3 row updated with quest HTTP projection links. | | `docs/decomposition/modules/module_dependency_register.md` | **N/A change** — no register row edit required beyond module doc / alignment table. | -| `docs/manual-qa/NEO-140.md` | **Matches** — Bruno + curl checklist covers gate rules and rep summary assertions. | +| `docs/manual-qa/NEO-140.md` | **Removed post-merge** — server-only story; manual QA reserved for client-facing work (story-end decision). | | `server/README.md` | **Matches** — Quest-definitions and quest-progress sections document new fields with NEO-140 cross-links. | ## Blocking issues @@ -48,7 +48,7 @@ None. cd /Users/don/neon-sprawl && dotnet test NeonSprawl.sln ``` -Manual (see `docs/manual-qa/NEO-140.md`): +Manual (Bruno — no separate manual QA doc for this server-only story): - `cd server/NeonSprawl.Server && dotnet run` - Bruno: `quest-definitions/Get quest definitions` From 5ab81215ad8232036af9d20d85b93da30323d90a Mon Sep 17 00:00:00 2001 From: VinPropane Date: Wed, 17 Jun 2026 19:23:25 -0400 Subject: [PATCH 2/6] NEO-141: add faction telemetry hook sites implementation plan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Kickoff plan for E7M3-09 — consolidate comment-only reputation_delta and faction_gate_blocked anchors from NEO-136/137/138 per NEO-121/130 pattern. --- docs/plans/NEO-141-implementation-plan.md | 162 ++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 docs/plans/NEO-141-implementation-plan.md diff --git a/docs/plans/NEO-141-implementation-plan.md b/docs/plans/NEO-141-implementation-plan.md new file mode 100644 index 0000000..3f24c62 --- /dev/null +++ b/docs/plans/NEO-141-implementation-plan.md @@ -0,0 +1,162 @@ +# NEO-141 — E7M3-09: Faction telemetry hook sites (comment-only) + +**Linear:** [NEO-141](https://linear.app/neon-sprawl/issue/NEO-141) +**Branch:** `NEO-141-faction-telemetry-hooks` +**Backlog:** [E7M3-pre-production-backlog.md](E7M3-pre-production-backlog.md) — **E7M3-09** +**Module:** [E7_M3_FactionReputationLedger.md](../decomposition/modules/E7_M3_FactionReputationLedger.md) +**Pattern:** [NEO-121](NEO-121-implementation-plan.md) / [NEO-130](NEO-130-implementation-plan.md) — comment-only engine anchors + `TODO(E9.M1)`; engine-only (no duplicate API-layer hooks) +**Precursors:** [NEO-136](https://linear.app/neon-sprawl/issue/NEO-136) **`Done`** — `ReputationOperations.TryApplyDelta` + partial `reputation_delta` hook comment; [NEO-137](https://linear.app/neon-sprawl/issue/NEO-137) **`Done`** — `FactionGateOperations.TryEvaluate` + `faction_gate_blocked` hook comment; [NEO-138](https://linear.app/neon-sprawl/issue/NEO-138) **`Done`** — router rep apply + duplicate router stub comment (to consolidate here) +**Blocks:** [NEO-143](https://linear.app/neon-sprawl/issue/NEO-143) (E7M3-11 client capstone — module telemetry vocabulary complete) +**Client counterpart:** none (infrastructure-only per [E7M3-09](E7M3-pre-production-backlog.md#e7m3-09--faction-telemetry-hook-sites-comment-only)) + +## Goal + +Anchor future E9.M1 catalog events **`reputation_delta`** and **`faction_gate_blocked`** on the server-authoritative faction path without ingest or behavior change. + +## Kickoff clarifications + +**No clarifications needed.** Linear AC, [E7M3-09 backlog scope](E7M3-pre-production-backlog.md#e7m3-09--faction-telemetry-hook-sites-comment-only), and NEO-121/130 comment-only telemetry precedents settle scope. Partial hook comments landed in NEO-136/137/138; this story **consolidates and documents** them. + +| Topic | Decision | Evidence | +|-------|----------|----------| +| **Runtime behavior** | Comments-only + `TODO(E9.M1)` — no ingest, no `ILogger` | NEO-121/130 precedent | +| **`reputation_delta` anchor** | **`ReputationOperations.TryApplyDelta`** only — after successful audit append | Backlog E7M3-09; NEO-121 engine-only precedent | +| **Router duplicate stub** | **Remove** `reputation_delta` comment from **`RewardRouterOperations`**; cross-link in README | NEO-130 reward table already notes “consolidated in NEO-141”; single authoritative ops layer | +| **`faction_gate_blocked` anchor** | **`FactionGateOperations.TryEvaluate`** deny paths only — not duplicate in **`QuestStateOperations.TryAccept`** or **`QuestAcceptApi`** | Backlog lists ops + TryAccept flow; NEO-137 placed hook at eval engine; HTTP maps all gate failures to `faction_gate_blocked` | +| **Standing read failure deny** | **Same event name** with ops **`reasonCode`** in planned payload (`unknown_faction` vs `gate_blocked`) — add comment block on read-failure branch | NEO-137 review: ops may carry `unknown_faction` for telemetry while quest HTTP surfaces `faction_gate_blocked` | +| **Emit timing for `reputation_delta`** | After **`auditStore.TryAppend`** returns **`true`** (standing + audit committed) — not on deny, audit rollback, or compensating revert paths | NEO-136 success path; mirror NEO-49 idempotent “commit site only” | +| **Manual QA doc** | **`docs/manual-qa/NEO-141.md`** — NEO-121/130 comment-only verification pattern | Infrastructure-only server story | + +## Scope and out-of-scope + +**In scope (from Linear + backlog):** + +- Complete comment-only **`reputation_delta`** hook in **`ReputationOperations`** (successful apply path — add `TODO(E9.M1)` + planned payload fields). +- Confirm / extend comment-only **`faction_gate_blocked`** in **`FactionGateOperations`** (threshold deny + standing read failure deny). +- Remove duplicate **`reputation_delta`** stub from **`RewardRouterOperations`**. +- **`server/README.md`** — dedicated **Faction telemetry hooks (NEO-141)** subsection; update **Reward telemetry hooks (NEO-130)** table to cross-link (remove stub row). +- **`E7_M3_FactionReputationLedger.md`**, alignment register, E7M3 backlog updates when implementation completes. +- **`docs/manual-qa/NEO-141.md`**. + +**Out of scope:** + +- E9.M1 ingest pipeline; runtime telemetry emit. +- `ILogger` / metrics / dev-only log lines. +- Duplicate hooks in **`QuestAcceptApi`**, **`QuestProgressApi`**, **`FactionStandingApi`**, or HTTP routes. +- Godot client (NEO-142/143). + +**Client counterpart:** none. + +## Acceptance criteria checklist + +- [ ] Hook sites documented in README; no behavior change beyond comments. + +## Technical approach + +### 1. Current state (precursor partial work) + +| Location | State today | NEO-141 action | +|----------|-------------|----------------| +| **`ReputationOperations.TryApplyDelta`** | One-line hook comment after audit append; **missing** `TODO(E9.M1)` + payload | **Complete** comment block | +| **`FactionGateOperations.TryEvaluate`** | Full hook on threshold deny (`standing < minStanding`); read-failure deny **without** hook | **Add** hook on read-failure deny with ops `reasonCode` in payload note | +| **`RewardRouterOperations`** | Duplicate `reputation_delta` stub after each rep row apply | **Remove** stub; router delegates to ops | +| **`server/README.md`** | `reputation_delta` only as NEO-130 stub row | **Add** NEO-141 subsection; **update** NEO-130 table | + +### 2. Hook site A — `reputation_delta` + +**Anchor:** **`ReputationOperations.TryApplyDelta`**, immediately after **`auditStore.TryAppend(row)`** returns **`true`**. + +- Comment block names future E9.M1 event **`reputation_delta`**. +- **`TODO(E9.M1): catalog emit`** — once per successful auditable apply (standing mutation + audit row committed). +- **Not** on: zero delta deny, standing store deny, audit append failure + compensating standing revert, or caller rollback paths. +- Planned payload fields from **`ReputationDeltaRow`**: `playerId`, `factionId`, `appliedDelta`, `newStanding`, `sourceKind`, `sourceId`, `deltaId`, `appliedAt`. +- Callers include **`RewardRouterOperations`** (quest completion), future admin tools — all funnel through this ops layer. + +### 3. Hook site B — `faction_gate_blocked` + +**Anchor:** **`FactionGateOperations.TryEvaluate`** deny returns only. + +**Branch B1 — threshold fail** (`read.Standing < rule.MinStanding`): hook comment **already present** — verify `TODO(E9.M1)` + payload fields match NEO-121 style. + +**Branch B2 — standing read fail** (`!read.Success`): **add** parallel hook comment before deny return. + +- Same event name **`faction_gate_blocked`**; planned payload includes ops **`reasonCode`** (`unknown_faction`, etc.), `playerId`, `factionId`, `minStanding`, optional `currentStanding` (null when unknown). +- **`QuestStateOperations.TryAccept`** maps all gate failures to HTTP **`faction_gate_blocked`** regardless of ops reason — E9.M1 can use ops payload for drill-down. +- Optional quest context (`questId`) deferred to E9.M1 wiring at **`TryAccept`** call site if needed — not duplicated at quest layer in prototype. + +### 4. README consolidation + +Add **`### Faction telemetry hooks (NEO-141)`** after [FactionGateOperations (NEO-137)](../../server/README.md#factiongateoperations-neo-137): + +| Event | Anchor | When | +|-------|--------|------| +| **`reputation_delta`** | **`ReputationOperations.TryApplyDelta`** | After successful audit append (auditable apply committed). | +| **`faction_gate_blocked`** | **`FactionGateOperations.TryEvaluate`** | On any rule deny (threshold or standing read failure). | + +Update [Reward telemetry hooks (NEO-130)](../../server/README.md#reward-telemetry-hooks-neo-130): remove **`reputation_delta`** stub row; add one-line pointer to NEO-141 (quest rep grants emit via ops, not router duplicate). + +### 5. Relationship to adjacent hooks + +| Event | Layer | Story | +|-------|-------|-------| +| **`quest_accept_denied`** | **`QuestStateOperations.TryAccept`** | NEO-121 (generic accept deny — includes gate denies at HTTP level) | +| **`faction_gate_blocked`** | **`FactionGateOperations.TryEvaluate`** | NEO-141 (faction-specific gate eval detail) | +| **`reward_delivery`** | **`RewardRouterOperations`** | NEO-130 | +| **`reputation_delta`** | **`ReputationOperations.TryApplyDelta`** | NEO-141 | + +Gate deny on accept may correlate **`quest_accept_denied`** (if added for gate path) vs **`faction_gate_blocked`** — E9.M1 can sample both or dedupe by correlation id. + +### 6. Flow (comment anchors only) + +```mermaid +sequenceDiagram + participant Accept as QuestStateOperations.TryAccept + participant Gate as FactionGateOperations + participant Router as RewardRouterOperations + participant Rep as ReputationOperations + + Accept->>Gate: TryEvaluate(rules) + alt gate deny + Gate-->>Accept: fail (hook: faction_gate_blocked) + else gate pass + Accept->>Accept: TryActivate (hook: quest_start NEO-121) + end + + Note over Router,Rep: quest completion path + Router->>Rep: TryApplyDelta per rep grant + Rep-->>Router: success (hook: reputation_delta) + Router->>Router: TryRecord delivery (hook: reward_delivery NEO-130) +``` + +## Files to add + +| Path | Rationale | +|------|-----------| +| `docs/manual-qa/NEO-141.md` | Manual QA: verify comment blocks + no behavior/API change (NEO-121/130 pattern) | + +## Files to modify + +| Path | Rationale | +|------|-----------| +| `server/NeonSprawl.Server/Game/Factions/ReputationOperations.cs` | Complete `reputation_delta` hook block (`TODO(E9.M1)` + planned payload fields) after audit append success | +| `server/NeonSprawl.Server/Game/Factions/FactionGateOperations.cs` | Add `faction_gate_blocked` hook on standing read-failure deny; verify threshold branch comment completeness | +| `server/NeonSprawl.Server/Game/Rewards/RewardRouterOperations.cs` | Remove duplicate `reputation_delta` stub comment (consolidate to ops layer) | +| `server/README.md` | Add **Faction telemetry hooks (NEO-141)** subsection; update NEO-130 reward table cross-link | +| `docs/decomposition/modules/E7_M3_FactionReputationLedger.md` | Status line: E7M3-09 NEO-141 telemetry hooks landed | +| `docs/decomposition/modules/documentation_and_implementation_alignment.md` | E7.M3 row: E7M3-09 hook sites | +| `docs/plans/E7M3-pre-production-backlog.md` | E7M3-09 checkboxes + landed note | + +## Tests + +| File | Coverage | +|------|----------| +| *(none added or changed)* | Comments-only change; no behavior or wire contract change. Regression: `dotnet test NeonSprawl.sln` (existing faction/quest/reward tests unchanged). Manual verification: **`docs/manual-qa/NEO-141.md`**. | + +## Open questions / risks + +| Question / risk | Agent recommendation | Status | +|-----------------|----------------------|--------| +| Duplicate `reputation_delta` if both router and ops had hooks | **Remove router stub** — single ops anchor | `adopted` | +| Gate deny emits both `quest_accept_denied` and `faction_gate_blocked` | **Expected** — different granularity; E9.M1 correlates by time/player/quest | `adopted` | +| `unknown_faction` on gate eval in prototype | Rare (catalog-validated rules); still document hook on read-failure deny for E9.M1 payload fidelity | `adopted` | +| E7.M3 module Ready vs In Progress | Leave **In Progress** until client capstone NEO-143; only note E7M3-09 complete in register | `adopted` | From 73403622f25d106f76d3be5e44d92fdd85f9c1ad Mon Sep 17 00:00:00 2001 From: VinPropane Date: Wed, 17 Jun 2026 19:25:25 -0400 Subject: [PATCH 3/6] NEO-141: consolidate faction telemetry hook comment sites Complete reputation_delta anchor in ReputationOperations, extend faction_gate_blocked on both gate deny paths, remove router duplicate stub, and add README Faction telemetry hooks subsection. --- .../Game/Factions/FactionGateOperations.cs | 5 ++++- .../Game/Factions/ReputationOperations.cs | 4 ++++ .../Game/Rewards/RewardRouterOperations.cs | 3 --- server/README.md | 14 +++++++++++++- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/server/NeonSprawl.Server/Game/Factions/FactionGateOperations.cs b/server/NeonSprawl.Server/Game/Factions/FactionGateOperations.cs index 5d28dfa..9752814 100644 --- a/server/NeonSprawl.Server/Game/Factions/FactionGateOperations.cs +++ b/server/NeonSprawl.Server/Game/Factions/FactionGateOperations.cs @@ -30,13 +30,16 @@ public static class FactionGateOperations var read = standingStore.TryGetStanding(normalizedPlayerId, factionId); if (!read.Success) { + // --- Telemetry hook site (NEO-141): future E9.M1 catalog event `faction_gate_blocked` --- + // TODO(E9.M1): catalog emit — ops reasonCode (e.g. unknown_faction), playerId, factionId, minStanding, currentStanding (null). + // No ingest or ILogger here (comments-only). return Deny(read.ReasonCode, factionId, rule.MinStanding, null); } if (read.Standing < rule.MinStanding) { // --- Telemetry hook site (NEO-141): future E9.M1 catalog event `faction_gate_blocked` --- - // TODO(E9.M1): catalog emit — playerId, factionId, minStanding, currentStanding, gate context. + // TODO(E9.M1): catalog emit — ops reasonCode (gate_blocked), playerId, factionId, minStanding, currentStanding. // No ingest or ILogger here (comments-only). return Deny( FactionGateEvaluateReasonCodes.GateBlocked, diff --git a/server/NeonSprawl.Server/Game/Factions/ReputationOperations.cs b/server/NeonSprawl.Server/Game/Factions/ReputationOperations.cs index 0343fa5..aaa42ba 100644 --- a/server/NeonSprawl.Server/Game/Factions/ReputationOperations.cs +++ b/server/NeonSprawl.Server/Game/Factions/ReputationOperations.cs @@ -67,6 +67,10 @@ public static class ReputationOperations if (auditStore.TryAppend(row)) { // --- Telemetry hook site (NEO-141): future E9.M1 catalog event `reputation_delta` --- + // TODO(E9.M1): catalog emit — once per successful auditable apply (standing + audit committed). + // Not on deny paths, audit append failure + compensating revert, or caller rollback paths. + // Planned payload fields: playerId, factionId, appliedDelta, newStanding, sourceKind, sourceId, deltaId, appliedAt. + // No ingest or ILogger here (comments-only). return new ReputationApplyOutcome( true, null, diff --git a/server/NeonSprawl.Server/Game/Rewards/RewardRouterOperations.cs b/server/NeonSprawl.Server/Game/Rewards/RewardRouterOperations.cs index e77a339..cb99ad3 100644 --- a/server/NeonSprawl.Server/Game/Rewards/RewardRouterOperations.cs +++ b/server/NeonSprawl.Server/Game/Rewards/RewardRouterOperations.cs @@ -220,9 +220,6 @@ public static class RewardRouterOperations var appliedAmount = repOutcome.NewStanding - repOutcome.PreviousStanding; appliedReputation.Add(new RewardReputationGrantApplied(grant.FactionId, appliedAmount)); - - // --- Telemetry hook site (NEO-141): future E9.M1 catalog event `reputation_delta` --- - // TODO(E9.M1): catalog emit — once per applied rep row on TryRecord success only (not rollback paths). } var deliveredAt = timeProvider.GetUtcNow(); diff --git a/server/README.md b/server/README.md index 3d64a37..efd1920 100644 --- a/server/README.md +++ b/server/README.md @@ -134,6 +134,17 @@ Quest accept evaluates **`FactionGateRule`** rows through **`FactionGateOperatio Plan: [NEO-137 implementation plan](../../docs/plans/NEO-137-implementation-plan.md). +### Faction telemetry hooks (NEO-141) + +Comment-only hook sites for future E9.M1 catalog events ([Epic 7 Slice 3](../../docs/decomposition/modules/E7_M3_FactionReputationLedger.md#related-implementation-slices)). **`TODO(E9.M1)`** — no production ingest. Quest accept and reward router delegate to these ops layers only (no duplicate API-layer or router-layer hooks for these names). Quest rep grants emit **`reputation_delta`** via **`ReputationOperations`** — see [Reward telemetry hooks (NEO-130)](#reward-telemetry-hooks-neo-130) cross-link. + +| Event | Anchor | When | +|-------|--------|------| +| **`reputation_delta`** | **`ReputationOperations.TryApplyDelta`** | After successful audit append (auditable apply committed). | +| **`faction_gate_blocked`** | **`FactionGateOperations.TryEvaluate`** | On any rule deny (threshold or standing read failure). | + +Manual QA: [`docs/manual-qa/NEO-141.md`](../../docs/manual-qa/NEO-141.md); plan: [NEO-141 implementation plan](../../docs/plans/NEO-141-implementation-plan.md). + **Dev quest fixture (NEO-137, Bruno/manual QA):** When `Game:EnableQuestFixtureApi` is **true** (default in **Development** via `appsettings.Development.json`, **Testing**, or CI Bruno step) or the host environment is **Development** / **Testing**, **`POST /game/players/{id}/__dev/quest-fixture`** accepts `schemaVersion` **1** with optional (resets run before **`completedQuestIds`** when combined): - **`resetQuestIds`** — deletes each quest's progress row for the player and clears matching **`IRewardDeliveryStore`** + quest-completion **`IReputationDeltaStore`** audit rows (idempotent when absent). - **`resetFactionIds`** — clears each faction's standing row for the player (missing row reads as **0**; idempotent when absent). @@ -408,9 +419,10 @@ Comment-only hook sites in **`RewardRouterOperations.TryDeliverQuestCompletion`* | Event | Anchor | When | |-------|--------|------| | **`reward_delivery`** | **`TryDeliverQuestCompletion`** | After **`TryRecord`** returns **`true`** on first-time delivery (not idempotent **`TryGet`** replay or race-loser paths). | -| **`reputation_delta`** | **`TryDeliverQuestCompletion`** (stub) | After each successful reputation row apply (NEO-138); consolidated in NEO-141. | | **`unlock_granted`** | **`TryDeliverQuestCompletion`** (stub) | Future **`UnlockGrant`** apply from bundle rows — prototype bundles have item + skill XP only; no runtime unlock apply in Slice 2. | +**`reputation_delta`** from quest bundle rep rows emits via **`ReputationOperations.TryApplyDelta`** ([NEO-141](#faction-telemetry-hooks-neo-141)) — not duplicated in the router. + **`perk_unlock`** side effects from skill XP grants use **`PerkUnlockEngine`** ([NEO-49](#perk-unlock-engine-and-telemetry-hooks-neo-47-neo-49)) — distinct from content **`UnlockGrant`** rows. Manual QA: [`docs/manual-qa/NEO-130.md`](../../docs/manual-qa/NEO-130.md); plan: [NEO-130 implementation plan](../../docs/plans/NEO-130-implementation-plan.md). ## Encounter definitions (NEO-103) From 3c1ade4185a90ee84d279730ca9f8a6571a9ac97 Mon Sep 17 00:00:00 2001 From: VinPropane Date: Wed, 17 Jun 2026 19:25:27 -0400 Subject: [PATCH 4/6] NEO-141: add manual QA and reconcile module docs Update E7M3 backlog, alignment register, and implementation plan for landed faction telemetry hook sites. --- .../modules/E7_M3_FactionReputationLedger.md | 2 +- ...umentation_and_implementation_alignment.md | 2 +- docs/manual-qa/NEO-141.md | 34 +++++++++++++++++++ docs/plans/E7M3-pre-production-backlog.md | 4 ++- docs/plans/NEO-141-implementation-plan.md | 10 +++++- 5 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 docs/manual-qa/NEO-141.md diff --git a/docs/decomposition/modules/E7_M3_FactionReputationLedger.md b/docs/decomposition/modules/E7_M3_FactionReputationLedger.md index 113c27d..81e7710 100644 --- a/docs/decomposition/modules/E7_M3_FactionReputationLedger.md +++ b/docs/decomposition/modules/E7_M3_FactionReputationLedger.md @@ -7,7 +7,7 @@ | **Module ID** | E7.M3 | | **Epic** | [Epic 7 — Quest / Faction](../epics/epic_07_quest_faction.md) | | **Stage target** | Pre-production | -| **Status** | In Progress — **E7M3-01 catalog landed** ([NEO-133](https://linear.app/neon-sprawl/issue/NEO-133)); **E7M3-02 server faction catalog load landed** ([NEO-134](https://linear.app/neon-sprawl/issue/NEO-134)): fail-fast `content/factions/*_factions.json`, `IFactionDefinitionRegistry`, quest faction cross-ref + E7M3 bundle/grid gates; **E7M3-03 faction standing + reputation delta stores landed** ([NEO-135](https://linear.app/neon-sprawl/issue/NEO-135)): `IFactionStandingStore`, `IReputationDeltaStore`, in-memory + Postgres `V009`/`V010`; **E7M3-04 `ReputationOperations` landed** ([NEO-136](https://linear.app/neon-sprawl/issue/NEO-136)): auditable `TryApplyDelta` orchestration ([NEO-136 plan](../../plans/NEO-136-implementation-plan.md)); **E7M3-05 `FactionGateOperations` landed** ([NEO-137](https://linear.app/neon-sprawl/issue/NEO-137)): quest accept gate eval + `faction_gate_blocked` ([NEO-137 plan](../../plans/NEO-137-implementation-plan.md)); **E7M3-06 reward router rep grants landed** ([NEO-138](https://linear.app/neon-sprawl/issue/NEO-138)): `RewardRouterOperations` applies `reputationGrants` idempotently via `ReputationOperations` ([NEO-138 plan](../../plans/NEO-138-implementation-plan.md)); **E7M3-07 faction standing HTTP read landed** ([NEO-139](https://linear.app/neon-sprawl/issue/NEO-139)): `GET …/faction-standing` snapshot API + Bruno ([NEO-139 plan](../../plans/NEO-139-implementation-plan.md)); **E7M3-08 quest HTTP projections landed** ([NEO-140](https://linear.app/neon-sprawl/issue/NEO-140)): world GET `factionGateRules` + quest-progress `completionRewardSummary.reputationGrants` ([NEO-140 plan](../../plans/NEO-140-implementation-plan.md)). Slice 3 backlog **E7M3-09** [NEO-141](https://linear.app/neon-sprawl/issue/NEO-141) → **E7M3-11** [NEO-143](https://linear.app/neon-sprawl/issue/NEO-143). Upstream: E7.M1 **Ready**, E7.M2 **Ready**. | +| **Status** | In Progress — **E7M3-01 catalog landed** ([NEO-133](https://linear.app/neon-sprawl/issue/NEO-133)); **E7M3-02 server faction catalog load landed** ([NEO-134](https://linear.app/neon-sprawl/issue/NEO-134)): fail-fast `content/factions/*_factions.json`, `IFactionDefinitionRegistry`, quest faction cross-ref + E7M3 bundle/grid gates; **E7M3-03 faction standing + reputation delta stores landed** ([NEO-135](https://linear.app/neon-sprawl/issue/NEO-135)): `IFactionStandingStore`, `IReputationDeltaStore`, in-memory + Postgres `V009`/`V010`; **E7M3-04 `ReputationOperations` landed** ([NEO-136](https://linear.app/neon-sprawl/issue/NEO-136)): auditable `TryApplyDelta` orchestration ([NEO-136 plan](../../plans/NEO-136-implementation-plan.md)); **E7M3-05 `FactionGateOperations` landed** ([NEO-137](https://linear.app/neon-sprawl/issue/NEO-137)): quest accept gate eval + `faction_gate_blocked` ([NEO-137 plan](../../plans/NEO-137-implementation-plan.md)); **E7M3-06 reward router rep grants landed** ([NEO-138](https://linear.app/neon-sprawl/issue/NEO-138)): `RewardRouterOperations` applies `reputationGrants` idempotently via `ReputationOperations` ([NEO-138 plan](../../plans/NEO-138-implementation-plan.md)); **E7M3-07 faction standing HTTP read landed** ([NEO-139](https://linear.app/neon-sprawl/issue/NEO-139)): `GET …/faction-standing` snapshot API + Bruno ([NEO-139 plan](../../plans/NEO-139-implementation-plan.md)); **E7M3-08 quest HTTP projections landed** ([NEO-140](https://linear.app/neon-sprawl/issue/NEO-140)): world GET `factionGateRules` + quest-progress `completionRewardSummary.reputationGrants` ([NEO-140 plan](../../plans/NEO-140-implementation-plan.md)); **E7M3-09 faction telemetry hooks landed** ([NEO-141](https://linear.app/neon-sprawl/issue/NEO-141)): comment-only **`reputation_delta`** + **`faction_gate_blocked`** in ops layers + README ([NEO-141 plan](../../plans/NEO-141-implementation-plan.md)). Slice 3 backlog **E7M3-10** [NEO-142](https://linear.app/neon-sprawl/issue/NEO-142) → **E7M3-11** [NEO-143](https://linear.app/neon-sprawl/issue/NEO-143). Upstream: E7.M1 **Ready**, E7.M2 **Ready**. | | **Linear** | Label **`E7.M3`** · [E7M3-pre-production-backlog.md](../../plans/E7M3-pre-production-backlog.md) | ## Purpose diff --git a/docs/decomposition/modules/documentation_and_implementation_alignment.md b/docs/decomposition/modules/documentation_and_implementation_alignment.md index a5767b9..c6516ab 100644 --- a/docs/decomposition/modules/documentation_and_implementation_alignment.md +++ b/docs/decomposition/modules/documentation_and_implementation_alignment.md @@ -61,7 +61,7 @@ Rows appear when work starts; default for unlisted modules is **Planned** / not | E5.M3 | Ready | **E5M3-01 catalog landed ([NEO-100](https://linear.app/neon-sprawl/issue/NEO-100)):** encounter + reward-table schemas, `prototype_encounters.json`, `prototype_reward_tables.json`, CI gates. **E5M3-02 server load ([NEO-101](https://linear.app/neon-sprawl/issue/NEO-101)):** fail-fast startup loaders for encounter + reward-table catalogs (CI parity). **E5M3-03 registries ([NEO-102](https://linear.app/neon-sprawl/issue/NEO-102)):** `IEncounterDefinitionRegistry` + `IRewardTableDefinitionRegistry` + DI. **E5M3-04 HTTP read ([NEO-103](https://linear.app/neon-sprawl/issue/NEO-103)):** **`GET /game/world/encounter-definitions`** — `EncounterDefinitionsWorldApi` + nested **`rewardTable`** summary ([NEO-103](../../plans/NEO-103-implementation-plan.md)); [server README — Encounter definitions (NEO-103)](../../../server/README.md#encounter-definitions-neo-103); Bruno `bruno/neon-sprawl-server/encounter-definitions/`. **E5M3-05 stores ([NEO-104](https://linear.app/neon-sprawl/issue/NEO-104)):** **`IEncounterProgressStore`** + **`IEncounterCompletionStore`** + **`EncounterProgressOperations`** ([NEO-104](../../plans/NEO-104-implementation-plan.md)); [server README — Encounter progress (NEO-104)](../../../server/README.md#encounter-progress--completion-stores-neo-104). **E5M3-06 completion grants ([NEO-105](https://linear.app/neon-sprawl/issue/NEO-105)):** **`EncounterCompletionOperations`** + **`EncounterCompleteEvent`** result payload ([NEO-105](../../plans/NEO-105-implementation-plan.md)); [server README — Encounter completion (NEO-105)](../../../server/README.md#encounter-completion--inventory-grants-neo-105). **E5M3-07 combat wiring ([NEO-106](https://linear.app/neon-sprawl/issue/NEO-106)):** **`EncounterCombatWiring`** on **`AbilityCastApi`** ([NEO-106](../../plans/NEO-106-implementation-plan.md)); [server README — Encounter combat wiring (NEO-106)](../../../server/README.md#encounter-combat-wiring-neo-106). **E5M3-09 event record ([NEO-107](https://linear.app/neon-sprawl/issue/NEO-107)):** **`IEncounterCompleteEventStore`** + E7.M2 hook stub ([NEO-107](../../plans/NEO-107-implementation-plan.md)); [server README — Encounter complete event (NEO-107)](../../../server/README.md#encounter-complete-event-record-neo-107). **E5M3-08 per-player GET ([NEO-108](https://linear.app/neon-sprawl/issue/NEO-108)):** **`GET /game/players/{id}/encounter-progress`** — `EncounterProgressApi` + DTOs ([NEO-108](../../plans/NEO-108-implementation-plan.md)); [server README — Per-player encounter progress (NEO-108)](../../../server/README.md#per-player-encounter-progress-neo-108); Bruno `bruno/neon-sprawl-server/encounter-progress/`. **E5M3-10 telemetry ([NEO-109](https://linear.app/neon-sprawl/issue/NEO-109)):** comment-only **`encounter_start`**, **`encounter_complete`**, **`reward_attribution`**, **`encounter_complete_denied`** in **`EncounterProgressOperations`** / **`EncounterCompletionOperations`** ([NEO-109](../../plans/NEO-109-implementation-plan.md)); [server README — Encounter telemetry hooks (NEO-109)](../../../server/README.md#encounter-telemetry-hooks-neo-109). **E5M3-11 client HUD ([NEO-110](https://linear.app/neon-sprawl/issue/NEO-110)):** **`encounter_progress_client.gd`**, **`EncounterProgressLabel`** / **`EncounterCompleteLabel`**; boot + defeat-triggered GET; inventory refresh on **`completed`** ([NEO-110](../../plans/NEO-110-implementation-plan.md), [`NEO-110` manual QA](../../manual-qa/NEO-110.md)); [client README — Encounter progress + loot HUD (NEO-110)](../../../client/README.md#encounter-progress--loot-hud-neo-110). **E5M3-12 client capstone ([NEO-111](https://linear.app/neon-sprawl/issue/NEO-111)):** playable encounter clear loop — [`NEO-111` manual QA](../../manual-qa/NEO-111.md); [client README — End-to-end encounter clear loop (NEO-111)](../../../client/README.md#end-to-end-encounter-clear-loop-neo-111); plan [NEO-111](../../plans/NEO-111-implementation-plan.md). **Epic 5 Slice 3 client capstone complete.** Epic 5 Slice 3 backlog [E5M3-prototype-backlog.md](../../plans/E5M3-prototype-backlog.md) **E5M3-01** [NEO-100](https://linear.app/neon-sprawl/issue/NEO-100) → **E5M3-12** [NEO-111](https://linear.app/neon-sprawl/issue/NEO-111) **landed**. Prototype spine: **`prototype_combat_pocket`** → **`prototype_combat_pocket_clear`**; idempotent completion; per-defeat gig XP unchanged ([NEO-44](../../plans/NEO-44-implementation-plan.md)). Upstream **E5.M2 Ready**, **E3.M3** inventory landed. | [NEO-111 plan](../../plans/NEO-111-implementation-plan.md), [NEO-110 plan](../../plans/NEO-110-implementation-plan.md), [NEO-109 plan](../../plans/NEO-109-implementation-plan.md), [NEO-108 plan](../../plans/NEO-108-implementation-plan.md), [NEO-107 plan](../../plans/NEO-107-implementation-plan.md), [NEO-106 plan](../../plans/NEO-106-implementation-plan.md), [NEO-105 plan](../../plans/NEO-105-implementation-plan.md), [NEO-104 plan](../../plans/NEO-104-implementation-plan.md), [NEO-103 plan](../../plans/NEO-103-implementation-plan.md), [NEO-102 plan](../../plans/NEO-102-implementation-plan.md), [E5_M3](E5_M3_EncounterAndRewardTables.md), label **`E5.M3`** on NEO-100–NEO-111 | | E7.M2 | Ready | **E7M2-01 catalog landed ([NEO-124](https://linear.app/neon-sprawl/issue/NEO-124)):** `quest-reward-bundle` + `quest-skill-xp-grant` schemas; **`completionRewardBundle`** on four frozen E7.M1 quests; CI bundle freeze + cross-ref gates. **E7M2-02 server load ([NEO-125](https://linear.app/neon-sprawl/issue/NEO-125)):** fail-fast quest catalog **`completionRewardBundle`** validation at startup — `PrototypeE7M2QuestCatalogRules` + bundle schema `$ref` registration ([NEO-125](../../plans/NEO-125-implementation-plan.md)); [server README — Quest catalog (NEO-125)](../../../server/README.md#quest-catalog-contentquests-neo-113). **E7M2-03 delivery store ([NEO-126](https://linear.app/neon-sprawl/issue/NEO-126)):** idempotent **`IRewardDeliveryStore`** + **`RewardDeliveryEvent`** in `Game/Rewards/` — in-memory prototype ([NEO-126](../../plans/NEO-126-implementation-plan.md)); [server README — Reward delivery store (NEO-126)](../../../server/README.md#reward-delivery-store-neo-126). **E7M2-04 router apply ([NEO-127](https://linear.app/neon-sprawl/issue/NEO-127)):** **`RewardRouterOperations.TryDeliverQuestCompletion`** — item + skill XP bundle apply with compensating rollback + store record ([NEO-127](../../plans/NEO-127-implementation-plan.md)); [server README — Reward router (NEO-127)](../../../server/README.md#reward-router-neo-127). **E7M2-05 quest-state wiring ([NEO-128](https://linear.app/neon-sprawl/issue/NEO-128)):** **`QuestStateOperations.TryMarkComplete`** + **`QuestObjectiveWiring`** deliver-then-mark via router ([NEO-128](../../plans/NEO-128-implementation-plan.md)); [server README — Quest state operations (NEO-117)](../../../server/README.md#quest-state-operations-neo-117). **E7M2-06 HTTP read ([NEO-129](https://linear.app/neon-sprawl/issue/NEO-129)):** **`GET …/quest-progress`** **`completionRewardSummary`** from **`IRewardDeliveryStore`** ([NEO-129](../../plans/NEO-129-implementation-plan.md)); Bruno `bruno/neon-sprawl-server/quest-progress/Get quest progress after gather intro complete.bru`. **E7M2-07 telemetry ([NEO-130](https://linear.app/neon-sprawl/issue/NEO-130)):** comment-only **`reward_delivery`** + **`unlock_granted`** stub hook sites in **`RewardRouterOperations.TryDeliverQuestCompletion`** ([NEO-130](../../plans/NEO-130-implementation-plan.md)); [server README — Reward telemetry hooks (NEO-130)](../../../server/README.md#reward-telemetry-hooks-neo-130). **E7M2-08 client HUD ([NEO-131](https://linear.app/neon-sprawl/issue/NEO-131)):** Godot **`QuestRewardDeliveryLabel`** paints **`completionRewardSummary`** on in-session completion transition ([NEO-131](../../plans/NEO-131-implementation-plan.md)); [client README — Quest completion reward HUD (NEO-131)](../../../client/README.md#quest-completion-reward-hud-neo-131). **E7M2-09 client capstone ([NEO-132](https://linear.app/neon-sprawl/issue/NEO-132)):** playable quest reward delivery loop — [`NEO-132` manual QA](../../manual-qa/NEO-132.md); [client README — End-to-end quest reward loop (NEO-132)](../../../client/README.md#end-to-end-quest-reward-loop-neo-132); plan [NEO-132](../../plans/NEO-132-implementation-plan.md). **Epic 7 Slice 2 client capstone complete.** Backlog **E7M2-01** [NEO-124](https://linear.app/neon-sprawl/issue/NEO-124) → **E7M2-09** [NEO-132](https://linear.app/neon-sprawl/issue/NEO-132) **landed**. **NEO-43 prep landed:** **`MissionRewardSkillXpGrant`**. **Encounter loot unchanged** (E5.M3 direct grants). Upstream: E7.M1 **Ready**, E2.M2 grant stack, E3.M3 inventory **Ready**. | [E7M2-prototype-backlog.md](../../plans/E7M2-prototype-backlog.md), [E7_M2](E7_M2_RewardAndUnlockRouter.md), [NEO-124](../../plans/NEO-124-implementation-plan.md), [NEO-125](../../plans/NEO-125-implementation-plan.md), [NEO-126](../../plans/NEO-126-implementation-plan.md), [NEO-127](../../plans/NEO-127-implementation-plan.md), [NEO-128](../../plans/NEO-128-implementation-plan.md), [NEO-129](../../plans/NEO-129-implementation-plan.md), [NEO-130](../../plans/NEO-130-implementation-plan.md), [NEO-131](../../plans/NEO-131-implementation-plan.md), [NEO-132](../../plans/NEO-132-implementation-plan.md), [NEO-43](../../plans/NEO-43-implementation-plan.md), label **`E7.M2`** on NEO-124–NEO-132 | | E7.M1 | Ready | **E7M1-01 catalog landed ([NEO-112](https://linear.app/neon-sprawl/issue/NEO-112)):** quest schemas, `prototype_quests.json`, CI gates (four frozen quest ids, objective cross-refs, acyclic prerequisites, chain terminal token). **E7M1-02 server load ([NEO-113](https://linear.app/neon-sprawl/issue/NEO-113)):** fail-fast startup load of `content/quests/*_quests.json` — `server/NeonSprawl.Server/Game/Quests/` ([NEO-113](../../plans/NEO-113-implementation-plan.md)); [server README — Quest catalog](../../../server/README.md#quest-catalog-contentquests-neo-113). **E7M1-03 registry ([NEO-114](https://linear.app/neon-sprawl/issue/NEO-114)):** **`IQuestDefinitionRegistry`** + DI ([NEO-114](../../plans/NEO-114-implementation-plan.md)). **E7M1-04 HTTP read ([NEO-115](https://linear.app/neon-sprawl/issue/NEO-115)):** **`GET /game/world/quest-definitions`** — `QuestDefinitionsWorldApi` + DTOs ([NEO-115](../../plans/NEO-115-implementation-plan.md)); [server README — Quest definitions (NEO-115)](../../../server/README.md#quest-definitions-neo-115); Bruno `bruno/neon-sprawl-server/quest-definitions/`. **E7M1-05 store ([NEO-116](https://linear.app/neon-sprawl/issue/NEO-116)):** **`IPlayerQuestStateStore`** + in-memory/Postgres persistence ([NEO-116](../../plans/NEO-116-implementation-plan.md)); [server README — Quest progress store (NEO-116)](../../../server/README.md#quest-progress-store-neo-116). **E7M1-06 operations ([NEO-117](https://linear.app/neon-sprawl/issue/NEO-117)):** **`QuestStateOperations`** + reason codes ([NEO-117](../../plans/NEO-117-implementation-plan.md)); [server README — Quest state operations (NEO-117)](../../../server/README.md#quest-state-operations-neo-117). **E7M1-07 objective wiring ([NEO-118](https://linear.app/neon-sprawl/issue/NEO-118)):** **`QuestObjectiveWiring`** on gather/craft/encounter + **`inventory_has_item`** snapshot passes ([NEO-118](../../plans/NEO-118-implementation-plan.md)); [server README — Quest objective wiring (NEO-118)](../../../server/README.md#quest-objective-wiring-neo-118). **E7M1-08 per-player GET ([NEO-119](https://linear.app/neon-sprawl/issue/NEO-119)):** **`GET /game/players/{id}/quest-progress`** — `QuestProgressApi` + DTOs; GET-side inventory refresh ([NEO-119](../../plans/NEO-119-implementation-plan.md)); [server README — Per-player quest progress (NEO-119)](../../../server/README.md#per-player-quest-progress-neo-119); Bruno `bruno/neon-sprawl-server/quest-progress/`. **E7M1-09 accept POST ([NEO-120](https://linear.app/neon-sprawl/issue/NEO-120)):** **`POST /game/players/{id}/quests/{questId}/accept`** — `QuestAcceptApi` + DTOs; **`QuestStateOperations.TryAccept`** ([NEO-120](../../plans/NEO-120-implementation-plan.md)); [server README — Quest accept POST (NEO-120)](../../../server/README.md#quest-accept-post-neo-120); Bruno accept spine in `bruno/neon-sprawl-server/quest-progress/`. **E7M1-10 telemetry ([NEO-121](https://linear.app/neon-sprawl/issue/NEO-121)):** comment-only **`quest_start`**, **`quest_step_complete`**, **`quest_complete`**, **`quest_accept_denied`** hook sites in **`QuestStateOperations`** ([NEO-121](../../plans/NEO-121-implementation-plan.md)); [server README — Quest telemetry hooks (NEO-121)](../../../server/README.md#quest-telemetry-hooks-neo-121). **E7M1-11 client HUD ([NEO-122](https://linear.app/neon-sprawl/issue/NEO-122)):** **`quest_progress_client.gd`**, **`quest_definitions_client.gd`**, **`QuestProgressLabel`** / **`QuestAcceptFeedbackLabel`**; boot + event-driven GET refresh; **Q** / **Shift+Q** accept ([NEO-122](../../plans/NEO-122-implementation-plan.md), [`NEO-122` manual QA](../../manual-qa/NEO-122.md)); [client README — Quest progress + accept HUD (NEO-122)](../../../client/README.md#quest-progress--accept-hud-neo-122). **E7M1-12 client capstone ([NEO-123](https://linear.app/neon-sprawl/issue/NEO-123)):** playable four-quest onboarding chain — [`NEO-123` manual QA](../../manual-qa/NEO-123.md); [client README — End-to-end onboarding quest loop (NEO-123)](../../../client/README.md#end-to-end-onboarding-quest-loop-neo-123); plan [NEO-123](../../plans/NEO-123-implementation-plan.md). **Epic 7 Slice 1 client capstone complete.** Backlog **E7M1-01** [NEO-112](https://linear.app/neon-sprawl/issue/NEO-112) → **E7M1-12** [NEO-123](https://linear.app/neon-sprawl/issue/NEO-123) **landed**. Upstream: E3.M1 gather **Ready**, E3.M2 craft **Ready**, E5.M3 encounter **`contract_handoff_token`** + **`EncounterCompleteEvent`** **Ready**. | [NEO-123 plan](../../plans/NEO-123-implementation-plan.md), [NEO-122 plan](../../plans/NEO-122-implementation-plan.md), [NEO-121 plan](../../plans/NEO-121-implementation-plan.md), [NEO-120 plan](../../plans/NEO-120-implementation-plan.md), [NEO-119 plan](../../plans/NEO-119-implementation-plan.md), [NEO-118 plan](../../plans/NEO-118-implementation-plan.md), [NEO-117 plan](../../plans/NEO-117-implementation-plan.md), [NEO-116 plan](../../plans/NEO-116-implementation-plan.md), [NEO-115 plan](../../plans/NEO-115-implementation-plan.md), [NEO-114 plan](../../plans/NEO-114-implementation-plan.md), [NEO-113 plan](../../plans/NEO-113-implementation-plan.md), [NEO-112 plan](../../plans/NEO-112-implementation-plan.md), [E7M1-prototype-backlog.md](../../plans/E7M1-prototype-backlog.md), [E7_M1](E7_M1_QuestStateMachine.md), label **`E7.M1`** on NEO-112–NEO-123 | -| E7.M3 | In Progress | **E7M3-01 catalog landed ([NEO-133](https://linear.app/neon-sprawl/issue/NEO-133)):** faction + gate/rep schemas, `prototype_factions.json`, quest extensions, five-quest CI gates, minimal server roster parity. **E7M3-02 server load landed ([NEO-134](https://linear.app/neon-sprawl/issue/NEO-134)):** fail-fast faction catalog, `IFactionDefinitionRegistry`, quest `factionGateRules` / `reputationGrants` parse + E7M3 cross-ref/bundle/grid gates ([NEO-134 plan](../../plans/NEO-134-implementation-plan.md)). **E7M3-03 stores landed ([NEO-135](https://linear.app/neon-sprawl/issue/NEO-135)):** `IFactionStandingStore`, `IReputationDeltaStore`, in-memory + Postgres `V009`/`V010` ([NEO-135 plan](../../plans/NEO-135-implementation-plan.md)); [server README — Faction standing store (NEO-135)](../../../server/README.md#faction-standing-store-neo-135). **E7M3-04 ops landed ([NEO-136](https://linear.app/neon-sprawl/issue/NEO-136)):** `ReputationOperations.TryApplyDelta` — standing + audit orchestration with compensating rollback ([NEO-136 plan](../../plans/NEO-136-implementation-plan.md)); [server README — ReputationOperations (NEO-136)](../../../server/README.md#reputationoperations-neo-136). **E7M3-05 gate eval landed ([NEO-137](https://linear.app/neon-sprawl/issue/NEO-137)):** `FactionGateOperations.TryEvaluate` wired into `QuestStateOperations.TryAccept`; `faction_gate_blocked` deny ([NEO-137 plan](../../plans/NEO-137-implementation-plan.md)); [server README — FactionGateOperations (NEO-137)](../../../server/README.md#factiongateoperations-neo-137). **E7M3-06 reward router landed ([NEO-138](https://linear.app/neon-sprawl/issue/NEO-138)):** `RewardRouterOperations` applies `reputationGrants` via `ReputationOperations`; Bruno grid-contract accept success after organic operator chain ([NEO-138 plan](../../plans/NEO-138-implementation-plan.md)); [server README — Reward router (NEO-127)](../../../server/README.md#reward-router-neo-127). **E7M3-07 HTTP read landed ([NEO-139](https://linear.app/neon-sprawl/issue/NEO-139)):** **`GET …/faction-standing`** — `FactionStandingApi` + DTOs; Bruno `bruno/neon-sprawl-server/faction-standing/` ([NEO-139 plan](../../plans/NEO-139-implementation-plan.md)); [server README — Faction standing read (NEO-139)](../../../server/README.md#faction-standing-read-neo-139). **E7M3-08 quest HTTP projections landed ([NEO-140](https://linear.app/neon-sprawl/issue/NEO-140)):** world GET **`factionGateRules`** + quest-progress **`completionRewardSummary.reputationGrants`**; Bruno quest-definitions + operator-chain progress bru ([NEO-140 plan](../../plans/NEO-140-implementation-plan.md)); [server README — Quest definitions (NEO-115)](../../../server/README.md#quest-definitions-neo-115), [Per-player quest progress (NEO-119)](../../../server/README.md#per-player-quest-progress-neo-119). **E7M3-09+** [NEO-141](https://linear.app/neon-sprawl/issue/NEO-141) → **E7M3-11** [NEO-143](https://linear.app/neon-sprawl/issue/NEO-143). Two frozen factions; operator-chain **`reputationGrants`** (+15 Grid Operators); gated **`prototype_quest_grid_contract`**; client capstone [NEO-143](https://linear.app/neon-sprawl/issue/NEO-143). Upstream: E7.M1 **Ready**, E7.M2 **Ready**. | [NEO-133 plan](../../plans/NEO-133-implementation-plan.md), [NEO-134 plan](../../plans/NEO-134-implementation-plan.md), [NEO-135 plan](../../plans/NEO-135-implementation-plan.md), [NEO-136 plan](../../plans/NEO-136-implementation-plan.md), [NEO-137 plan](../../plans/NEO-137-implementation-plan.md), [NEO-138 plan](../../plans/NEO-138-implementation-plan.md), [NEO-139 plan](../../plans/NEO-139-implementation-plan.md), [NEO-140 plan](../../plans/NEO-140-implementation-plan.md), [E7M3-pre-production-backlog.md](../../plans/E7M3-pre-production-backlog.md), [E7_M3](E7_M3_FactionReputationLedger.md), label **`E7.M3`** on NEO-133–NEO-143 | +| E7.M3 | In Progress | **E7M3-01 catalog landed ([NEO-133](https://linear.app/neon-sprawl/issue/NEO-133)):** faction + gate/rep schemas, `prototype_factions.json`, quest extensions, five-quest CI gates, minimal server roster parity. **E7M3-02 server load landed ([NEO-134](https://linear.app/neon-sprawl/issue/NEO-134)):** fail-fast faction catalog, `IFactionDefinitionRegistry`, quest `factionGateRules` / `reputationGrants` parse + E7M3 cross-ref/bundle/grid gates ([NEO-134 plan](../../plans/NEO-134-implementation-plan.md)). **E7M3-03 stores landed ([NEO-135](https://linear.app/neon-sprawl/issue/NEO-135)):** `IFactionStandingStore`, `IReputationDeltaStore`, in-memory + Postgres `V009`/`V010` ([NEO-135 plan](../../plans/NEO-135-implementation-plan.md)); [server README — Faction standing store (NEO-135)](../../../server/README.md#faction-standing-store-neo-135). **E7M3-04 ops landed ([NEO-136](https://linear.app/neon-sprawl/issue/NEO-136)):** `ReputationOperations.TryApplyDelta` — standing + audit orchestration with compensating rollback ([NEO-136 plan](../../plans/NEO-136-implementation-plan.md)); [server README — ReputationOperations (NEO-136)](../../../server/README.md#reputationoperations-neo-136). **E7M3-05 gate eval landed ([NEO-137](https://linear.app/neon-sprawl/issue/NEO-137)):** `FactionGateOperations.TryEvaluate` wired into `QuestStateOperations.TryAccept`; `faction_gate_blocked` deny ([NEO-137 plan](../../plans/NEO-137-implementation-plan.md)); [server README — FactionGateOperations (NEO-137)](../../../server/README.md#factiongateoperations-neo-137). **E7M3-06 reward router landed ([NEO-138](https://linear.app/neon-sprawl/issue/NEO-138)):** `RewardRouterOperations` applies `reputationGrants` via `ReputationOperations`; Bruno grid-contract accept success after organic operator chain ([NEO-138 plan](../../plans/NEO-138-implementation-plan.md)); [server README — Reward router (NEO-127)](../../../server/README.md#reward-router-neo-127). **E7M3-07 HTTP read landed ([NEO-139](https://linear.app/neon-sprawl/issue/NEO-139)):** **`GET …/faction-standing`** — `FactionStandingApi` + DTOs; Bruno `bruno/neon-sprawl-server/faction-standing/` ([NEO-139 plan](../../plans/NEO-139-implementation-plan.md)); [server README — Faction standing read (NEO-139)](../../../server/README.md#faction-standing-read-neo-139). **E7M3-08 quest HTTP projections landed ([NEO-140](https://linear.app/neon-sprawl/issue/NEO-140)):** world GET **`factionGateRules`** + quest-progress **`completionRewardSummary.reputationGrants`**; Bruno quest-definitions + operator-chain progress bru ([NEO-140 plan](../../plans/NEO-140-implementation-plan.md)); [server README — Quest definitions (NEO-115)](../../../server/README.md#quest-definitions-neo-115), [Per-player quest progress (NEO-119)](../../../server/README.md#per-player-quest-progress-neo-119). **E7M3-09 telemetry landed ([NEO-141](https://linear.app/neon-sprawl/issue/NEO-141)):** comment-only **`reputation_delta`** + **`faction_gate_blocked`** hook sites in ops layers ([NEO-141 plan](../../plans/NEO-141-implementation-plan.md)); [server README — Faction telemetry hooks (NEO-141)](../../../server/README.md#faction-telemetry-hooks-neo-141). **E7M3-10+** [NEO-142](https://linear.app/neon-sprawl/issue/NEO-142) → **E7M3-11** [NEO-143](https://linear.app/neon-sprawl/issue/NEO-143). Two frozen factions; operator-chain **`reputationGrants`** (+15 Grid Operators); gated **`prototype_quest_grid_contract`**; client capstone [NEO-143](https://linear.app/neon-sprawl/issue/NEO-143). Upstream: E7.M1 **Ready**, E7.M2 **Ready**. | [NEO-133 plan](../../plans/NEO-133-implementation-plan.md), [NEO-134 plan](../../plans/NEO-134-implementation-plan.md), [NEO-135 plan](../../plans/NEO-135-implementation-plan.md), [NEO-136 plan](../../plans/NEO-136-implementation-plan.md), [NEO-137 plan](../../plans/NEO-137-implementation-plan.md), [NEO-138 plan](../../plans/NEO-138-implementation-plan.md), [NEO-139 plan](../../plans/NEO-139-implementation-plan.md), [NEO-140 plan](../../plans/NEO-140-implementation-plan.md), [NEO-141 plan](../../plans/NEO-141-implementation-plan.md), [E7M3-pre-production-backlog.md](../../plans/E7M3-pre-production-backlog.md), [E7_M3](E7_M3_FactionReputationLedger.md), label **`E7.M3`** on NEO-133–NEO-143 | --- diff --git a/docs/manual-qa/NEO-141.md b/docs/manual-qa/NEO-141.md new file mode 100644 index 0000000..3dd07dc --- /dev/null +++ b/docs/manual-qa/NEO-141.md @@ -0,0 +1,34 @@ +# Manual QA — NEO-141 (faction telemetry hook sites) + +Reference: [implementation plan](../plans/NEO-141-implementation-plan.md), [NEO-136 implementation plan](../plans/NEO-136-implementation-plan.md) (reputation ops), [NEO-137 implementation plan](../plans/NEO-137-implementation-plan.md) (gate ops). + +## Preconditions + +- Run `NeonSprawl.Server` (`dotnet run` from `server/NeonSprawl.Server`; default `http://localhost:5253`) if exercising Bruno; not required for code review only. +- This story adds **comments only**; faction standing, gate eval, and reward delivery behavior match NEO-136/137/138. + +## Code review check + +- [ ] Open `server/NeonSprawl.Server/Game/Factions/ReputationOperations.cs`. +- [ ] Confirm **`reputation_delta`** comment block immediately after **`auditStore.TryAppend`** returns **`true`**. +- [ ] Confirm **`TODO(E9.M1)`** markers and planned payload fields; **no** new `ILogger` or metrics calls. +- [ ] Open `server/NeonSprawl.Server/Game/Factions/FactionGateOperations.cs`. +- [ ] Confirm **`faction_gate_blocked`** comment on standing read-failure deny and threshold deny branches. +- [ ] Confirm **no** duplicate hook comments in **`QuestStateOperations`**, **`QuestAcceptApi`**, **`FactionStandingApi`**, or **`RewardRouterOperations`** for these event names. +- [ ] Open [server README — Faction telemetry hooks (NEO-141)](../../server/README.md#faction-telemetry-hooks-neo-141) — NEO-141 subsection present with event table. +- [ ] Open [server README — Reward telemetry hooks (NEO-130)](../../server/README.md#reward-telemetry-hooks-neo-130) — **`reputation_delta`** stub row removed; cross-link to NEO-141 present. + +## Regression (optional) + +```bash +cd server && dotnet test NeonSprawl.sln +``` + +Expect all tests pass (comments-only change). + +## Bruno (optional sanity) + +From `bruno/neon-sprawl-server/` against a running server: + +- **Accept grid contract faction gate deny** — **`faction_gate_blocked`** deny unchanged. +- **Accept grid contract after operator chain** — rep grant + accept success unchanged. diff --git a/docs/plans/E7M3-pre-production-backlog.md b/docs/plans/E7M3-pre-production-backlog.md index 25f3fb5..ef89cea 100644 --- a/docs/plans/E7M3-pre-production-backlog.md +++ b/docs/plans/E7M3-pre-production-backlog.md @@ -305,7 +305,9 @@ Accepting **`prototype_quest_grid_contract`** before operator-chain completion o **Acceptance criteria** -- [ ] Hook sites documented in README; no behavior change beyond comments. +- [x] Hook sites documented in README; no behavior change beyond comments. + +**Landed (NEO-141):** comment-only **`reputation_delta`** in **`ReputationOperations.TryApplyDelta`**; **`faction_gate_blocked`** in **`FactionGateOperations.TryEvaluate`** (threshold + standing read failure); router duplicate stub removed; [server README — Faction telemetry hooks (NEO-141)](../../../server/README.md#faction-telemetry-hooks-neo-141); manual QA [`NEO-141.md`](../../manual-qa/NEO-141.md). **Client counterpart:** none (infrastructure-only). diff --git a/docs/plans/NEO-141-implementation-plan.md b/docs/plans/NEO-141-implementation-plan.md index 3f24c62..964da71 100644 --- a/docs/plans/NEO-141-implementation-plan.md +++ b/docs/plans/NEO-141-implementation-plan.md @@ -49,7 +49,15 @@ Anchor future E9.M1 catalog events **`reputation_delta`** and **`faction_gate_bl ## Acceptance criteria checklist -- [ ] Hook sites documented in README; no behavior change beyond comments. +- [x] Hook sites documented in README; no behavior change beyond comments. + +## Implementation reconciliation (shipped) + +- **`ReputationOperations.TryApplyDelta`:** complete **`reputation_delta`** comment block after audit append success (`TODO(E9.M1)` + planned payload fields). +- **`FactionGateOperations.TryEvaluate`:** **`faction_gate_blocked`** hooks on threshold deny and standing read-failure deny (ops `reasonCode` in payload note). +- **`RewardRouterOperations`:** duplicate **`reputation_delta`** stub removed — single ops-layer anchor. +- **Docs:** `server/README.md` **Faction telemetry hooks (NEO-141)** subsection; NEO-130 reward table cross-link; E7.M3 module + alignment register + E7M3 backlog updated. +- **Manual QA:** `docs/manual-qa/NEO-141.md`. ## Technical approach From cbd8444da3ef7ba944263eabb8ad90ee9e366a07 Mon Sep 17 00:00:00 2001 From: VinPropane Date: Wed, 17 Jun 2026 19:26:54 -0400 Subject: [PATCH 5/6] NEO-141: add code review for faction telemetry hook sites --- docs/reviews/2026-06-17-NEO-141.md | 55 ++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 docs/reviews/2026-06-17-NEO-141.md diff --git a/docs/reviews/2026-06-17-NEO-141.md b/docs/reviews/2026-06-17-NEO-141.md new file mode 100644 index 0000000..1487939 --- /dev/null +++ b/docs/reviews/2026-06-17-NEO-141.md @@ -0,0 +1,55 @@ +# Code review — NEO-141 (E7M3-09) + +**Date:** 2026-06-17 +**Scope:** Branch `NEO-141-faction-telemetry-hooks` — commits `5ab8121` … `3c1ade4` vs `main` +**Base:** `main` + +## Verdict + +**Approve with nits** + +## Summary + +This branch lands **E7M3-09**: comment-only telemetry anchor sites for future E9.M1 catalog events **`reputation_delta`** and **`faction_gate_blocked`**, consolidating partial stubs from NEO-136/137/138 into single ops-layer hooks. **`ReputationOperations.TryApplyDelta`** gains a full NEO-121-style block after audit append success; **`FactionGateOperations.TryEvaluate`** adds the missing read-failure deny hook and tightens threshold-branch payload notes; **`RewardRouterOperations`** duplicate **`reputation_delta`** stub is removed. **`server/README.md`** adds a dedicated NEO-141 subsection and cross-links NEO-130. Docs (plan, module, alignment register, backlog, manual QA) are reconciled. The branch also includes **NEO-140 story-end cleanup** (remove `docs/manual-qa/NEO-140.md`, plan/review wording). No runtime, wire, or test behavior changes — **823** server tests pass. Low merge risk. + +## Documentation checked + +| Path | Result | +|------|--------| +| `docs/plans/NEO-141-implementation-plan.md` | **Matches** — kickoff decisions, AC checklist, reconciliation section, and file list align with the shipped diff. | +| `docs/plans/E7M3-pre-production-backlog.md` | **Partially matches** — E7M3-09 AC checked and landed note correct; in-scope bullet still mentions **`TryAccept`** deny path while implementation (correctly) anchors only **`FactionGateOperations`** per adopted plan decision. | +| `docs/decomposition/modules/E7_M3_FactionReputationLedger.md` | **Matches** — Status line notes E7M3-09 landed (NEO-141); Responsibilities “Emit telemetry hook sites” satisfied at comment layer. | +| `docs/decomposition/modules/documentation_and_implementation_alignment.md` | **Matches** — E7.M3 row updated with NEO-141 telemetry + README link. | +| `docs/decomposition/modules/module_dependency_register.md` | **N/A change** — no register row edit required beyond module doc / alignment table. | +| `docs/manual-qa/NEO-141.md` | **Matches** — NEO-130-style code-review checklist; Bruno optional sanity steps. | +| `docs/plans/NEO-140-implementation-plan.md` | **Matches** — manual QA removal note consistent with story-end convention (bundled in branch). | +| `server/README.md` | **Matches** — Faction telemetry hooks (NEO-141) subsection; NEO-130 table cross-link; no duplicate router stub. | + +## Blocking issues + +None. + +## Suggestions + +1. **Align E7M3-09 backlog in-scope bullet with shipped anchor.** [E7M3-pre-production-backlog.md](../plans/E7M3-pre-production-backlog.md) §E7M3-09 still lists “`**TryAccept`** deny path” alongside **`FactionGateOperations`**, but the adopted plan and code place **`faction_gate_blocked`** only in **`FactionGateOperations.TryEvaluate`** (quest layer maps HTTP reason without duplicating the hook). Update the in-scope bullet to ops-only wording so future readers do not expect a **`QuestStateOperations`** comment block. + +## Nits + +- Nit: **`FactionGateOperations`** class summary says “deny return path below” (singular) while two deny branches now carry hooks — consider “deny return paths” for accuracy. +- Nit: Branch bundles **NEO-140** manual QA removal (`ef75111`) with **NEO-141** work — acceptable hygiene; PR description should mention both if opened as one branch. + +## Verification + +```bash +cd /Users/don/neon-sprawl && dotnet test NeonSprawl.sln +``` + +Manual (see `docs/manual-qa/NEO-141.md` — code review checklist): + +- Confirm hook comment blocks in `ReputationOperations.cs`, `FactionGateOperations.cs`; no **`reputation_delta`** stub in `RewardRouterOperations.cs`. +- Confirm README NEO-141 subsection and NEO-130 cross-link. + +Optional Bruno (unchanged behavior): + +- `quest-progress/Accept grid contract faction gate deny` +- `quest-progress/Accept grid contract after operator chain` From 0a4450a199c00ec63ab5d34b6cbf7fda5e0d9c6b Mon Sep 17 00:00:00 2001 From: VinPropane Date: Wed, 17 Jun 2026 19:27:27 -0400 Subject: [PATCH 6/6] =?UTF-8?q?NEO-141:=20address=20review=20=E2=80=94=20b?= =?UTF-8?q?acklog=20wording=20and=20class=20summary?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Align E7M3-09 in-scope bullet with ops-only hook anchors; pluralize FactionGateOperations telemetry remark; mark review findings done. --- docs/plans/E7M3-pre-production-backlog.md | 2 +- docs/reviews/2026-06-17-NEO-141.md | 6 +++--- .../Game/Factions/FactionGateOperations.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/plans/E7M3-pre-production-backlog.md b/docs/plans/E7M3-pre-production-backlog.md index ef89cea..f812a2f 100644 --- a/docs/plans/E7M3-pre-production-backlog.md +++ b/docs/plans/E7M3-pre-production-backlog.md @@ -296,7 +296,7 @@ Accepting **`prototype_quest_grid_contract`** before operator-chain completion o **In scope** - Comment-only **`reputation_delta`** in **`ReputationOperations`** (successful apply). -- Comment-only **`faction_gate_blocked`** in **`FactionGateOperations`** / **`TryAccept`** deny path. +- Comment-only **`faction_gate_blocked`** in **`FactionGateOperations.TryEvaluate`** (threshold + standing read failure deny paths). - `server/README.md` hook list. **Out of scope** diff --git a/docs/reviews/2026-06-17-NEO-141.md b/docs/reviews/2026-06-17-NEO-141.md index 1487939..38a0517 100644 --- a/docs/reviews/2026-06-17-NEO-141.md +++ b/docs/reviews/2026-06-17-NEO-141.md @@ -17,7 +17,7 @@ This branch lands **E7M3-09**: comment-only telemetry anchor sites for future E9 | Path | Result | |------|--------| | `docs/plans/NEO-141-implementation-plan.md` | **Matches** — kickoff decisions, AC checklist, reconciliation section, and file list align with the shipped diff. | -| `docs/plans/E7M3-pre-production-backlog.md` | **Partially matches** — E7M3-09 AC checked and landed note correct; in-scope bullet still mentions **`TryAccept`** deny path while implementation (correctly) anchors only **`FactionGateOperations`** per adopted plan decision. | +| `docs/plans/E7M3-pre-production-backlog.md` | **Matches** — E7M3-09 AC checked and landed note correct; in-scope bullets align with ops-only anchors. | | `docs/decomposition/modules/E7_M3_FactionReputationLedger.md` | **Matches** — Status line notes E7M3-09 landed (NEO-141); Responsibilities “Emit telemetry hook sites” satisfied at comment layer. | | `docs/decomposition/modules/documentation_and_implementation_alignment.md` | **Matches** — E7.M3 row updated with NEO-141 telemetry + README link. | | `docs/decomposition/modules/module_dependency_register.md` | **N/A change** — no register row edit required beyond module doc / alignment table. | @@ -31,11 +31,11 @@ None. ## Suggestions -1. **Align E7M3-09 backlog in-scope bullet with shipped anchor.** [E7M3-pre-production-backlog.md](../plans/E7M3-pre-production-backlog.md) §E7M3-09 still lists “`**TryAccept`** deny path” alongside **`FactionGateOperations`**, but the adopted plan and code place **`faction_gate_blocked`** only in **`FactionGateOperations.TryEvaluate`** (quest layer maps HTTP reason without duplicating the hook). Update the in-scope bullet to ops-only wording so future readers do not expect a **`QuestStateOperations`** comment block. +1. ~~**Align E7M3-09 backlog in-scope bullet with shipped anchor.** [E7M3-pre-production-backlog.md](../plans/E7M3-pre-production-backlog.md) §E7M3-09 still lists “`**TryAccept`** deny path” alongside **`FactionGateOperations`**, but the adopted plan and code place **`faction_gate_blocked`** only in **`FactionGateOperations.TryEvaluate`** (quest layer maps HTTP reason without duplicating the hook). Update the in-scope bullet to ops-only wording so future readers do not expect a **`QuestStateOperations`** comment block.~~ **Done.** In-scope bullet now reads **`FactionGateOperations.TryEvaluate`** (threshold + standing read failure deny paths). ## Nits -- Nit: **`FactionGateOperations`** class summary says “deny return path below” (singular) while two deny branches now carry hooks — consider “deny return paths” for accuracy. +- ~~Nit: **`FactionGateOperations`** class summary says “deny return path below” (singular) while two deny branches now carry hooks — consider “deny return paths” for accuracy.~~ **Done.** - Nit: Branch bundles **NEO-140** manual QA removal (`ef75111`) with **NEO-141** work — acceptable hygiene; PR description should mention both if opened as one branch. ## Verification diff --git a/server/NeonSprawl.Server/Game/Factions/FactionGateOperations.cs b/server/NeonSprawl.Server/Game/Factions/FactionGateOperations.cs index 9752814..59f91b8 100644 --- a/server/NeonSprawl.Server/Game/Factions/FactionGateOperations.cs +++ b/server/NeonSprawl.Server/Game/Factions/FactionGateOperations.cs @@ -5,7 +5,7 @@ namespace NeonSprawl.Server.Game.Factions; /// /// Evaluates minimum-standing gates (NEO-137). /// Quest accept wiring: . -/// NEO-141 telemetry hook site: deny return path below. +/// NEO-141 telemetry hook site: deny return paths below. /// public static class FactionGateOperations {