fix(creation-model): migrate stray class_id/allowed_classes copies
The canon-log schema and NewGame code already migrated to race_id/ calling_id + allowed_callings, but two copies of the roster outside that schema were missed, and no parity test guards them: - docs/canon-log.md (the cross-boundary contract doc) still documented the OLD player shape and allowed_classes. Anyone building a player block from the doc would get a 422. - .claude/skills/world-building's schema reference still emitted allowed_classes with two dead calling ids (assassin, priest). Author a new origin with that skill and it fails origin.schema.json (additionalProperties: false, allowed_callings required) AND, if that ever loosened, silently allows zero callings at runtime. Also: - add schema tests rejecting a dead class_id field and an unknown calling_id (priest) - guard NewGame._validate's container types (spend/skills) so a malformed JSON round-trip (null spend, string skills) produces a front-loaded error list instead of a GDScript runtime crash - extend the no-mechanics-in-content test to db.races, not just db.callings - rewrite the roadmap's M4 bullet: the two contract migrations landed; only the creation screen and title->creation->shell flow remain client: 250/250. api: 74 passed, 2 skipped. content_build --check: clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QYa9u7Kdxv5gX4AnwWexy8
This commit is contained in:
@@ -126,8 +126,19 @@ static func _validate(origin: Dictionary, world: ContentDB, creation: Dictionary
|
||||
if calling_id not in allowed:
|
||||
errors.append("calling not allowed by origin: %s" % calling_id)
|
||||
|
||||
errors.append_array(_validate_spend(creation.get("spend", {})))
|
||||
if Races.exists(race_id) and Callings.exists(calling_id):
|
||||
# Guard container SHAPES before any statically-typed call below touches them.
|
||||
# A JSON round-trip can hand back {"spend": null} or {"skills": "stealth"} —
|
||||
# without this check, assigning that into a typed Dictionary/Array parameter
|
||||
# throws a GDScript runtime error INSIDE construct() instead of landing in the
|
||||
# front-loaded {"ok": false, "errors": [...]} the spec promises.
|
||||
if typeof(creation.get("spend", {})) != TYPE_DICTIONARY:
|
||||
errors.append("spend must be an object")
|
||||
else:
|
||||
errors.append_array(_validate_spend(creation.get("spend", {})))
|
||||
|
||||
if typeof(creation.get("skills", [])) != TYPE_ARRAY:
|
||||
errors.append("skills must be an array")
|
||||
elif Races.exists(race_id) and Callings.exists(calling_id):
|
||||
errors.append_array(_validate_skills(creation, race_id, calling_id))
|
||||
|
||||
return errors
|
||||
|
||||
Reference in New Issue
Block a user