# 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** | **`RewardRouterOperations.TryDeliverBundle`** at **`CompensatingRevertAll`** invocations (partial grant apply then revert); E9.M1 filters by **`SourceKind`** (`contract_completion` vs `quest_completion`) | Shared router core (NEO-148); rollback site is private bundle path, not HTTP | | **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; 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. Current state (precursor partial work) | Location | State today | NEO-152 action | |----------|-------------|----------------| | **`ContractGeneratorOperations.TryIssue`** | Class doc references NEO-152; **no** hook at success or economy deny | **Add** `contract_issued` + `reward_anomaly` comment blocks | | **`ContractCompletionOperations.TryCompleteOnEncounterClear`** | Partial **`contract_complete`** hook after **`TryMarkComplete`** (NEO-149) | **Verify/extend** to full NEO-121-style block (`TODO(E9.M1)` + payload fields) | | **`RewardRouterOperations.TryDeliverBundle`** | NEO-130 **`reward_delivery`** at **`TryRecord`** success; **`CompensatingRevertAll`** with no anomaly stub | **Add** `reward_anomaly` stub comment at rollback invocations (shared quest + contract path) | | **`server/README.md`** | Contract ops documented; **no** telemetry subsection | **Add** NEO-152 hook table; cross-link NEO-130 reward delivery for contract completions | ### 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:** **`RewardRouterOperations.TryDeliverBundle`**, at **`CompensatingRevertAll`** call sites after partial grant apply (skill XP / reputation / TryRecord race loser). - 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.TryDeliverBundle`** (partial-apply rollback) | 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 comments at `CompensatingRevertAll` invocations 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` |