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.md — E7M3-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 Done — ReputationOperations.TryApplyDelta + partial reputation_delta hook comment; NEO-137 Done — FactionGateOperations.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_deltahook inReputationOperations(successful apply path — addTODO(E9.M1)+ planned payload fields). - Confirm / extend comment-only
faction_gate_blockedinFactionGateOperations(threshold deny + standing read failure deny). - Remove duplicate
reputation_deltastub fromRewardRouterOperations. 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: completereputation_deltacomment block after audit append success (TODO(E9.M1)+ planned payload fields).FactionGateOperations.TryEvaluate:faction_gate_blockedhooks on threshold deny and standing read-failure deny (opsreasonCodein payload note).RewardRouterOperations: duplicatereputation_deltastub removed — single ops-layer anchor.- Docs:
server/README.mdFaction 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 testcovers 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 opsreasonCode(unknown_faction, etc.),playerId,factionId,minStanding, optionalcurrentStanding(null when unknown). QuestStateOperations.TryAcceptmaps all gate failures to HTTPfaction_gate_blockedregardless of ops reason — E9.M1 can use ops payload for drill-down.- Optional quest context (
questId) deferred to E9.M1 wiring atTryAcceptcall 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 |