72 lines
5.5 KiB
Markdown
72 lines
5.5 KiB
Markdown
# Code review — NEO-8 (PostgreSQL position persistence)
|
||
|
||
**Date:** 2026-03-30
|
||
|
||
**Scope:** Branch `NEO-8-persist-position-postgres`; Linear [NEO-8](https://linear.app/neon-sprawl/issue/NEO-8). Working tree includes **modified** and **untracked** files (e.g. `PostgresPositionStateStore.cs`, `server/db/`, Postgres test types are **untracked** at review time).
|
||
|
||
**Base:** `main` (comparison: `git diff main --stat` plus untracked NEO-8 assets).
|
||
|
||
---
|
||
|
||
## Verdict
|
||
|
||
**Approve with nits** — Persistence wiring matches the NEO-8 plan and authority docs; tests and CI cover the durable path. Decomposition **implementation snapshot** docs updated for NEO-8 (see **Author follow-up** below).
|
||
|
||
---
|
||
|
||
## Summary
|
||
|
||
This work adds a PostgreSQL-backed `IPositionStateStore`, a versioned DDL file under `server/db/migrations/`, configuration via `ConnectionStrings:NeonSprawl`, idempotent schema application once per process, and per-request idempotent dev-row seeding so behavior stays consistent with TRUNCATE-heavy tests. GitHub Actions gains a Postgres service and passes the connection string into `dotnet test`. In-memory API tests are isolated via `InMemoryWebApplicationFactory` + `ConfigureTestServices` so a global CI env var does not poison the in-memory suite. Overall risk is **low–medium**: prototype-grade lifecycle (no explicit `NpgsqlDataSource` disposal), runtime DDL from the app, and every request paying a seed `INSERT` (no-op when row exists).
|
||
|
||
The diff also includes **repo-wide C# brace style** updates in `.cursor/rules/csharp-style.md` and touched server files — acceptable but **mixed scope** vs NEO-8; consider separate commit or callout in PR.
|
||
|
||
---
|
||
|
||
## Documentation checked
|
||
|
||
| Document | Assessment |
|
||
|----------|------------|
|
||
| `docs/plans/NEO-8-implementation-plan.md` | **Matches** — columns not JSONB, `player_position`, `IPositionStateStore`, connection string toggle, CI service + env, integration tests (POST→GET, second factory, unknown player), README/env docs. **Partially matches** — plan listed optional `appsettings.Development.json`; implementation correctly leaves connection string unset by default (still within plan). |
|
||
| `docs/decomposition/modules/client_server_authority.md` | **Matches** — server remains authoritative for `PositionState`; persistence does not move truth to the client. |
|
||
| `docs/decomposition/modules/E1_M1_InputAndMovementRuntime.md` | **Matches (post-review update)** — Snapshot describes optional Postgres persistence + NEO-8; “Not yet” trimmed to prediction / full slice / telemetry. |
|
||
| `docs/decomposition/modules/documentation_and_implementation_alignment.md` | **Matches (post-review update)** — E1.M1 tracking row includes NEO-8 and Postgres-when-configured. |
|
||
| `docs/decomposition/modules/module_dependency_register.md` | **Matches** — E1.M1 remains **In Progress**; no contract version promotion required for this slice. |
|
||
| `docs/decomposition/modules/contracts.md` | **Matches** — HTTP JSON shapes unchanged; NEO-8 is durability, not a breaking wire change. |
|
||
| `server/README.md` | **Matches** — persistence, env vars, local Postgres test instructions. |
|
||
|
||
---
|
||
|
||
## Blocking issues
|
||
|
||
*None identified.*
|
||
|
||
---
|
||
|
||
## Suggestions
|
||
|
||
1. ~~**Docs alignment (recommended before merge):**~~ **Done** — `E1_M1_InputAndMovementRuntime.md` and `documentation_and_implementation_alignment.md` updated for NEO-8 / optional Postgres (planned follow-up; separate from **Nit 1** below).
|
||
2. ~~**Lifecycle:**~~ **Tracked:** [NEO-13](https://linear.app/neon-sprawl/issue/NEO-13) under epic [Tech Debt](https://linear.app/neon-sprawl/project/tech-debt-d2148715e875) — dispose `NpgsqlDataSource` on host shutdown (was: singleton never disposed; acceptable for prototype until addressed).
|
||
3. ~~**Naming clarity:**~~ **Done** — `EnsureSchema()` XML summary in `PostgresPositionStateStore.cs` now states the heavy path (read migration + DDL) runs at most once per process; later calls are the fast volatile-read path. No rename.
|
||
|
||
---
|
||
|
||
## Nits
|
||
|
||
- ~~**`.vscode/`** appears as untracked in the working tree~~ — **Resolved (author):** no longer listed as untracked; do not commit editor-local `.vscode` unless the team opts in.
|
||
- ~~**`EnsureDevPlayerRow` every call**~~ **Addressed:** dev row is seeded at PostgreSQL host startup (`PostgresDevPlayerSeedHostedService`); integration tests re-seed after `TRUNCATE` via `PostgresPositionBootstrap.SeedDevPlayer` + `IOptions<GamePositionOptions>` from the factory.
|
||
- ~~**Unicode normalization:**~~ **Deferred / accepted:** team expects **ASCII** player ids; `ToLowerInvariant()` vs in-memory `StringComparer.OrdinalIgnoreCase` parity for full Unicode is **not** a priority unless requirements change.
|
||
|
||
---
|
||
|
||
## Verification
|
||
|
||
Before merge, the author should run:
|
||
|
||
1. **No Postgres:** `dotnet test NeonSprawl.sln -c Release` — expect in-memory tests green; Postgres tests **skipped** if `ConnectionStrings__NeonSprawl` is unset.
|
||
2. **With Postgres:** `docker compose up -d` from repo root, then
|
||
`export ConnectionStrings__NeonSprawl='Host=localhost;Port=5432;Database=neon_sprawl;Username=neon_sprawl;Password=neon_sprawl_dev'`
|
||
and `dotnet test NeonSprawl.sln -c Release` — all tests including `PostgresPositionStateIntegrationTests` should pass.
|
||
3. **Manual AC (optional):** `dotnet run` with connection string set, `POST …/move`, restart server, `GET …/position` unchanged.
|
||
|
||
Ensure **all** NEO-8 files (including `server/db/` and new test types) are **staged and committed** — at review time much of the implementation was still untracked relative to `git status`.
|