neon-sprawl/docs/plans/NEO-65-implementation-plan.md

130 lines
9.9 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.

# 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](../decomposition/modules/E3_M2_RefinementAndRecipeExecution.md) · Epic 3 Slice 3 · backlog **E3M2-01** |
| **Branch** | `NEO-65-e3m2-prototype-recipedef-catalog-schemas-ci` |
| **Upstream** | [NEO-50](https://linear.app/neon-sprawl/issue/NEO-50) — frozen six-item catalog; prototype skill trio includes **`refine`**. |
| **Pattern** | [NEO-50](https://linear.app/neon-sprawl/issue/NEO-50) / [NEO-57](https://linear.app/neon-sprawl/issue/NEO-57) — row JSON Schema + catalog envelope + `scripts/validate_content.py` Slice gate |
| **Blocks** | [NEO-66](https://linear.app/neon-sprawl/issue/NEO-66) — server recipe catalog load |
## Kickoff clarifications
**No blocking clarifications needed.** Linear AC, [E3M2-prototype-backlog — E3M2-01](E3M2-prototype-backlog.md#e3m2-01--prototype-recipedef-catalog--schemas--ci), [E3.M2 kickoff decisions](E3M2-prototype-backlog.md#kickoff-decisions-decomposition-defaults), and the [Slice 3 freeze table](../decomposition/modules/E3_M2_RefinementAndRecipeExecution.md#prototype-slice-3-freeze-e3m2-01) 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](E3M2-prototype-backlog.md#e3m2-01--prototype-recipedef-catalog--schemas--ci)):**
- `content/schemas/recipe-def.schema.json` and `content/schemas/recipe-io-row.schema.json`.
- `content/recipes/prototype_recipes.json` — eight frozen ids (freeze table below).
- `scripts/validate_content.py` — schema validation, duplicate `id`, exact eight-recipe allowlist, `recipeKind` coverage (**`process`** + **`make`**), cross-check every input/output **`itemId`** against item catalogs, every **`requiredSkillId`** against skill catalogs (prototype gate: all **`refine`**).
- Designer note in **E3.M2** module doc + `content/README.md` Slice 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
- [x] PR gate validates recipe JSON against schema.
- [x] Exactly eight prototype recipe ids; duplicate `id` fails CI.
- [x] Every input/output references a valid frozen item id; every recipe references **`refine`**.
- [x] Stable id list documented in module doc freeze box.
## Technical approach
1. **Row schema — I/O row (`recipe-io-row.schema.json`):** Draft 2020-12 object; `additionalProperties: false`. Required: `itemId`, `quantity`. `itemId` pattern `^[a-z][a-z0-9_]*$`. `quantity` integer ≥ 1.
2. **Row schema — `RecipeDef` (`recipe-def.schema.json`):** Draft 2020-12 object; `additionalProperties: false`. Required: `id`, `displayName`, `recipeKind`, `requiredSkillId`, `inputs`, `outputs`. `id` pattern `^[a-z][a-z0-9_]*$`. `recipeKind` enum: `process` \| `make`. `requiredSkillId` string (same id pattern). `inputs` / `outputs`: arrays with `minItems: 1`; each element validates against `recipe-io-row.schema.json` (via `$ref`). Optional stubs (unused in prototype rows): `requiredSkillLevel` (int ≥ 0), `stationTag` (string).
3. **Catalog file:** `content/recipes/prototype_recipes.json` with `schemaVersion: 1` and eight rows from the freeze table; placeholder `displayName` strings acceptable.
4. **Frozen content values (prototype Slice 3):**
| `RecipeDef.id` | `recipeKind` | inputs → outputs |
|----------------|--------------|------------------|
| `refine_scrap_standard` | `process` | 5× `scrap_metal_bulk` → 1× `refined_plate_stock` |
| `refine_scrap_efficient` | `process` | 10× `scrap_metal_bulk` → 2× `refined_plate_stock` |
| `make_field_stim_mk0` | `make` | 2× `refined_plate_stock` + 1× `scrap_metal_bulk` → 1× `field_stim_mk0` |
| `make_field_stim_batch` | `make` | 5× `refined_plate_stock` + 3× `scrap_metal_bulk` → 3× `field_stim_mk0` |
| `make_prototype_armor` | `make` | 8× `refined_plate_stock` + 5× `scrap_metal_bulk` → 1× `prototype_armor_shell` |
| `make_armor_quick` | `make` | 4× `refined_plate_stock` + 3× `scrap_metal_bulk` → 1× `prototype_armor_shell` |
| `make_survey_drone_kit` | `make` | 3× `refined_plate_stock` + 2× `scrap_metal_bulk` → 1× `survey_drone_kit` |
| `make_contract_token` | `make` | 2× `refined_plate_stock` + 1× `scrap_metal_bulk` → 1× `contract_handoff_token` |
All rows: `requiredSkillId: "refine"`. No `requiredSkillLevel` or `stationTag` on prototype rows.
5. **`validate_content.py`:**
- Add `RECIPE_SCHEMA`, `RECIPE_IO_ROW_SCHEMA`, `RECIPES_DIR`; discover `content/recipes/*_recipes.json`.
- `_validate_recipe_catalogs`: envelope `schemaVersion: 1`, top-level `recipes` array; validate each row against `recipe-def.schema.json`; track `seen_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 equal `PROTOTYPE_SLICE3_RECIPE_IDS`; `recipeKind` set must include **`process`** and **`make`**; every row `requiredSkillId` must be **`refine`** (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_IDS` in sync with [E3.M2 freeze table](../decomposition/modules/E3_M2_RefinementAndRecipeExecution.md#prototype-slice-3-freeze-e3m2-01) and future NEO-66 server loader.
6. **Docs:**
- `content/README.md` — ensure `recipes/` 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.
7. **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. | **done** — prep landed in prior commits; catalog + CI in this story. |
None blocking.