9.9 KiB
NEO-65 — Implementation plan
Story reference
| Field | Value |
|---|---|
| Key | NEO-65 |
| Title | E3.M2: Prototype RecipeDef catalog + schemas + CI |
| Linear | https://linear.app/neon-sprawl/issue/NEO-65/e3m2-prototype-recipedef-catalog-schemas-ci |
| Module | E3.M2 — RefinementAndRecipeExecution · Epic 3 Slice 3 · backlog E3M2-01 |
| Branch | NEO-65-e3m2-prototype-recipedef-catalog-schemas-ci |
| Upstream | NEO-50 — frozen six-item catalog; prototype skill trio includes refine. |
| Pattern | NEO-50 / NEO-57 — row JSON Schema + catalog envelope + scripts/validate_content.py Slice gate |
| Blocks | NEO-66 — server recipe catalog load |
Kickoff clarifications
No blocking clarifications needed. Linear AC, E3M2-prototype-backlog — E3M2-01, E3.M2 kickoff decisions, and the Slice 3 freeze table fully specify recipe count, ids, I/O quantities, recipeKind, and requiredSkillId. Schema shape follows NEO-50 / NEO-57 precedent:
| Topic | Agent recommendation | Status |
|---|---|---|
| Catalog envelope | { "schemaVersion": 1, "recipes": [ … ] } in content/recipes/prototype_recipes.json (mirror items / resource-nodes). |
Adopted (NEO-50 precedent) |
displayName on RecipeDef |
Required — mirror ItemDef; supports E3M2-04 GET without schema churn. |
Adopted (NEO-50 / NEO-57 precedent) |
| Optional forward-compat stubs | Optional requiredSkillLevel (int ≥ 0) and stationTag (string) on recipe-def.schema.json; omitted on all eight prototype rows (Slice 3 has no gates / stations). |
Adopted (NEO-50 stub pattern) |
requiredSkillLevel in prototype rows |
Omitted — decomposition default; no level gates in Slice 3. | Adopted (backlog kickoff table) |
Goal, scope, and out-of-scope
Goal: Lock content shape and CI validation for eight frozen RecipeDef rows before server load (NEO-66+).
In scope (from Linear + E3M2-01):
content/schemas/recipe-def.schema.jsonandcontent/schemas/recipe-io-row.schema.json.content/recipes/prototype_recipes.json— eight frozen ids (freeze table below).scripts/validate_content.py— schema validation, duplicateid, exact eight-recipe allowlist,recipeKindcoverage (process+make), cross-check every input/outputitemIdagainst item catalogs, everyrequiredSkillIdagainst skill catalogs (prototype gate: allrefine).- Designer note in E3.M2 module doc +
content/README.mdSlice 3 paragraph. - CT.M1 PR gate paragraph for recipe catalogs.
Out of scope (from Linear):
- Server loader, craft engine, HTTP (NEO-66+).
Acceptance criteria checklist
- PR gate validates recipe JSON against schema.
- Exactly eight prototype recipe ids; duplicate
idfails CI. - Every input/output references a valid frozen item id; every recipe references
refine. - Stable id list documented in module doc freeze box.
Technical approach
-
Row schema — I/O row (
recipe-io-row.schema.json): Draft 2020-12 object;additionalProperties: false. Required:itemId,quantity.itemIdpattern^[a-z][a-z0-9_]*$.quantityinteger ≥ 1. -
Row schema —
RecipeDef(recipe-def.schema.json): Draft 2020-12 object;additionalProperties: false. Required:id,displayName,recipeKind,requiredSkillId,inputs,outputs.idpattern^[a-z][a-z0-9_]*$.recipeKindenum:process|make.requiredSkillIdstring (same id pattern).inputs/outputs: arrays withminItems: 1; each element validates againstrecipe-io-row.schema.json(via$ref). Optional stubs (unused in prototype rows):requiredSkillLevel(int ≥ 0),stationTag(string). -
Catalog file:
content/recipes/prototype_recipes.jsonwithschemaVersion: 1and eight rows from the freeze table; placeholderdisplayNamestrings acceptable. -
Frozen content values (prototype Slice 3):
RecipeDef.idrecipeKindinputs → outputs refine_scrap_standardprocess5× scrap_metal_bulk→ 1×refined_plate_stockrefine_scrap_efficientprocess10× scrap_metal_bulk→ 2×refined_plate_stockmake_field_stim_mk0make2× refined_plate_stock+ 1×scrap_metal_bulk→ 1×field_stim_mk0make_field_stim_batchmake5× refined_plate_stock+ 3×scrap_metal_bulk→ 3×field_stim_mk0make_prototype_armormake8× refined_plate_stock+ 5×scrap_metal_bulk→ 1×prototype_armor_shellmake_armor_quickmake4× refined_plate_stock+ 3×scrap_metal_bulk→ 1×prototype_armor_shellmake_survey_drone_kitmake3× refined_plate_stock+ 2×scrap_metal_bulk→ 1×survey_drone_kitmake_contract_tokenmake2× refined_plate_stock+ 1×scrap_metal_bulk→ 1×contract_handoff_tokenAll rows:
requiredSkillId: "refine". NorequiredSkillLevelorstationTagon prototype rows. -
validate_content.py:- Add
RECIPE_SCHEMA,RECIPE_IO_ROW_SCHEMA,RECIPES_DIR; discovercontent/recipes/*_recipes.json. _validate_recipe_catalogs: envelopeschemaVersion: 1, top-levelrecipesarray; validate each row againstrecipe-def.schema.json; trackseen_ids; reject duplicates across files.- Post-schema cross-checks per row: every I/O
itemId∈ loaded item ids;requiredSkillId∈ loaded skill ids. _prototype_slice3_recipe_gate: ids must equalPROTOTYPE_SLICE3_RECIPE_IDS;recipeKindset must includeprocessandmake; every rowrequiredSkillIdmust berefine(prototype Slice 3 contract).- Run recipe validation after skill + item catalogs succeed (reuse
seen_ids/item_seen_ids). - Require recipe schema files in
main()startup checks; extend success summary with recipe file + id counts. - Keep
PROTOTYPE_SLICE3_RECIPE_IDSin sync with E3.M2 freeze table and future NEO-66 server loader.
- Add
-
Docs:
content/README.md— ensurerecipes/table row + Slice 3 freeze paragraph (may already exist on branch from decomposition prep).E3_M2_RefinementAndRecipeExecution.md— ensure freeze table + CI rules line (may already exist on branch).CT_M1_ContentValidationPipeline.md— recipe catalog PR gate paragraph.documentation_and_implementation_alignment.md— E3.M2 row → In Progress / NEO-65.E3M2-prototype-backlog.md— E3M2-01 checkboxes when implementation completes.
-
No server/C#/client changes in this story.
Files to add
| Path | Purpose |
|---|---|
content/schemas/recipe-io-row.schema.json |
JSON Schema for a single recipe input/output row. |
content/schemas/recipe-def.schema.json |
JSON Schema for a single RecipeDef row. |
content/recipes/prototype_recipes.json |
Prototype eight-recipe catalog (schemaVersion + recipes array). |
docs/plans/NEO-65-implementation-plan.md |
This plan. |
Files to modify
| Path | Rationale |
|---|---|
scripts/validate_content.py |
Load/validate recipe catalogs; duplicate id; Slice 3 eight-id + recipeKind + refine gates; I/O item + skill cross-checks; success summary. |
content/README.md |
Document content/recipes/, schema paths, Slice 3 freeze (active catalog). |
docs/decomposition/modules/E3_M2_RefinementAndRecipeExecution.md |
Confirm freeze table + CI enforcement note; link to plan when complete. |
docs/decomposition/modules/CT_M1_ContentValidationPipeline.md |
PR gate paragraph for recipe catalog validation. |
docs/decomposition/modules/documentation_and_implementation_alignment.md |
E3.M2 / NEO-65 status after catalog lands. |
docs/plans/E3M2-prototype-backlog.md |
E3M2-01 checkboxes + landed note when complete. |
Tests
| Item | Coverage |
|---|---|
| CI / PR gate | .github/workflows/pr-gate.yml already runs python scripts/validate_content.py — extended script is the regression signal. |
| Manual negative cases | From repo root after implementation: (1) duplicate recipe id → non-zero exit; (2) remove one frozen id → gate error; (3) unknown itemId in inputs → cross-check error; (4) requiredSkillId: "salvage" → Slice 3 gate error; (5) break schema (empty outputs) → schema error. |
| Manual happy path | pip install -r scripts/requirements-content.txt && python3 scripts/validate_content.py — reports recipe catalog + existing catalogs OK. |
No dedicated pytest module in-repo today (same as NEO-50 / NEO-57).
Open questions / risks
| Question / risk | Agent recommendation | Status |
|---|---|---|
| Catalog growth | Slice 3 gate requires exactly the eight frozen ids; expanding the roster needs a follow-up issue to change PROTOTYPE_SLICE3_RECIPE_IDS. |
adopted |
| C# vs Python drift | None until NEO-66; document “keep in sync” constant names in both places when server load lands. | adopted |
| Branch carries E3M2 decomposition prep | Commit decomposition doc deltas (freeze table, backlog, README) with implementation or as first doc batch on this branch. | pending |
None blocking.