5.5 KiB
Code review — NEON-5 (PostgreSQL position persistence)
Date: 2026-03-30
Scope: Branch NEON-5-persist-position-postgres; Jira NEON-5. 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 NEON-5 assets).
Verdict
Approve with nits — Persistence wiring matches the NEON-5 plan and authority docs; tests and CI cover the durable path. Decomposition implementation snapshot docs updated for NEON-5 (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 NEON-5; consider separate commit or callout in PR.
Documentation checked
| Document | Assessment |
|---|---|
docs/plans/NEON-5-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 + NEON-5; “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 NEON-5 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; NEON-5 is durability, not a breaking wire change. |
server/README.md |
Matches — persistence, env vars, local Postgres test instructions. |
Blocking issues
None identified.
Suggestions
Docs alignment (recommended before merge):Done —E1_M1_InputAndMovementRuntime.mdanddocumentation_and_implementation_alignment.mdupdated for NEON-5 / optional Postgres (planned follow-up; separate from Nit 1 below).Lifecycle:Tracked: NEON-15 under epic NEON-13 — Tech Debt — disposeNpgsqlDataSourceon host shutdown (was: singleton never disposed; acceptable for prototype until addressed).Naming clarity:Done —EnsureSchema()XML summary inPostgresPositionStateStore.csnow states the heavy path (read migration + DDL) runs at most once per process; later calls are the fast volatile-read path. No rename.
Nits
— Resolved (author): no longer listed as untracked; do not commit editor-local.vscode/appears as untracked in the working tree.vscodeunless the team opts in.Addressed: dev row is seeded at PostgreSQL host startup (EnsureDevPlayerRowevery callPostgresDevPlayerSeedHostedService); integration tests re-seed afterTRUNCATEviaPostgresPositionBootstrap.SeedDevPlayer+IOptions<GamePositionOptions>from the factory.Unicode normalization:Deferred / accepted: team expects ASCII player ids;ToLowerInvariant()vs in-memoryStringComparer.OrdinalIgnoreCaseparity for full Unicode is not a priority unless requirements change.
Verification
Before merge, the author should run:
- No Postgres:
dotnet test NeonSprawl.sln -c Release— expect in-memory tests green; Postgres tests skipped ifConnectionStrings__NeonSprawlis unset. - With Postgres:
docker compose up -dfrom repo root, then
export ConnectionStrings__NeonSprawl='Host=localhost;Port=5432;Database=neon_sprawl;Username=neon_sprawl;Password=neon_sprawl_dev'
anddotnet test NeonSprawl.sln -c Release— all tests includingPostgresPositionStateIntegrationTestsshould pass. - Manual AC (optional):
dotnet runwith connection string set,POST …/move, restart server,GET …/positionunchanged.
Ensure all NEON-5 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.