Merge pull request #194 from ViPro-Technologies/NEO-152-e7m4-09-contract-telemetry-hook-sites-comment-only
NEO-152: Contract telemetry hook sites (comment-only)main
commit
fe0d23b01c
|
|
@ -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 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
|
## Source anchors
|
||||||
|
|
||||||
- Master plan: [`neon_sprawl_vision.plan.md`](../../../neon_sprawl_vision.plan.md) — Epic 7.
|
- Master plan: [`neon_sprawl_vision.plan.md`](../../../neon_sprawl_vision.plan.md) — Epic 7.
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -307,7 +307,9 @@ Working backlog for **Epic 7 — Slice 4** ([contract generator](../decompositio
|
||||||
|
|
||||||
**Acceptance criteria**
|
**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).
|
**Client counterpart:** none (infrastructure-only).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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` |
|
||||||
|
|
@ -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).
|
||||||
|
|
@ -101,7 +101,10 @@ public static class ContractCompletionOperations
|
||||||
completedAt);
|
completedAt);
|
||||||
// --- Telemetry hook site (NEO-152): future E9.M1 catalog event `contract_complete` ---
|
// --- 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.
|
// 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);
|
return Success(completedSnapshot, outcome);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,11 @@ public static class ContractGeneratorOperations
|
||||||
factionRegistry,
|
factionRegistry,
|
||||||
out var economyReason))
|
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!);
|
return Deny(economyReason!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,6 +92,11 @@ public static class ContractGeneratorOperations
|
||||||
timeProvider.GetUtcNow(),
|
timeProvider.GetUtcNow(),
|
||||||
out var snapshot))
|
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);
|
return Success(snapshot, template.EncounterTemplateId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@ namespace NeonSprawl.Server.Game.Rewards;
|
||||||
/// Applies quest and contract <see cref="QuestRewardBundleRow"/> completion grants idempotently (NEO-127, NEO-148).
|
/// Applies quest and contract <see cref="QuestRewardBundleRow"/> completion grants idempotently (NEO-127, NEO-148).
|
||||||
/// Quest-state wiring: <see cref="QuestStateOperations"/> (NEO-128).
|
/// Quest-state wiring: <see cref="QuestStateOperations"/> (NEO-128).
|
||||||
/// Reputation grants: <see cref="ReputationOperations"/> (NEO-138).
|
/// Reputation grants: <see cref="ReputationOperations"/> (NEO-138).
|
||||||
/// E9.M1 telemetry hook sites: <see cref="TryDeliverQuestCompletion"/> (NEO-130).
|
/// E9.M1 telemetry hook sites: <see cref="TryDeliverQuestCompletion"/> (NEO-130);
|
||||||
|
/// shared bundle rollback <c>reward_anomaly</c> (NEO-152).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class RewardRouterOperations
|
public static class RewardRouterOperations
|
||||||
{
|
{
|
||||||
|
|
@ -335,9 +336,9 @@ public static class RewardRouterOperations
|
||||||
if (deliveryStore.TryRecord(deliveryEvent))
|
if (deliveryStore.TryRecord(deliveryEvent))
|
||||||
{
|
{
|
||||||
// --- Telemetry hook site (NEO-130): future E9.M1 catalog event `reward_delivery` ---
|
// --- 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.
|
// 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.
|
// grantedSkillXp (skillId, amount), grantedReputation (factionId, amount), idempotencyKey.
|
||||||
// No ingest or ILogger here (comments-only).
|
// No ingest or ILogger here (comments-only).
|
||||||
|
|
||||||
|
|
@ -371,6 +372,11 @@ public static class RewardRouterOperations
|
||||||
return Deny(RewardDeliveryReasonCodes.AlreadyDelivered);
|
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(
|
private static void CompensatingRevertAll(
|
||||||
string playerId,
|
string playerId,
|
||||||
DeliveryTarget target,
|
DeliveryTarget target,
|
||||||
|
|
|
||||||
|
|
@ -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).
|
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)
|
## 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`**.
|
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`**.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue