diff --git a/docs/decomposition/modules/E2_M1_SkillDefinitionRegistry.md b/docs/decomposition/modules/E2_M1_SkillDefinitionRegistry.md index 455af2c..e74c5ba 100644 --- a/docs/decomposition/modules/E2_M1_SkillDefinitionRegistry.md +++ b/docs/decomposition/modules/E2_M1_SkillDefinitionRegistry.md @@ -93,7 +93,7 @@ The **first shipped trio** under `content/skills/*.json` is **frozen** for downs - Every loaded `SkillDef` validates against [`skill-def.schema.json`](../../../content/schemas/skill-def.schema.json) in CI ([CT.M1](CT_M1_ContentValidationPipeline.md#ci-prototype) — [`scripts/validate_content.py`](../../../scripts/validate_content.py) in **PR gate**) or at server boot. - **Duplicate `id`** in a catalog fails validation. -- **Roster freeze for prototype:** when Slice 1 locks three skills, their **`id`** values are treated as **stable**—downstream content (recipes, quests) may reference them; changing an `id` requires a **migration** or new row. +- **Roster freeze for prototype:** Slice 1 locks **`salvage`**, **`refine`**, and **`intrusion`**; their **`id`** values are **stable**—downstream content (recipes, quests) may reference them; changing an `id` requires a **migration** or new row. - Document for designers: each new skill row **must** declare **`allowedXpSourceKinds`** so engineers know which systems may grant XP (gather/craft hooks vs E7.M2 mission rewards only, etc.). ## Module dependencies diff --git a/docs/reviews/2026-05-02-NEO-33.md b/docs/reviews/2026-05-02-NEO-33.md index 4236283..c874f5b 100644 --- a/docs/reviews/2026-05-02-NEO-33.md +++ b/docs/reviews/2026-05-02-NEO-33.md @@ -36,9 +36,9 @@ The branch implements NEO-33 as documented: a post-schema **prototype Slice 1** ## Suggestions -1. **`validate_content.py` — reserve `seen_ids` only for schema-clean rows** — Today, a string `id` is inserted into `seen_ids` even when `row_schema_errors > 0`, while `id_to_category` is gated on `row_schema_errors == 0`. If an author fixes a broken row and reuses an `id` that appeared only on an invalid earlier row, the validator can report **duplicate skill id** instead of focusing on the remaining schema errors. Consider only updating `seen_ids` when `row_schema_errors == 0` (mirror the `id_to_category` guard) so duplicate detection applies to validated rows only. +1. ~~**`validate_content.py` — reserve `seen_ids` only for schema-clean rows** — Today, a string `id` is inserted into `seen_ids` even when `row_schema_errors > 0`, while `id_to_category` is gated on `row_schema_errors == 0`. If an author fixes a broken row and reuses an `id` that appeared only on an invalid earlier row, the validator can report **duplicate skill id** instead of focusing on the remaining schema errors. Consider only updating `seen_ids` when `row_schema_errors == 0` (mirror the `id_to_category` guard) so duplicate detection applies to validated rows only.~~ **Done.** -2. **E2.M1 acceptance criteria (optional doc polish)** — The bullet that says “when Slice 1 locks three skills” could name **`salvage` / `refine` / `intrusion`** explicitly now that NEO-33 shipped, so the acceptance section matches the freeze subsection without reading both. +2. ~~**E2.M1 acceptance criteria (optional doc polish)** — The bullet that says “when Slice 1 locks three skills” could name **`salvage` / `refine` / `intrusion`** explicitly now that NEO-33 shipped, so the acceptance section matches the freeze subsection without reading both.~~ **Done.** ## Nits diff --git a/scripts/validate_content.py b/scripts/validate_content.py index 1e02744..3363cad 100644 --- a/scripts/validate_content.py +++ b/scripts/validate_content.py @@ -86,7 +86,9 @@ def main() -> int: row_schema_errors += 1 errors += 1 sid = row.get("id") - if isinstance(sid, str): + # Reserve seen_ids (and duplicate detection) only for schema-clean rows so + # fixing a broken row does not surface a spurious duplicate vs an earlier invalid row. + if isinstance(sid, str) and row_schema_errors == 0: prev = seen_ids.get(sid) if prev: print( @@ -96,10 +98,9 @@ def main() -> int: errors += 1 else: seen_ids[sid] = str(path.relative_to(REPO_ROOT)) - if row_schema_errors == 0: - cat = row.get("category") - if isinstance(cat, str): - id_to_category[sid] = cat + cat = row.get("category") + if isinstance(cat, str): + id_to_category[sid] = cat if errors: print(f"content validation failed with {errors} error(s)", file=sys.stderr)