All notable changes to :wpl_validator.

The format follows Keep a Changelog. This project adheres to Semantic Versioning.

[Unreleased]

[1.10.2] — 2026-07-07

Changed

  • Harden codegen tests: drop fragile nested mix run that broke the Hex publish job. The no-drift guarantee is already enforced by the CI vocab-codegen-no-diff job; the in-process assertions (module data == vendored JSON) remain.

[1.10.1] — 2026-07-07

Changed

  • Re-vendor data/goal-categories.json from wpl v1.9.1 (vocab version 1.1.0): adds recovery category. GOAL_CATEGORY_OFF_VOCAB no longer warns on "recovery".

[1.10.0] — 2026-07-07

Added

  • Soft-validation vocabulary warnings (Elixir parity of @gymbile/wpl-validator PR #7):
    • GOAL_CATEGORY_OFF_VOCAB (:warning) — warns for each plan goal whose category is not in the recommended data/goal-categories.json vocabulary AND is not "custom".
    • DIETARY_TAGS_OFF_VOCAB (:warning) — warns for each nutrition activity dietary_tags entry not in the recommended data/dietary-tags.json vocabulary. Activities without dietary_tags are silently skipped.
    • Both rules are pass-2 semantic warnings; they do NOT affect plan validity; now active end-to-end with dietary_tags field supported by the v1.9.0 schema.
    • valid/goal-general-fitness-and-dietary-tags.json conformance fixture exercises both.
  • Vendored vocabularies: priv/data/goal-categories.json and priv/data/dietary-tags.json (from gymbile/wpl@v1.9.0) with corresponding *-version.txt pins.
  • Generated data modules: WPL.Data.GoalCategories.ids/0 and WPL.Data.DietaryTags.ids/0 (compile-time lists via scripts/gen_goal_categories.exs / scripts/gen_dietary_tags.exs).

Changed

  • WPL v1.9.0 vendor sync: priv/schema/v1.schema.json and the entire priv/conformance/ tree (valid/, invalid/, README.md, error-codes.md) updated to match gymbile/wpl@v1.9.0 byte-for-byte. New conformance fixtures: valid/goal-general-fitness-and-dietary-tags.json, valid/personalization-forbid-exercise.json, valid/personalization-nested-compound.json, invalid/action-unknown-type.json.
  • DUPLICATE_ID scope format: meta scope field now uses the narrowest container (e.g. "day:day_1" for blocks/activities, "phase:phase_1" for weeks) matching the canonical conformance contract. Previously emitted the full path "phase:P/week:W/day:D".
  • Pass-1 OneOf expansion: tied branches (equal error count) now emit errors from all tied branches, ensuring the conformance check can match whichever branch the expected fixture targets. Nested OneOf failures surface as additionalProperties at the containing field path, matching ajv parity.
  • Personalization action schema/semantic split: Pass2 semantic validation (INVALID_PERSONALIZATION_RULE) now runs for actions that carry a scope field even when the v1.9.0 schema's action enums fail at schema level. Bare type-only actions without a scope field are caught by schema alone (SCHEMA_VIOLATION). This preserves both the legacy INVALID_PERSONALIZATION_RULE conformance contract and the new action-unknown-type SCHEMA_VIOLATION fixture.
  • Enforcement fixtures moved: priv/conformance/enforcement/ relocated to priv/enforcement/ so the schema drift-check loop does not compare them against upstream (they are not part of gymbile/wpl conformance).
  • CI step order fix: mix compile --warnings-as-errors now runs before mix test to avoid "redefining module" warnings on Elixir 1.15/1.16 where mix test and a subsequent mix compile in the same job could trigger spurious redefinition warnings via the BEAM module consolidation step.

[1.9.0] — 2026-06-18

Added

  • Matcher vocabulary SSOT: WPL.Enforce.MatcherVocab (qualifier_tokens/short_plurals) is generated from a vendored wpl/data/matcher-vocab.json; WPL.Enforce.Matcher sources its vocab from it; adds matcher-vocab drift-check. Matcher logic unchanged.

[1.8.0] — 2026-06-17

Added

  • Pass-3 enforcement engine: WPL.Enforce.enforce/3,4 evaluates personalization rules against a ClientContext and strips forbidden activities from a compiled plan. Exports: WPL.Enforce, WPL.Enforce.Matcher, WPL.Enforce.RuleEvaluator, WPL.Enforce.Cycle. Fail-closed diagnostics: UNKNOWN_CONDITION_FIELD, UNKNOWN_ACTION_TYPE.
  • Enforcement conformance fixtures (priv/conformance/enforcement/) — 6 cross-language fixtures shared with @gymbile/wpl-validator@1.8.0.
  • forbid_exercise accepted by WPL.Validator.Rules.InvalidPersonalizationRule.
  • in / not_in condition ops tested (already pass; schema sync completes support).
  • Strict catalog mode: validate(plan, require_catalog: true) emits :catalog_required instead of silently skipping entity resolution when no catalog is supplied.
  • :catalog_required added to WPL.Validator.Error.@type code.

Changed

  • Catalog ref resolution is now case-insensitive (lowercases both ref and catalog entries before comparing). Mirrors @gymbile/wpl-validator@1.8.0 hasRef behavior.
  • Vendored schema updated to WPL v1.7.0.

[1.6.6] — 2026-05-05

Changed

  • ACTIVITY_BLOCK_MISMATCH allowed-activity table relaxed for warmup and cooldownexercise is now allowed in both warmup and cooldown blocks. Mirrors @gymbile/wpl-validator@1.6.6.

Removed

  • Conformance fixture invalid/activity-block-mismatch-exercise-in-cooldown (no longer a violation).

[1.6.5] — 2026-05-04

Added

  • Pass-2 rule :activity_block_mismatch rejects activities whose type is not allowed in the parent block's type (e.g. exercise in a cooldown block). :activity_block_mismatch added to WPL.Validator.Error.code typespec. See conformance/error-codes.md for the full allowed-activity table.

[1.6.0] — 2026-05-04

Changed

  • Sync vendored schema + conformance suite from gymbile/wpl@v1.6.0 (was v1.5.0).

Added

  • Contraindication tightening. Optional severity (low | moderate | high) and action: "require_clearance".

  • Cardio interval consistency. CardioPrescription.intervals.work.duration / .rest.duration accept a full Duration object alongside bare seconds.
  • Cardio intensity slots. intensity.target gains typed slots (zone, min_bpm/max_bpm, min_watts/max_watts, value+unit for pace).
  • Resistance extras. Reps.amrap: bool, ExercisePrescription.to_failure: bool, Weight.metric enum (1RM | e1RM | training_max | daily_max).

  • Typed progress measurements. Checkpoint.measurements[] items accept a free string or a typed MeasurementSpec with MeasurementMetric enum and Questionnaire enum.
  • Recovery typing. RecoveryExercise gains modality, intensity_rpe, pnf block, and body_part.
  • 5 new valid conformance fixtures: contraindication-clearance, cardio-intervals-duration, amrap-to-failure, checkpoint-typed-measurements, recovery-pnf-smr.
  • 5 new invalid conformance fixtures: contraindication-bad-severity, contraindication-bad-action, checkpoint-bad-metric, recovery-bad-modality, weight-bad-metric.

Notes

All schema changes are additive; every plan valid under 1.5.0 continues to validate under 1.6.0.

[1.4.0] — 2026-05-03

Added

  • Pass-2 rule :cyclic_subplan. Detects sub-plan reference self-cycles (a SubPlanActivity whose sub_plan_ref equals the containing plan's id). Cross-plan cycles deferred pending a sub_plans resolution map in the validate API.
  • :cyclic_subplan added to WPL.Validator.Error.code typespec.

Changed

  • Sync vendored schema + conformance suite from gymbile/wpl@v1.5.0 (was v1.4.0).

Notes

99/99 tests pass.

[1.3.0] — 2026-05-03

Changed

  • Sync vendored schema + conformance suite from gymbile/wpl@v1.4.0 (was v1.3.0).

Notes

Schema v1.4.0 adds per-bodyweight scaling for macros/calories/load and documented controlled-vocabulary prefixes for telemetry sources and clinical contraindications. All additive. 41/41 conformance + full validator suite pass.

[1.2.0] — 2026-05-03

Fixed

  • Pass 1 now drills into the best-matching branch for oneOf schema failures, matching ajv's native behavior. Previously, an invalid inner field on (e.g.) an ExerciseActivity produced a generic oneOf error at the activity level instead of the specific (e.g. enum) error at the offending field. Surfaced by the round-3 bad-muscle-group conformance fixture; codified in error-codes.md.

Changed

  • Sync vendored schema + conformance suite from gymbile/wpl@v1.3.0 (was v1.2.0).

Notes

Schema v1.3.0 adds optional primary_muscles/secondary_muscles/movement_pattern on ExerciseActivity, plan-level athlete_thresholds, and intensity.zone_model on cardio. 94/94 tests pass.

[1.1.0] — 2026-05-03

Changed

  • Sync vendored schema + conformance suite from gymbile/wpl@v1.2.0 (was v1.1.1).

Notes

Schema v1.2.0 is purely additive: Phase.type enum, Week.is_deload boolean, and a structured Tempo shape (alongside the existing string form). No new validator rules; Pass 1 (schema validation) covers all three additions. Plans authored against v1.1.x continue to validate unchanged.

[1.0.0] — 2026-05-02

Added

  • Initial release of :wpl_validator. Sister implementation of @gymbile/wpl-validator (TypeScript) — same conformance fixtures, identical (code, path) results.
  • Pass 1: JSON Schema validation (Draft 2020-12 schema, validated via ex_json_schema with a $schema Draft-7 compatibility swap; the schema uses only Draft-7-compatible keywords).
  • Pass 2: semantic invariants — single tree-walk with rule behaviour. Rules: :duplicate_id (5 scopes), :empty_phases_for_type, :invalid_prescription, :invalid_personalization_rule (with nested CompoundCondition recursion), :invalid_points_rule, :phase_duration_mismatch (warning), :unresolved_ref (catalog-optional).
  • Public API: WPL.Validator.validate/2 returns a WPL.Validator.Result struct with structured WPL.Validator.Error entries (path, code atom, severity, meta).
  • Conformance suite vendored from gymbile/wpl@v1.1.1 — all 3 valid + 9 invalid fixtures pass.
  • Drift-check CI (weekly) against gymbile/wpl upstream.