NEO-112: enforce objective id uniqueness per quest in CI

Move obj_ids_seen to quest scope so duplicate objective ids are
rejected across steps, matching CT.M1 documentation and E7M1-04
HTTP projection expectations (Bugbot PR #151).
pull/151/head
VinPropane 2026-05-31 21:23:45 -04:00
parent 50956bac11
commit a4ed4abadb
2 changed files with 3 additions and 3 deletions

View File

@ -44,7 +44,7 @@ NEO-112 delivers the Epic 7 Slice 1 **content foundation**: three JSON Schemas (
- Nit: `quest-objective-def.schema.json` lists all kind-specific fields in `properties` plus `oneOf` branches, so schema validation may accept wrong-field-at-wrong-kind (e.g. `recipeId` on `encounter_complete`) if both are present — plan adopted this tradeoff; CI cross-ref gate still catches unknown ids. - Nit: `quest-objective-def.schema.json` lists all kind-specific fields in `properties` plus `oneOf` branches, so schema validation may accept wrong-field-at-wrong-kind (e.g. `recipeId` on `encounter_complete`) if both are present — plan adopted this tradeoff; CI cross-ref gate still catches unknown ids.
- Nit: Duplicate step/objective ids are enforced **within** a quest only, not globally across quests — acceptable for prototype; E7M1-04 HTTP projection should namespace by quest id. - ~~Nit: Duplicate step/objective ids are enforced **within** a quest only, not globally across quests — acceptable for prototype; E7M1-04 HTTP projection should namespace by quest id.~~ **Superseded (Bugbot):** objective duplicate check was incorrectly scoped per-step; fixed in `validate_content.py` to quest-level (step ids remain per-quest; objective ids now per-quest too). Cross-quest uniqueness still not enforced (acceptable for prototype).
- Nit: Branch bundles the full E7M1 backlog decomposition (~390 lines) with E7M1-01 implementation — large diff but intentional and documented. - Nit: Branch bundles the full E7M1 backlog decomposition (~390 lines) with E7M1-01 implementation — large diff but intentional and documented.

View File

@ -905,6 +905,7 @@ def _validate_quest_catalogs(
steps = row.get("steps") steps = row.get("steps")
if isinstance(steps, list): if isinstance(steps, list):
step_ids_seen: set[str] = set() step_ids_seen: set[str] = set()
obj_ids_seen: set[str] = set()
for si, step in enumerate(steps): for si, step in enumerate(steps):
if not isinstance(step, dict): if not isinstance(step, dict):
continue continue
@ -920,7 +921,6 @@ def _validate_quest_catalogs(
step_ids_seen.add(sid) step_ids_seen.add(sid)
objectives = step.get("objectives") objectives = step.get("objectives")
if isinstance(objectives, list): if isinstance(objectives, list):
obj_ids_seen: set[str] = set()
for oi, obj in enumerate(objectives): for oi, obj in enumerate(objectives):
if not isinstance(obj, dict): if not isinstance(obj, dict):
continue continue
@ -929,7 +929,7 @@ def _validate_quest_catalogs(
if oid in obj_ids_seen: if oid in obj_ids_seen:
print( print(
f"error: {rel} quests[{i}] steps[{si}] objectives[{oi}]: " f"error: {rel} quests[{i}] steps[{si}] objectives[{oi}]: "
f"duplicate objective id {oid!r}", f"duplicate objective id {oid!r} within quest",
file=sys.stderr, file=sys.stderr,
) )
errors += 1 errors += 1