7.5 KiB
7.5 KiB
NEO-34 — Implementation plan
Story reference
| Field | Value |
|---|---|
| Key | NEO-34 |
| Title | E2.M1: Server loads SkillDef catalog at startup (fail-fast) |
| Linear | https://linear.app/neon-sprawl/issue/NEO-34/e2m1-server-loads-skilldef-catalog-at-startup-fail-fast |
Kickoff clarifications
| Topic | Question | Answer |
|---|---|---|
| NEO-33 dependency / sequencing | Plan relative to NEO-33 merge? | NEO-33 merged — assume current content/skills/*.json + scripts/validate_content.py behavior is authoritative. |
| Runtime validation strategy (mirror Python vs subprocess vs defer to implementer) | C#, subprocess, or agent picks? | Agent default: in-process C# validation mirroring validate_content.py (JSON Schema Draft 2020-12 + same Slice-1 id/category gate). Rationale: no Python runtime on minimal server/CI images; fail-fast with clear paths in exceptions/logs; drift risk mitigated by parallel test fixtures and shared constants documented next to the Python script. |
Goal, scope, and out-of-scope
Goal: On host startup, load all *.json under the configured skills catalog directory (same layout as CI: top-level skills array per file), validate every row against content/schemas/skill-def.schema.json rules, enforce global duplicate id, apply the NEO-33 prototype Slice 1 gate (exact ids + category coverage), build an in-memory catalog, log skill count and resolved paths at Information (or Debug for noisy detail), and register the result in DI for NEO-35 (lookup service). Invalid/missing data prevents the host from listening.
In scope (from Linear):
- Configurable content / skills root (absolute path or path relative to the server’s content root / current working directory — documented in
server/README.md). - Fail-fast semantics aligned with
scripts/validate_content.pyand E2.M1 — SkillDefinitionRegistry. - Singleton (or equivalent) registration holding loaded definitions for follow-on stories.
Out of scope (from Linear):
- Public HTTP API for skills (NEO-36).
- XP / level engine (E2.M2).
Acceptance criteria checklist
- Integration or host-level test: valid fixture directory →
WebApplicationFactory(or equivalent) starts and catalog is resolvable from DI. - Integration or host-level test: invalid fixture (malformed JSON, schema violation, duplicate
id, or Slice-1 gate failure) → host startup fails with an actionable error (message includes file path and rule hint where practical). - At success: log includes skill count and catalog directory (Information or Debug per choice above).
- No silent acceptance of bad catalog at runtime (matches E2.M1 acceptance).
Technical approach
- Configuration: Add a
Content(orSkillCatalog) section, e.g.SkillsDirectory— optional string. If unset, resolve default by walking ancestors ofAppContext.BaseDirectoryuntil a directorycontent/skillsexists (supportsdotnet runfromserver/NeonSprawl.Serverwithout extra env). Document override for Docker/CI via env/config. - Validation library: Add
JsonSchema.Net(or equivalent with Draft 2020-12) toNeonSprawl.Server.csproj; loadskill-def.schema.jsononce. Replicatevalidate_content.pyloop: enumerate*.json, parse JSON, validate each object inskills, duplicate detection only for schema-clean rows, then run the same frozen id set and category coverage checks as Python’s_prototype_slice1_gate(share the three ids as a single named constant in C#; cross-reference the Python constant in a short comment). - Model: Minimal POCOs for load-time data (
Id,Category,DisplayName,AllowedXpSourceKinds, etc.) — enough for registry identity and logging; NEO-35 can refine public surface. - Registration:
AddSkillDefinitionCatalog(builder.Configuration)registers options +SkillDefinitionCatalogsingleton; eager creation aftervar app = builder.Build()viaapp.Services.GetRequiredService<SkillDefinitionCatalog>()(or a tinyIHostedServicethat only validates inStartAsyncif we need ordering with other startup) soapp.Run()never starts with a bad catalog. - Logging: Inject
ILoggerinto loader or log fromProgramafter successful resolve — include absolute resolved skills directory path and distinct skill count.
Files to add
| Path | Purpose |
|---|---|
server/NeonSprawl.Server/Game/Skills/ContentPathsOptions.cs (or SkillCatalogOptions.cs) |
Options type bound from configuration (SkillsDirectory, optional future SchemaPath). |
server/NeonSprawl.Server/Game/Skills/SkillCatalogServiceCollectionExtensions.cs |
AddSkillDefinitionCatalog + options validation. |
server/NeonSprawl.Server/Game/Skills/SkillDefinitionCatalog.cs |
Immutable in-memory result of load (e.g. dictionary by id + read-only list). |
server/NeonSprawl.Server/Game/Skills/SkillDefinitionCatalogLoader.cs |
File I/O + schema validation + Slice-1 gate; throws typed/InvalidOperationException with clear messages on failure. |
server/NeonSprawl.Server/Game/Skills/SkillDefRow.cs (or similar) |
DTO for one catalog row after validation. |
Files to modify
| Path | Rationale |
|---|---|
server/NeonSprawl.Server/NeonSprawl.Server.csproj |
Add JSON Schema NuGet package; optionally Content link to copy or embed schema path strategy (prefer reading schema from resolved repo content/schemas next to skills dir, or single ContentRoot option — align with loader). |
server/NeonSprawl.Server/Program.cs |
Register catalog; eager-resolve after build so startup fails before listen; no new public routes. |
server/NeonSprawl.Server/appsettings.json |
Optional Content section: SkillsDirectory / SkillDefSchemaPath as empty strings (auto-discovery); override via env or user secrets per server README. |
server/NeonSprawl.Server.Tests/InMemoryWebApplicationFactory.cs |
Set default SkillsDirectory / content overrides so existing tests use a valid temp or repo-relative catalog path (tests must not depend on undeclared cwd). |
server/README.md |
Document Content:SkillsDirectory, default discovery, and Docker/CI override. |
Tests
| Test file | What it covers |
|---|---|
server/NeonSprawl.Server.Tests/Game/Skills/SkillDefinitionCatalogLoaderTests.cs |
Unit: loader against temp directories — valid minimal catalog passes; cases for bad skills type, schema error, duplicate id across files, Slice-1 wrong ids / missing category coverage; assert exception messages mention file path. Host: InMemoryWebApplicationFactory + /health + DI catalog resolution; WebApplicationFactory negative case for invalid skills directory (merged here instead of a separate SkillCatalogStartupIntegrationTests.cs, per kickoff plan). |
Open questions / risks
- Schema path vs skills path: If only
SkillsDirectoryis configured, schema resolution should be deterministic (e.g.content/schemas/skill-def.schema.jsonunder the inferred repo root parent ofskills, or a second optional setting). Document the rule in code + README to avoid Docker surprises. - Test cwd: CI runs
dotnet testfromserver/or repo root — default discovery and test overrides must be verified in both if applicable.
None blocking beyond the above (handled in implementation).