diff --git a/docs/decomposition/modules/E7_M4_ContractMissionGenerator.md b/docs/decomposition/modules/E7_M4_ContractMissionGenerator.md
index 0076acf..7bd0fd5 100644
--- a/docs/decomposition/modules/E7_M4_ContractMissionGenerator.md
+++ b/docs/decomposition/modules/E7_M4_ContractMissionGenerator.md
@@ -69,6 +69,8 @@ Epic 7 **Slice 4** — `contract_issued`, `contract_complete`, reward anomalies.
**Contract HTTP (NEO-151):** **`POST …/contracts/issue`** + **`GET …/contracts`** — issue envelope + per-player list with **`encounterTemplateId`** and **`completionRewardSummary`** on completed rows; Bruno `bruno/neon-sprawl-server/contracts/`; [server README — Contract issue HTTP (NEO-151)](../../../server/README.md#contract-issue-http-neo-151), [Per-player contract list HTTP (NEO-151)](../../../server/README.md#per-player-contract-list-http-neo-151); plan [NEO-151](../../plans/NEO-151-implementation-plan.md).
+**Contract telemetry hooks (NEO-152):** comment-only **`contract_issued`**, **`contract_complete`**, and **`reward_anomaly`** anchor sites in generator/completion ops + shared reward router rollback; [server README — Contract telemetry hooks (NEO-152)](../../../server/README.md#contract-telemetry-hooks-neo-152); plan [NEO-152](../../plans/NEO-152-implementation-plan.md).
+
## Source anchors
- Master plan: [`neon_sprawl_vision.plan.md`](../../../neon_sprawl_vision.plan.md) — Epic 7.
diff --git a/docs/decomposition/modules/documentation_and_implementation_alignment.md b/docs/decomposition/modules/documentation_and_implementation_alignment.md
index 00b4074..87ac194 100644
--- a/docs/decomposition/modules/documentation_and_implementation_alignment.md
+++ b/docs/decomposition/modules/documentation_and_implementation_alignment.md
@@ -62,7 +62,7 @@ Rows appear when work starts; default for unlisted modules is **Planned** / not
| 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 | Ready | **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 client faction HUD landed ([NEO-142](https://linear.app/neon-sprawl/issue/NEO-142)):** `faction_standing_client.gd`, standing label + gate deny + rep reward lines ([NEO-142 plan](../../plans/NEO-142-implementation-plan.md)); [client README — Faction standing + gate feedback HUD (NEO-142)](../../../client/README.md#faction-standing--gate-feedback-hud-neo-142); [`NEO-142` manual QA](../../manual-qa/NEO-142.md). **E7M3-11 client capstone landed ([NEO-143](https://linear.app/neon-sprawl/issue/NEO-143)):** playable faction reputation + gate capstone — [`NEO-143` manual QA](../../manual-qa/NEO-143.md); [client README — End-to-end faction reputation loop (NEO-143)](../../../client/README.md#end-to-end-faction-reputation-loop-neo-143); plan [NEO-143](../../plans/NEO-143-implementation-plan.md). **Epic 7 Slice 3 client capstone complete.** Two frozen factions; operator-chain **`reputationGrants`** (+15 Grid Operators); gated **`prototype_quest_grid_contract`**. 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), [NEO-142 plan](../../plans/NEO-142-implementation-plan.md), [NEO-143 plan](../../plans/NEO-143-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.M4 | In Progress | **E7M4-01 catalog landed ([NEO-144](https://linear.app/neon-sprawl/issue/NEO-144)):** `contract-template` + `contract-seed` schemas; `prototype_contract_templates.json`; CI roster/freeze/cross-ref/band-cap gates. **E7M4-02 server load landed ([NEO-145](https://linear.app/neon-sprawl/issue/NEO-145)):** fail-fast contract template catalog, `IContractTemplateRegistry`, `PrototypeE7M4ContractCatalogRules` at startup ([NEO-145 plan](../../plans/NEO-145-implementation-plan.md)); [server README — Contract template catalog](../../../server/README.md#contract-template-catalog-contentcontracts-neo-145). **E7M4-03 stores landed ([NEO-146](https://linear.app/neon-sprawl/issue/NEO-146)):** `IContractInstanceStore`, `IContractOutcomeStore`, in-memory + Postgres `V011`/`V012` ([NEO-146 plan](../../plans/NEO-146-implementation-plan.md)); [server README — Contract instance store (NEO-146)](../../../server/README.md#contract-instance-store-neo-146), [Contract outcome store (NEO-146)](../../../server/README.md#contract-outcome-store-neo-146); Bruno `bruno/neon-sprawl-server/contract-stores/`. **E7M4-04 generator landed ([NEO-147](https://linear.app/neon-sprawl/issue/NEO-147)):** `ContractGeneratorOperations.TryIssue`, `ContractGeneratorReasonCodes`, deterministic `ci_{hex}` instance ids ([NEO-147 plan](../../plans/NEO-147-implementation-plan.md)); [server README — Contract generator operations (NEO-147)](../../../server/README.md#contract-generator-operations-neo-147). **E7M4-05 reward router landed ([NEO-148](https://linear.app/neon-sprawl/issue/NEO-148)):** `RewardRouterOperations.TryDeliverContractCompletion`, source-kind-aware `IRewardDeliveryStore` / `RewardDeliveryEvent` extension ([NEO-148 plan](../../plans/NEO-148-implementation-plan.md)); [server README — Reward router (NEO-127)](../../../server/README.md#reward-router-neo-127), [Reward delivery store (NEO-126)](../../../server/README.md#reward-delivery-store-neo-126). **E7M4-06 completion wiring landed ([NEO-149](https://linear.app/neon-sprawl/issue/NEO-149)):** `ContractCompletionOperations.TryCompleteOnEncounterClear`, encounter hook after E5.M3 loot + quest wiring, `QuestFixtureRequest.resetContractInstanceIds` ([NEO-149 plan](../../plans/NEO-149-implementation-plan.md)); [server README — Contract completion operations (NEO-149)](../../../server/README.md#contract-completion-operations-neo-149); Bruno `bruno/neon-sprawl-server/contract-stores/Reset contract instance via quest fixture.bru`. **E7M4-07 economy validation landed ([NEO-150](https://linear.app/neon-sprawl/issue/NEO-150)):** `ContractEconomyValidation.TryValidateTemplate` at issue — band caps + cross-ref deny codes ([NEO-150 plan](../../plans/NEO-150-implementation-plan.md)); [server README — Contract generator operations (NEO-147)](../../../server/README.md#contract-generator-operations-neo-147). **E7M4-08 contract HTTP landed ([NEO-151](https://linear.app/neon-sprawl/issue/NEO-151)):** `POST …/contracts/issue` + `GET …/contracts` — issue envelope, active + recent completed list, `completionRewardSummary` on completed rows ([NEO-151 plan](../../plans/NEO-151-implementation-plan.md)); [server README — Contract issue HTTP (NEO-151)](../../../server/README.md#contract-issue-http-neo-151); Bruno `bruno/neon-sprawl-server/contracts/`. **Decomposition landed:** Epic 7 Slice 4 backlog [E7M4-pre-production-backlog.md](../../plans/E7M4-pre-production-backlog.md) — **E7M4-01** [NEO-144](https://linear.app/neon-sprawl/issue/NEO-144) → **E7M4-11** [NEO-154](https://linear.app/neon-sprawl/issue/NEO-154); label **`E7.M4`**. Prototype spine: **`prototype_contract_clear_combat_pocket`** template → **`prototype_combat_pocket`** encounter clear → repeat payout **`scrap_metal_bulk` ×5** + salvage **15**; one active contract per player; economy band caps at issue; E4.M1 live zone deferred (content **`zoneDifficultyBand`**). Client capstone **E7M4-11** [NEO-154](https://linear.app/neon-sprawl/issue/NEO-154). Upstream: E5.M3 **Ready**, E7.M2 router **Ready**, E7.M3 standing **Ready**. | [E7M4-pre-production-backlog.md](../../plans/E7M4-pre-production-backlog.md), [E7_M4](E7_M4_ContractMissionGenerator.md), [NEO-144](../../plans/NEO-144-implementation-plan.md), [NEO-145](../../plans/NEO-145-implementation-plan.md), [NEO-146](../../plans/NEO-146-implementation-plan.md), [NEO-147](../../plans/NEO-147-implementation-plan.md), [NEO-148](../../plans/NEO-148-implementation-plan.md), [NEO-149](../../plans/NEO-149-implementation-plan.md), [NEO-150](../../plans/NEO-150-implementation-plan.md), [NEO-151](../../plans/NEO-151-implementation-plan.md), label **`E7.M4`** on NEO-144–NEO-154 |
+| E7.M4 | In Progress | **E7M4-01 catalog landed ([NEO-144](https://linear.app/neon-sprawl/issue/NEO-144)):** `contract-template` + `contract-seed` schemas; `prototype_contract_templates.json`; CI roster/freeze/cross-ref/band-cap gates. **E7M4-02 server load landed ([NEO-145](https://linear.app/neon-sprawl/issue/NEO-145)):** fail-fast contract template catalog, `IContractTemplateRegistry`, `PrototypeE7M4ContractCatalogRules` at startup ([NEO-145 plan](../../plans/NEO-145-implementation-plan.md)); [server README — Contract template catalog](../../../server/README.md#contract-template-catalog-contentcontracts-neo-145). **E7M4-03 stores landed ([NEO-146](https://linear.app/neon-sprawl/issue/NEO-146)):** `IContractInstanceStore`, `IContractOutcomeStore`, in-memory + Postgres `V011`/`V012` ([NEO-146 plan](../../plans/NEO-146-implementation-plan.md)); [server README — Contract instance store (NEO-146)](../../../server/README.md#contract-instance-store-neo-146), [Contract outcome store (NEO-146)](../../../server/README.md#contract-outcome-store-neo-146); Bruno `bruno/neon-sprawl-server/contract-stores/`. **E7M4-04 generator landed ([NEO-147](https://linear.app/neon-sprawl/issue/NEO-147)):** `ContractGeneratorOperations.TryIssue`, `ContractGeneratorReasonCodes`, deterministic `ci_{hex}` instance ids ([NEO-147 plan](../../plans/NEO-147-implementation-plan.md)); [server README — Contract generator operations (NEO-147)](../../../server/README.md#contract-generator-operations-neo-147). **E7M4-05 reward router landed ([NEO-148](https://linear.app/neon-sprawl/issue/NEO-148)):** `RewardRouterOperations.TryDeliverContractCompletion`, source-kind-aware `IRewardDeliveryStore` / `RewardDeliveryEvent` extension ([NEO-148 plan](../../plans/NEO-148-implementation-plan.md)); [server README — Reward router (NEO-127)](../../../server/README.md#reward-router-neo-127), [Reward delivery store (NEO-126)](../../../server/README.md#reward-delivery-store-neo-126). **E7M4-06 completion wiring landed ([NEO-149](https://linear.app/neon-sprawl/issue/NEO-149)):** `ContractCompletionOperations.TryCompleteOnEncounterClear`, encounter hook after E5.M3 loot + quest wiring, `QuestFixtureRequest.resetContractInstanceIds` ([NEO-149 plan](../../plans/NEO-149-implementation-plan.md)); [server README — Contract completion operations (NEO-149)](../../../server/README.md#contract-completion-operations-neo-149); Bruno `bruno/neon-sprawl-server/contract-stores/Reset contract instance via quest fixture.bru`. **E7M4-07 economy validation landed ([NEO-150](https://linear.app/neon-sprawl/issue/NEO-150)):** `ContractEconomyValidation.TryValidateTemplate` at issue — band caps + cross-ref deny codes ([NEO-150 plan](../../plans/NEO-150-implementation-plan.md)); [server README — Contract generator operations (NEO-147)](../../../server/README.md#contract-generator-operations-neo-147). **E7M4-08 contract HTTP landed ([NEO-151](https://linear.app/neon-sprawl/issue/NEO-151)):** `POST …/contracts/issue` + `GET …/contracts` — issue envelope, active + recent completed list, `completionRewardSummary` on completed rows ([NEO-151 plan](../../plans/NEO-151-implementation-plan.md)); [server README — Contract issue HTTP (NEO-151)](../../../server/README.md#contract-issue-http-neo-151); Bruno `bruno/neon-sprawl-server/contracts/`. **E7M4-09 telemetry landed ([NEO-152](https://linear.app/neon-sprawl/issue/NEO-152)):** comment-only **`contract_issued`**, **`contract_complete`**, and **`reward_anomaly`** hook sites ([NEO-152 plan](../../plans/NEO-152-implementation-plan.md)); [server README — Contract telemetry hooks (NEO-152)](../../../server/README.md#contract-telemetry-hooks-neo-152). **Decomposition landed:** Epic 7 Slice 4 backlog [E7M4-pre-production-backlog.md](../../plans/E7M4-pre-production-backlog.md) — **E7M4-01** [NEO-144](https://linear.app/neon-sprawl/issue/NEO-144) → **E7M4-11** [NEO-154](https://linear.app/neon-sprawl/issue/NEO-154); label **`E7.M4`**. Prototype spine: **`prototype_contract_clear_combat_pocket`** template → **`prototype_combat_pocket`** encounter clear → repeat payout **`scrap_metal_bulk` ×5** + salvage **15**; one active contract per player; economy band caps at issue; E4.M1 live zone deferred (content **`zoneDifficultyBand`**). Client capstone **E7M4-11** [NEO-154](https://linear.app/neon-sprawl/issue/NEO-154). Upstream: E5.M3 **Ready**, E7.M2 router **Ready**, E7.M3 standing **Ready**. | [E7M4-pre-production-backlog.md](../../plans/E7M4-pre-production-backlog.md), [E7_M4](E7_M4_ContractMissionGenerator.md), [NEO-144](../../plans/NEO-144-implementation-plan.md), [NEO-145](../../plans/NEO-145-implementation-plan.md), [NEO-146](../../plans/NEO-146-implementation-plan.md), [NEO-147](../../plans/NEO-147-implementation-plan.md), [NEO-148](../../plans/NEO-148-implementation-plan.md), [NEO-149](../../plans/NEO-149-implementation-plan.md), [NEO-150](../../plans/NEO-150-implementation-plan.md), [NEO-151](../../plans/NEO-151-implementation-plan.md), [NEO-152](../../plans/NEO-152-implementation-plan.md), label **`E7.M4`** on NEO-144–NEO-154 |
---
diff --git a/docs/plans/E7M4-pre-production-backlog.md b/docs/plans/E7M4-pre-production-backlog.md
index f9fa208..db96553 100644
--- a/docs/plans/E7M4-pre-production-backlog.md
+++ b/docs/plans/E7M4-pre-production-backlog.md
@@ -307,7 +307,9 @@ Working backlog for **Epic 7 — Slice 4** ([contract generator](../decompositio
**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-152):** comment-only **`contract_issued`**, **`contract_complete`**, and **`reward_anomaly`** hooks; [server README — Contract telemetry hooks (NEO-152)](../../server/README.md#contract-telemetry-hooks-neo-152); plan [NEO-152](../../plans/NEO-152-implementation-plan.md).
**Client counterpart:** none (infrastructure-only).
diff --git a/docs/plans/NEO-152-implementation-plan.md b/docs/plans/NEO-152-implementation-plan.md
new file mode 100644
index 0000000..8afea9f
--- /dev/null
+++ b/docs/plans/NEO-152-implementation-plan.md
@@ -0,0 +1,188 @@
+# NEO-152 — E7M4-09: Contract telemetry hook sites (comment-only)
+
+**Linear:** [NEO-152](https://linear.app/neon-sprawl/issue/NEO-152)
+**Branch:** `NEO-152-e7m4-09-contract-telemetry-hook-sites-comment-only`
+**Backlog:** [E7M4-pre-production-backlog.md](E7M4-pre-production-backlog.md) — **E7M4-09**
+**Module:** [E7_M4_ContractMissionGenerator.md](../decomposition/modules/E7_M4_ContractMissionGenerator.md)
+**Pattern:** [NEO-121](NEO-121-implementation-plan.md) / [NEO-130](NEO-130-implementation-plan.md) / [NEO-141](NEO-141-implementation-plan.md) — comment-only engine anchors + `TODO(E9.M1)`; engine-only (no duplicate API-layer hooks)
+**Precursors:** [NEO-147](https://linear.app/neon-sprawl/issue/NEO-147) **`Done`** — `ContractGeneratorOperations.TryIssue`; [NEO-149](https://linear.app/neon-sprawl/issue/NEO-149) **`Done`** — `ContractCompletionOperations` + partial `contract_complete` hook comment; [NEO-150](https://linear.app/neon-sprawl/issue/NEO-150) **`Done`** — `ContractEconomyValidation` at issue (deferred `reward_anomaly` to this story); [NEO-151](https://linear.app/neon-sprawl/issue/NEO-151) **`Done`** — contract HTTP (**on `main`**)
+**Blocks:** [NEO-153](https://linear.app/neon-sprawl/issue/NEO-153) (Godot contract HUD — telemetry vocabulary complete for server path)
+**Client counterpart:** none (infrastructure-only per [E7M4-09](E7M4-pre-production-backlog.md#e7m4-09--contract-telemetry-hook-sites-comment-only))
+
+## Goal
+
+Anchor future E9.M1 catalog events **`contract_issued`**, **`contract_complete`**, and **`reward_anomaly`** on the server-authoritative contract path without ingest or behavior change.
+
+## Kickoff clarifications
+
+**No clarifications needed.** Linear AC, [E7M4-09 backlog scope](E7M4-pre-production-backlog.md#e7m4-09--contract-telemetry-hook-sites-comment-only), landed contract ops/HTTP (NEO-147/149/150/151), and NEO-121/130/141 comment-only telemetry precedents settle scope.
+
+| Topic | Decision | Evidence |
+|-------|----------|----------|
+| **Runtime behavior** | Comments-only + `TODO(E9.M1)` — no ingest, no `ILogger` | NEO-121/130/141 precedent |
+| **`contract_issued` anchor** | **`ContractGeneratorOperations.TryIssue`** — after successful **`TryCreateActive`** (first-time active row persisted) | Backlog E7M4-09; NEO-147 deferred hook |
+| **`contract_complete` anchor** | **`ContractCompletionOperations.TryCompleteOnEncounterClear`** — after successful **`TryMarkComplete`** + outcome append (delivery already succeeded) | Backlog E7M4-09; partial comment landed in NEO-149 — complete to NEO-121 block style |
+| **`reward_anomaly` — economy deny** | **`ContractGeneratorOperations.TryIssue`** when **`ContractEconomyValidation.TryValidateTemplate`** returns false (`economy_cap_exceeded`, `invalid_reward_bundle`) | Backlog E7M4-09; NEO-150 deferred |
+| **`reward_anomaly` — delivery rollback** | Private **`CompensatingRevertAll`** in **`RewardRouterOperations`** (single method-level stub — all **`TryDeliverBundle`** rollback call sites); E9.M1 filters by **`SourceKind`** | Shared router core (NEO-148); DRY vs per-call-site duplicates (NEO-121 central **`Deny`** precedent) |
+| **API-layer hooks** | **None** — **`ContractIssueApi`** / **`ContractListApi`** delegate to ops only | NEO-121/141 engine-only precedent |
+| **`reward_delivery` for contracts** | **No duplicate** — reuse NEO-130 **`reward_delivery`** hook at **`TryRecord`** success inside shared **`TryDeliverBundle`**; README cross-links contract **`SourceKind`** | NEO-130 already documents payload; contract uses same hook site |
+| **Manual QA doc** | **None** — comment-only server story; `dotnet test` covers regression | NEO-141 precedent; no user-visible behavior |
+
+## Scope and out-of-scope
+
+**In scope (from Linear + backlog):**
+
+- Comment-only **`contract_issued`** hook in **`ContractGeneratorOperations.TryIssue`** (successful issue commit).
+- Complete comment-only **`contract_complete`** hook in **`ContractCompletionOperations`** (verify/extend NEO-149 partial block).
+- Comment-only **`reward_anomaly`** stubs at economy validation deny (issue path) and delivery rollback paths (shared router bundle).
+- **`server/README.md`** — dedicated **Contract telemetry hooks (NEO-152)** subsection with event table.
+- **`E7_M4_ContractMissionGenerator.md`**, alignment register, E7M4 backlog updates when implementation completes.
+
+**Out of scope:**
+
+- E9.M1 ingest pipeline; runtime telemetry emit.
+- `ILogger` / metrics / dev-only log lines.
+- Duplicate hooks in **`ContractIssueApi`**, **`ContractListApi`**, **`EncounterCompletionOperations`**, or **`ContractEconomyValidation`** (orchestrator/router anchor only).
+- Godot client (**NEO-153** / E7M4-10).
+
+**Client counterpart:** none.
+
+## Acceptance criteria checklist
+
+- [x] Hook sites documented in README; no behavior change beyond comments.
+
+## Implementation reconciliation (shipped)
+
+- **`ContractGeneratorOperations.TryIssue`:** **`contract_issued`** hook after successful **`TryCreateActive`**; **`reward_anomaly`** stub on economy validation deny.
+- **`ContractCompletionOperations.TryCompleteOnEncounterClear`:** **`contract_complete`** hook extended to NEO-121 block style (NEO-149 partial comment completed).
+- **`RewardRouterOperations.TryDeliverBundle` / `CompensatingRevertAll`:** **`reward_anomaly`** stub for partial-apply rollback (single block on private **`CompensatingRevertAll`** — covers all four **`TryDeliverBundle`** call sites; DRY vs per-call-site duplicates); NEO-130 **`reward_delivery`** comment updated for contract **`SourceKind`**.
+- **Docs:** `server/README.md` **Contract telemetry hooks (NEO-152)** subsection; E7.M4 module + alignment register + E7M4 backlog updated.
+- **Tests:** no new tests — comments-only; **`dotnet test`** regression green.
+
+## Technical approach
+
+### 1. Shipped state (post-NEO-152)
+
+| Location | Shipped state |
+|----------|---------------|
+| **`ContractGeneratorOperations.TryIssue`** | **`contract_issued`** + economy-deny **`reward_anomaly`** comment blocks |
+| **`ContractCompletionOperations.TryCompleteOnEncounterClear`** | Full NEO-121-style **`contract_complete`** hook after deliver-then-mark |
+| **`RewardRouterOperations.CompensatingRevertAll`** | Single **`reward_anomaly`** stub (private method — all rollback call sites) |
+| **`server/README.md`** | **Contract telemetry hooks (NEO-152)** subsection + NEO-130 cross-link |
+
+### 2. Hook site A — `contract_issued`
+
+**Anchor:** **`ContractGeneratorOperations.TryIssue`**, immediately after **`instanceStore.TryCreateActive`** returns **`true`**.
+
+- Comment block names future E9.M1 event **`contract_issued`**.
+- **`TODO(E9.M1): catalog emit`** — once per first-time active instance persist.
+- **Not** on: deny paths, idempotent create failure, or economy validation failure.
+- Planned payload fields: `playerId`, `contractInstanceId`, `templateId`, `seedBucket`, `encounterTemplateId`, `issuedAt`.
+
+### 3. Hook site B — `contract_complete`
+
+**Anchor:** **`ContractCompletionOperations.TryCompleteOnEncounterClear`**, after successful **`TryMarkComplete`** + outcome append (delivery already committed via router).
+
+- Extend existing NEO-149 partial comment to full block style.
+- **`TODO(E9.M1): catalog emit`** — first-time completion only; idempotent replay / already-completed short-circuit excluded.
+- **Not** on: no-op paths (`no_active_contract`, `encounter_mismatch`), delivery deny (contract stays active), or mark-complete failure after successful delivery.
+- Planned payload fields: `playerId`, `contractInstanceId`, `templateId`, `encounterId`, `completedAt`, idempotencyKey (`{playerId}:contract_complete:{contractInstanceId}`).
+
+### 4. Hook site C — `reward_anomaly` (stub)
+
+**C1 — economy validation deny**
+
+**Anchor:** **`ContractGeneratorOperations.TryIssue`**, when **`ContractEconomyValidation.TryValidateTemplate`** returns **`false`** (before persist).
+
+- Stub comment names **`reward_anomaly`** with ops **`reasonCode`** (`economy_cap_exceeded`, `invalid_reward_bundle`).
+- Defense-in-depth path — startup catalog load should prevent most cases; issue-time lint is explicit Slice 4 AC.
+
+**C2 — delivery rollback**
+
+**Anchor:** private **`CompensatingRevertAll`** in **`RewardRouterOperations`** (invoked from four **`TryDeliverBundle`** rollback paths after partial grant apply — skill XP / reputation / TryRecord race loser). Single method-level stub covers all call sites (NEO-121 central **`Deny`** precedent).
+
+- Stub comment names **`reward_anomaly`**; note E9.M1 filters by **`DeliveryTarget.SourceKind`** — contract completions use **`contract_completion`**.
+- **Not** on: early denies before any grant apply (e.g. **`inventory_full`** simulation fail), idempotent **`TryGet`** replay, or successful **`TryRecord`**.
+
+### 5. README consolidation
+
+Add **`### Contract telemetry hooks (NEO-152)`** after [Contract completion operations (NEO-149)](../../server/README.md#contract-completion-operations-neo-149):
+
+| Event | Anchor | When |
+|-------|--------|------|
+| **`contract_issued`** | **`ContractGeneratorOperations.TryIssue`** | After successful **`TryCreateActive`** (active instance persisted). |
+| **`contract_complete`** | **`ContractCompletionOperations.TryCompleteOnEncounterClear`** | After successful deliver-then-mark (instance marked complete + outcome appended). |
+| **`reward_anomaly`** | **`ContractGeneratorOperations.TryIssue`** (economy deny); **`RewardRouterOperations.CompensatingRevertAll`** | Economy cap / bundle cross-ref deny at issue; compensating revert after partial delivery apply. |
+| **`reward_delivery`** *(cross-link)* | **`RewardRouterOperations.TryDeliverBundle`** | NEO-130 — first-time **`TryRecord`** success; contract path uses **`SourceKind = contract_completion`**. |
+
+### 6. Relationship to adjacent hooks
+
+| Event | Layer | Story |
+|-------|-------|-------|
+| **`contract_issued`** | **`ContractGeneratorOperations`** | NEO-152 |
+| **`contract_complete`** | **`ContractCompletionOperations`** | NEO-152 |
+| **`reward_anomaly`** | Generator (economy deny) + router (rollback) | NEO-152 |
+| **`reward_delivery`** | **`RewardRouterOperations`** | NEO-130 (shared; contract via NEO-148) |
+| **`reputation_delta`** | **`ReputationOperations`** | NEO-141 (contract rep grants funnel here) |
+| **`encounter_complete`** | Encounter ops | NEO-109 (encounter clear triggers contract completion wiring) |
+
+### 7. Flow (comment anchors only)
+
+```mermaid
+sequenceDiagram
+ participant Issue as ContractGeneratorOperations.TryIssue
+ participant Econ as ContractEconomyValidation
+ participant Store as IContractInstanceStore
+ participant Enc as EncounterCompletionOperations
+ participant Complete as ContractCompletionOperations
+ participant Router as RewardRouterOperations
+
+ Issue->>Econ: TryValidateTemplate
+ alt economy deny
+ Econ-->>Issue: fail (hook: reward_anomaly)
+ else pass
+ Issue->>Store: TryCreateActive
+ Store-->>Issue: success (hook: contract_issued)
+ end
+
+ Enc->>Complete: TryCompleteOnEncounterClear
+ Complete->>Router: TryDeliverContractCompletion
+ alt partial apply then revert
+ Router-->>Complete: deny (hook: reward_anomaly in bundle)
+ else delivery ok
+ Router-->>Complete: success (hook: reward_delivery NEO-130)
+ Complete->>Store: TryMarkComplete
+ Complete-->>Complete: append outcome (hook: contract_complete)
+ end
+```
+
+## 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/Contracts/ContractGeneratorOperations.cs` | Add `contract_issued` hook after successful create; `reward_anomaly` stub on economy validation deny |
+| `server/NeonSprawl.Server/Game/Contracts/ContractCompletionOperations.cs` | Verify/extend `contract_complete` hook block to NEO-121 style |
+| `server/NeonSprawl.Server/Game/Rewards/RewardRouterOperations.cs` | Add `reward_anomaly` stub on private `CompensatingRevertAll` (covers all rollback call sites in `TryDeliverBundle`) |
+| `server/README.md` | Add **Contract telemetry hooks (NEO-152)** subsection with event table + NEO-130 cross-link |
+| `docs/decomposition/modules/E7_M4_ContractMissionGenerator.md` | Status line: E7M4-09 NEO-152 telemetry hooks landed |
+| `docs/decomposition/modules/documentation_and_implementation_alignment.md` | E7.M4 row: E7M4-09 hook sites |
+| `docs/plans/E7M4-pre-production-backlog.md` | E7M4-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 contract generator/completion/API tests unchanged). |
+
+## Open questions / risks
+
+| Question / risk | Agent recommendation | Status |
+|-----------------|----------------------|--------|
+| `reward_anomaly` in shared `TryDeliverBundle` affects quest path docs | **Single stub** at rollback sites with **`SourceKind`** filter note — avoid duplicate contract-only copies | `adopted` |
+| Partial `contract_complete` comment from NEO-149 | **Extend in place** — do not relocate hook to router or encounter layer | `adopted` |
+| Economy deny hook vs validator module | **Anchor at orchestrator** (`TryIssue`) when validation fails — not inside `ContractEconomyValidation` | `adopted` |
+| Duplicate `reward_delivery` for contracts | **No** — NEO-130 hook at `TryRecord` already covers contract path via `SourceKind` | `adopted` |
diff --git a/docs/reviews/2026-06-28-NEO-152.md b/docs/reviews/2026-06-28-NEO-152.md
new file mode 100644
index 0000000..f09efe6
--- /dev/null
+++ b/docs/reviews/2026-06-28-NEO-152.md
@@ -0,0 +1,58 @@
+# Code review — NEO-152 contract telemetry hook sites (comment-only)
+
+**Date:** 2026-06-28
+**Scope:** Branch `NEO-152-e7m4-09-contract-telemetry-hook-sites-comment-only` — commits `6e5ea78`..`0d089a6` (2 commits)
+**Base:** `main`
+**Issue:** [NEO-152](https://linear.app/neon-sprawl/issue/NEO-152) — E7M4-09 contract telemetry hook sites (comment-only)
+
+**Follow-up:** Review suggestions addressed in docs-only follow-up commit.
+
+## Verdict
+
+**Approve with nits**
+
+## Summary
+
+This branch anchors future E9.M1 catalog events on the server-authoritative contract path with comment-only hook sites and no runtime behavior change. **`contract_issued`** and economy-deny **`reward_anomaly`** land in **`ContractGeneratorOperations.TryIssue`**; **`contract_complete`** extends the NEO-149 partial block to full NEO-121 style; shared-router **`reward_anomaly`** documents partial-apply rollback on **`CompensatingRevertAll`**, and the NEO-130 **`reward_delivery`** comment is updated for contract **`SourceKind`**.
+
+Hook placement follows NEO-121/130/141 precedents (engine-only, **`TODO(E9.M1)`**, no API-layer duplicates). Docs (implementation plan, README subsection, E7.M4 module, alignment register, E7M4 backlog) are updated. Full suite passes (**933** tests). Risk is negligible — comments only; no wire or ingest change.
+
+## Documentation checked
+
+| Document | Result |
+|----------|--------|
+| `docs/plans/NEO-152-implementation-plan.md` | **Matches** — AC checklist complete; shipped reconciliation aligns with diff; kickoff decisions (engine-only, no duplicate `reward_delivery`, economy deny at orchestrator) reflected in code |
+| `docs/plans/E7M4-pre-production-backlog.md` | **Matches** — E7M4-09 AC checked; landed note + README/plan links |
+| `docs/decomposition/modules/E7_M4_ContractMissionGenerator.md` | **Matches** — NEO-152 telemetry hooks line added after NEO-151 HTTP |
+| `docs/decomposition/modules/documentation_and_implementation_alignment.md` | **Matches** — E7.M4 row includes E7M4-09 / NEO-152 body text + explicit [NEO-152 plan](../../plans/NEO-152-implementation-plan.md) in trailing refs |
+| `docs/decomposition/modules/module_dependency_register.md` | **Not re-read in diff** — no register change required; E7.M4 already tracked |
+| `server/README.md` | **Matches** — **Contract telemetry hooks (NEO-152)** subsection with event table + NEO-130 cross-link |
+| Client counterpart (NEO-153) | **N/A** — correctly out of scope; plan does not claim player-visible slice complete |
+
+Register/tracking table updates are included in this branch.
+
+## Blocking issues
+
+None.
+
+## Suggestions
+
+1. ~~**Add `NEO-152` plan link to alignment register footer** — The E7.M4 row body cites E7M4-09 / NEO-152, but the pipe-separated trailing refs list `[NEO-144]` … `[NEO-151]` then `label E7.M4 on NEO-144–NEO-154` without `[NEO-152 plan]`. Add for parity with adjacent landed stories.~~ **Done.** Added `[NEO-152 plan]` to alignment register trailing refs.
+
+2. ~~**Note method-level vs call-site rollback anchor in plan** — Plan §4 C2 and the files table say stub comments at **`CompensatingRevertAll` invocations** in **`TryDeliverBundle`**; implementation uses a **single block on `CompensatingRevertAll`** (four call sites). Functionally equivalent and DRY (similar to NEO-121 central **`Deny`**); optional one-line note in plan **Implementation reconciliation** that the adopted anchor is the private method.~~ **Done.** Plan reconciliation, §1 (shipped state), §4 C2, README table, and files table updated.
+
+## Nits
+
+- Nit: **`reward_anomaly`** at economy validation deny is semantically a lint/cap deny rather than a delivery rollback; naming matches backlog/NEO-150 deferral — no change needed, but E9.M1 wiring should treat **`reasonCode`** distinctly from router rollback anomalies.
+- ~~Nit: Plan **§1 Current state** table still describes pre-ship “no hook” state; **Implementation reconciliation** is authoritative — optional cleanup of §1 on merge to past tense.~~ **Done.** §1 renamed to **Shipped state (post-NEO-152)** with past-tense rows.
+
+## Verification
+
+```bash
+dotnet test NeonSprawl.sln
+```
+
+Manual (optional — comment-only):
+
+- Open `ContractGeneratorOperations.cs`, `ContractCompletionOperations.cs`, and `RewardRouterOperations.cs`; confirm **`TODO(E9.M1)`** markers, planned payload fields, and no new log/ingest calls.
+- Confirm **`ContractIssueApi`** / **`ContractListApi`** have no NEO-152 hooks (engine-only).
diff --git a/server/NeonSprawl.Server/Game/Contracts/ContractCompletionOperations.cs b/server/NeonSprawl.Server/Game/Contracts/ContractCompletionOperations.cs
index 6d150e5..069e48a 100644
--- a/server/NeonSprawl.Server/Game/Contracts/ContractCompletionOperations.cs
+++ b/server/NeonSprawl.Server/Game/Contracts/ContractCompletionOperations.cs
@@ -101,7 +101,10 @@ public static class ContractCompletionOperations
completedAt);
// --- Telemetry hook site (NEO-152): future E9.M1 catalog event `contract_complete` ---
// TODO(E9.M1): catalog emit — first-time completion only; idempotent replay returns below without hook.
- // Planned payload: playerId, contractInstanceId, templateId, encounterId, completedAt, idempotencyKey.
+ // Planned payload fields: playerId, contractInstanceId, templateId, encounterId, completedAt,
+ // idempotencyKey ({playerId}:contract_complete:{contractInstanceId}).
+ // No ingest or ILogger here (comments-only).
+
return Success(completedSnapshot, outcome);
}
diff --git a/server/NeonSprawl.Server/Game/Contracts/ContractGeneratorOperations.cs b/server/NeonSprawl.Server/Game/Contracts/ContractGeneratorOperations.cs
index 8919803..d41bac3 100644
--- a/server/NeonSprawl.Server/Game/Contracts/ContractGeneratorOperations.cs
+++ b/server/NeonSprawl.Server/Game/Contracts/ContractGeneratorOperations.cs
@@ -67,6 +67,11 @@ public static class ContractGeneratorOperations
factionRegistry,
out var economyReason))
{
+ // --- Telemetry hook site (NEO-152): future E9.M1 catalog event `reward_anomaly` ---
+ // TODO(E9.M1): catalog emit — economy validation deny at issue (defense-in-depth; startup load should prevent most).
+ // Planned payload fields: playerId, templateId, reasonCode (economy_cap_exceeded | invalid_reward_bundle).
+ // No ingest or ILogger here (comments-only).
+
return Deny(economyReason!);
}
@@ -87,6 +92,11 @@ public static class ContractGeneratorOperations
timeProvider.GetUtcNow(),
out var snapshot))
{
+ // --- Telemetry hook site (NEO-152): future E9.M1 catalog event `contract_issued` ---
+ // TODO(E9.M1): catalog emit — first-time active instance persist only (deny paths excluded).
+ // Planned payload fields: playerId, contractInstanceId, templateId, seedBucket, encounterTemplateId, issuedAt.
+ // No ingest or ILogger here (comments-only).
+
return Success(snapshot, template.EncounterTemplateId);
}
diff --git a/server/NeonSprawl.Server/Game/Rewards/RewardRouterOperations.cs b/server/NeonSprawl.Server/Game/Rewards/RewardRouterOperations.cs
index 9e259b3..8f05bc6 100644
--- a/server/NeonSprawl.Server/Game/Rewards/RewardRouterOperations.cs
+++ b/server/NeonSprawl.Server/Game/Rewards/RewardRouterOperations.cs
@@ -12,7 +12,8 @@ namespace NeonSprawl.Server.Game.Rewards;
/// Applies quest and contract completion grants idempotently (NEO-127, NEO-148).
/// Quest-state wiring: (NEO-128).
/// Reputation grants: (NEO-138).
-/// E9.M1 telemetry hook sites: (NEO-130).
+/// E9.M1 telemetry hook sites: (NEO-130);
+/// shared bundle rollback reward_anomaly (NEO-152).
///
public static class RewardRouterOperations
{
@@ -335,9 +336,9 @@ public static class RewardRouterOperations
if (deliveryStore.TryRecord(deliveryEvent))
{
// --- Telemetry hook site (NEO-130): future E9.M1 catalog event `reward_delivery` ---
- // TODO(E9.M1): catalog emit — once per first-time quest completion delivery (TryRecord success).
+ // TODO(E9.M1): catalog emit — once per first-time quest or contract completion delivery (TryRecord success).
// Not on TryGet idempotent replay, deny paths, or TryRecord race-loser rollback paths.
- // Planned payload fields: playerId, questId, deliveredAt, grantedItems (itemId, quantity),
+ // Planned payload fields: playerId, sourceKind, sourceId, deliveredAt, grantedItems (itemId, quantity),
// grantedSkillXp (skillId, amount), grantedReputation (factionId, amount), idempotencyKey.
// No ingest or ILogger here (comments-only).
@@ -371,6 +372,11 @@ public static class RewardRouterOperations
return Deny(RewardDeliveryReasonCodes.AlreadyDelivered);
}
+ // --- Telemetry hook site (NEO-152): future E9.M1 catalog event `reward_anomaly` (stub) ---
+ // TODO(E9.M1): catalog emit — partial grant apply then compensating revert (skill XP, reputation, TryRecord race).
+ // E9.M1 filters by DeliveryTarget.SourceKind (contract_completion vs quest_completion).
+ // Planned payload fields: playerId, sourceKind, sourceId, reasonCode, partialAppliedGrants summary.
+ // Not on early denies before any grant apply or idempotent TryGet replay. No ingest or ILogger (comments-only).
private static void CompensatingRevertAll(
string playerId,
DeliveryTarget target,
diff --git a/server/README.md b/server/README.md
index b91f723..c8c3d97 100644
--- a/server/README.md
+++ b/server/README.md
@@ -372,6 +372,19 @@ Unknown player ids return **404** (position gate). Bruno: `bruno/neon-sprawl-ser
Plan: [NEO-149 implementation plan](../../docs/plans/NEO-149-implementation-plan.md). Contract HTTP GET/issue: [Per-player contract list HTTP (NEO-151)](#per-player-contract-list-http-neo-151).
+### Contract telemetry hooks (NEO-152)
+
+Comment-only hook sites for future E9.M1 catalog events ([Epic 7 Slice 4](../../docs/decomposition/modules/E7_M4_ContractMissionGenerator.md#related-implementation-slices)). **`TODO(E9.M1)`** — no production ingest. **`ContractIssueApi`** and **`ContractListApi`** delegate to ops layers only (no duplicate API-layer hooks). Contract completion payouts emit **`reward_delivery`** via shared **`RewardRouterOperations.TryDeliverBundle`** — see [Reward telemetry hooks (NEO-130)](#reward-telemetry-hooks-neo-130) cross-link (`SourceKind = contract_completion`).
+
+| Event | Anchor | When |
+|-------|--------|------|
+| **`contract_issued`** | **`ContractGeneratorOperations.TryIssue`** | After successful **`TryCreateActive`** (active instance persisted). |
+| **`contract_complete`** | **`ContractCompletionOperations.TryCompleteOnEncounterClear`** | After successful deliver-then-mark (instance marked complete + outcome appended). |
+| **`reward_anomaly`** | **`ContractGeneratorOperations.TryIssue`** (economy deny); **`RewardRouterOperations.CompensatingRevertAll`** (via **`TryDeliverBundle`**) | Economy cap / bundle cross-ref deny at issue; compensating revert after partial delivery apply. |
+| **`reward_delivery`** *(cross-link)* | **`RewardRouterOperations.TryDeliverBundle`** | NEO-130 — first-time **`TryRecord`** success; contract path uses **`SourceKind = contract_completion`**. |
+
+Plan: [NEO-152 implementation plan](../../docs/plans/NEO-152-implementation-plan.md).
+
## Contract outcome store (NEO-146)
Append-only contract completion audit rows live in **`IContractOutcomeStore`**. Each **`ContractOutcomeRow`** captures instance id, delivery idempotency key **`{playerId}:contract_complete:{contractInstanceId}`**, grant snapshot summary (items, skill XP, reputation — same shapes as **`RewardDeliveryEvent`**), and **`recordedAt`**.