neon-sprawl/docs/plans/NEO-141-implementation-plan.md

12 KiB

NEO-141 — E7M3-09: Faction telemetry hook sites (comment-only)

Linear: NEO-141
Branch: NEO-141-faction-telemetry-hooks
Backlog: E7M3-pre-production-backlog.mdE7M3-09
Module: E7_M3_FactionReputationLedger.md
Pattern: NEO-121 / NEO-130 — comment-only engine anchors + TODO(E9.M1); engine-only (no duplicate API-layer hooks)
Precursors: NEO-136 DoneReputationOperations.TryApplyDelta + partial reputation_delta hook comment; NEO-137 DoneFactionGateOperations.TryEvaluate + faction_gate_blocked hook comment; NEO-138 Done — router rep apply + duplicate router stub comment (to consolidate here)
Blocks: NEO-143 (E7M3-11 client capstone — module telemetry vocabulary complete)
Client counterpart: none (infrastructure-only per E7M3-09)

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, 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 None — comment-only server story; dotnet test covers regression (NEO-140 precedent; planning-implementation-docs skip when AC fully automated) Infrastructure-only; no user-visible behavior

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.

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.

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: none — comment-only server story; dotnet test covers regression (manual QA checklists reserved for user-visible stories per project convention).

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):

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): 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)

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

None — comment-only changes to existing ops files and docs; no new source or test files.

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).

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