neon-sprawl/docs/reviews/2026-05-24-NEO-61.md

63 lines
5.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Code review — NEO-61 world node instance depletion store
**Date:** 2026-05-24
**Scope:** Branch `NEO-61-e3m1-world-node-instance-depletion-store` · commits `869d411``6d2d3c0` vs `origin/main`
**Base:** `origin/main`
## Verdict
**Approve with nits**
## Summary
NEO-61 adds server-authoritative **remaining gathers** per world node instance: **`IResourceNodeInstanceStore`** (in-memory + Postgres **`V006`**) and **`ResourceNodeInstanceOperations.TryCommitSuccessfulGatherDecrement`** with registry-backed lazy init, atomic decrement, and stable **`node_depleted`** / **`unknown_node`** reason codes. The split-store pattern mirrors NEO-54; DI chains from **`AddResourceNodeCatalog`**, and **`InMemoryWebApplicationFactory`** forces the in-memory store in tests. Ten automated tests cover lazy init, depletion loop, unknown-node deny, store normalization/idempotency, and Postgres cross-factory persistence. Risk is low: no HTTP surface, no interact wiring, no inventory mutation — persistence + rules only, ready for NEO-62/NEO-63.
## Documentation checked
| Document | Result |
|----------|--------|
| [`docs/plans/NEO-61-implementation-plan.md`](../plans/NEO-61-implementation-plan.md) | **Matches** — split store + operations, lazy init, keep depleted rows at 0, reason codes, **`V006`**, DI chain, README, acceptance checklist complete. Minor: plan listed in-memory **concurrency tests** and an `out outcome` signature; implementation uses per-id locks (no concurrent test) and returns the outcome struct directly. |
| [`docs/plans/E3M1-prototype-backlog.md`](../plans/E3M1-prototype-backlog.md) | **Matches** — E3M1-05 acceptance items flipped with NEO-61 landed note. |
| [`docs/decomposition/modules/E3_M1_ResourceNodeAndGatherLoop.md`](../decomposition/modules/E3_M1_ResourceNodeAndGatherLoop.md) | **Matches** — NEO-61 depletion-store bullet under **Related implementation slices**; per-instance capacity, world-wide keying, **`node_depleted`** align with **Purpose** / **Responsibilities**. |
| [`docs/decomposition/modules/documentation_and_implementation_alignment.md`](../decomposition/modules/documentation_and_implementation_alignment.md) | **Matches** — E3.M1 row notes NEO-61 landed with README link; **Still planned** correctly lists gather engine / interact wiring. |
| [`docs/decomposition/modules/module_dependency_register.md`](../decomposition/modules/module_dependency_register.md) | **Matches** — E3.M1 row **In Progress** is correct; **E3.M1 note** footnote updated for NEO-61 (E3M1-05). |
| [`docs/decomposition/modules/client_server_authority.md`](../decomposition/modules/client_server_authority.md) | **Matches** — server-authoritative capacity; no client-trusted depletion state. |
| [`server/README.md`](../../server/README.md) | **Matches** — Resource node instance depletion subsection: lazy init, reason-code table, persistence split, NEO-63 deferral. |
Register/tracking: alignment table and **E3.M1 note** footnote updated for NEO-61.
## Blocking issues
None.
## Suggestions
1. ~~**AAA layout in stateful tests** — Two new test methods mix **Assert** into **Act**:~~ Done.
- ~~`ResourceNodeInstanceOperationsTests.TryCommitSuccessfulGatherDecrement_RepeatedUntilCapacity_ShouldReturnNodeDepletedWithoutGoingNegative` — assertions inside the decrement loop.~~
- ~~`ResourceNodeInstancePersistenceIntegrationTests.TryCommitSuccessfulGatherDecrement_WhenDepleted_ShouldPersistZeroRow` — assertions inside the final `using` Act block.~~
~~Move loop invariants / final expectations to a dedicated **Assert** section (collect last applied outcome during Act, assert after) per [csharp-style](../../.cursor/rules/csharp-style.md#unit-and-integration-tests-arrange-act-assert).~~
2. ~~**Dependency register footnote (optional)** — Extend **E3.M1 note** in [`module_dependency_register.md`](../decomposition/modules/module_dependency_register.md) to include **NEO-61** (E3M1-05 depletion store) so the register matches the alignment table — same follow-up as NEO-60 for NEO-57NEO-60.~~ Done.
3. **Concurrency test gap (optional)** — Plan §8 listed in-memory get/ensure/decrement **concurrency hygiene** tests; implementation uses per-id locks but has no parallel test. NEO-54 inventory store also omits concurrent tests, so non-blocking; add a lightweight `Parallel.For` smoke test if you want plan parity.
## Nits
- ~~Nit: **`NormalizeInteractableId`** lives as `internal static` on **`InMemoryResourceNodeInstanceStore`** but is called from **`PostgresResourceNodeInstanceStore`** and **`ResourceNodeInstanceOperations`** — works, but a small shared helper (e.g. `ResourceNodeInstanceIds.Normalize`) would reduce cross-type coupling if this grows.~~ Done.
- ~~Nit: **`TryEnsureInitialized` failure → `unknown_node`** in `ResourceNodeInstanceOperations` (lines 2631) is unreachable after a registry hit with non-negative **`maxGathers`**; a distinct internal reason or `Debug.Assert` would clarify intent vs catalog miss.~~ Done — throws **`InvalidOperationException`** (programmer error after validated catalog **`maxGathers`**).
- Nit: Postgres **`TryDecrementRemainingGathers`** opens a second connection via **`TryGetRemainingGathers`** on decrement miss to distinguish depleted-at-zero — correct behavior, slightly extra round-trip on deny path only.
## Verification
```bash
cd server
dotnet test NeonSprawl.Server.Tests/NeonSprawl.Server.Tests.csproj --filter "FullyQualifiedName~ResourceNodeInstance"
# Passed! Failed: 0, Passed: 10, Skipped: 0
dotnet test NeonSprawl.Server.Tests/NeonSprawl.Server.Tests.csproj
```
Postgres integration (when `ConnectionStrings__NeonSprawl` is set): `ResourceNodeInstancePersistenceIntegrationTests` — cross-factory read and depleted-row-at-zero persistence.