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.
pull/181/head
VinPropane 2026-06-17 19:25:25 -04:00
parent 5ab81215ad
commit 73403622f2
4 changed files with 21 additions and 5 deletions

View File

@ -30,13 +30,16 @@ public static class FactionGateOperations
var read = standingStore.TryGetStanding(normalizedPlayerId, factionId); var read = standingStore.TryGetStanding(normalizedPlayerId, factionId);
if (!read.Success) 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); return Deny(read.ReasonCode, factionId, rule.MinStanding, null);
} }
if (read.Standing < rule.MinStanding) if (read.Standing < rule.MinStanding)
{ {
// --- Telemetry hook site (NEO-141): future E9.M1 catalog event `faction_gate_blocked` --- // --- 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). // No ingest or ILogger here (comments-only).
return Deny( return Deny(
FactionGateEvaluateReasonCodes.GateBlocked, FactionGateEvaluateReasonCodes.GateBlocked,

View File

@ -67,6 +67,10 @@ public static class ReputationOperations
if (auditStore.TryAppend(row)) if (auditStore.TryAppend(row))
{ {
// --- Telemetry hook site (NEO-141): future E9.M1 catalog event `reputation_delta` --- // --- 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( return new ReputationApplyOutcome(
true, true,
null, null,

View File

@ -220,9 +220,6 @@ public static class RewardRouterOperations
var appliedAmount = repOutcome.NewStanding - repOutcome.PreviousStanding; var appliedAmount = repOutcome.NewStanding - repOutcome.PreviousStanding;
appliedReputation.Add(new RewardReputationGrantApplied(grant.FactionId, appliedAmount)); 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(); var deliveredAt = timeProvider.GetUtcNow();

View File

@ -134,6 +134,17 @@ Quest accept evaluates **`FactionGateRule`** rows through **`FactionGateOperatio
Plan: [NEO-137 implementation plan](../../docs/plans/NEO-137-implementation-plan.md). 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): **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). - **`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). - **`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 | | Event | Anchor | When |
|-------|--------|------| |-------|--------|------|
| **`reward_delivery`** | **`TryDeliverQuestCompletion`** | After **`TryRecord`** returns **`true`** on first-time delivery (not idempotent **`TryGet`** replay or race-loser paths). | | **`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. | | **`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). **`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) ## Encounter definitions (NEO-103)