NEO-141: add faction telemetry hook sites implementation plan

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.
pull/181/head
VinPropane 2026-06-17 19:23:25 -04:00
parent ef75111b2a
commit 5ab81215ad
1 changed files with 162 additions and 0 deletions

View File

@ -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` |