NEO-65: kickoff implementation plan for RecipeDef catalog + CI
Lock content shape for eight frozen prototype recipes before server load, following NEO-50/NEO-57 schema + validate_content.py patterns.pull/99/head
parent
fa7bcce78b
commit
0556d5fa0f
|
|
@ -0,0 +1,129 @@
|
|||
# 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
|
||||
|
||||
- [ ] PR gate validates recipe JSON against schema.
|
||||
- [ ] Exactly eight prototype recipe ids; duplicate `id` fails 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
|
||||
|
||||
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. | **pending** |
|
||||
|
||||
None blocking.
|
||||
Loading…
Reference in New Issue