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

181 lines
13 KiB
Markdown

# 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
- [ ] Hook sites documented in README; no behavior change beyond comments.
## 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` |