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 runthat broke the Hex publish job. The no-drift guarantee is already enforced by the CIvocab-codegen-no-diffjob; the in-process assertions (module data == vendored JSON) remain.
[1.10.1] — 2026-07-07
Changed
- Re-vendor
data/goal-categories.jsonfrom wpl v1.9.1 (vocab version 1.1.0): addsrecoverycategory.GOAL_CATEGORY_OFF_VOCABno longer warns on"recovery".
[1.10.0] — 2026-07-07
Added
- Soft-validation vocabulary warnings (Elixir parity of
@gymbile/wpl-validatorPR #7):GOAL_CATEGORY_OFF_VOCAB(:warning) — warns for each plan goal whosecategoryis not in the recommendeddata/goal-categories.jsonvocabulary AND is not"custom".DIETARY_TAGS_OFF_VOCAB(:warning) — warns for eachnutritionactivitydietary_tagsentry not in the recommendeddata/dietary-tags.jsonvocabulary. Activities withoutdietary_tagsare silently skipped.- Both rules are pass-2 semantic warnings; they do NOT affect plan validity; now active
end-to-end with
dietary_tagsfield supported by the v1.9.0 schema. valid/goal-general-fitness-and-dietary-tags.jsonconformance fixture exercises both.
- Vendored vocabularies:
priv/data/goal-categories.jsonandpriv/data/dietary-tags.json(fromgymbile/wpl@v1.9.0) with corresponding*-version.txtpins. - Generated data modules:
WPL.Data.GoalCategories.ids/0andWPL.Data.DietaryTags.ids/0(compile-time lists viascripts/gen_goal_categories.exs/scripts/gen_dietary_tags.exs).
Changed
- WPL v1.9.0 vendor sync:
priv/schema/v1.schema.jsonand the entirepriv/conformance/tree (valid/, invalid/, README.md, error-codes.md) updated to matchgymbile/wpl@v1.9.0byte-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_IDscope format: metascopefield 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
additionalPropertiesat the containing field path, matching ajv parity. - Personalization action schema/semantic split: Pass2 semantic validation
(
INVALID_PERSONALIZATION_RULE) now runs for actions that carry ascopefield even when the v1.9.0 schema's action enums fail at schema level. Bare type-only actions without ascopefield are caught by schema alone (SCHEMA_VIOLATION). This preserves both the legacyINVALID_PERSONALIZATION_RULEconformance contract and the newaction-unknown-typeSCHEMA_VIOLATIONfixture. - Enforcement fixtures moved:
priv/conformance/enforcement/relocated topriv/enforcement/so the schema drift-check loop does not compare them against upstream (they are not part ofgymbile/wplconformance). - CI step order fix:
mix compile --warnings-as-errorsnow runs beforemix testto avoid "redefining module" warnings on Elixir 1.15/1.16 wheremix testand a subsequentmix compilein 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 vendoredwpl/data/matcher-vocab.json;WPL.Enforce.Matchersources 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,4evaluates personalization rules against aClientContextand 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_exerciseaccepted byWPL.Validator.Rules.InvalidPersonalizationRule.in/not_incondition ops tested (already pass; schema sync completes support).- Strict catalog mode:
validate(plan, require_catalog: true)emits:catalog_requiredinstead of silently skipping entity resolution when no catalog is supplied. :catalog_requiredadded toWPL.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.0hasRefbehavior. - 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 cooldown —
exerciseis now allowed in bothwarmupandcooldownblocks. 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_mismatchrejects activities whosetypeis not allowed in the parent block'stype(e.g.exercisein acooldownblock).:activity_block_mismatchadded toWPL.Validator.Error.codetypespec. Seeconformance/error-codes.mdfor the full allowed-activity table.
[1.6.0] — 2026-05-04
Changed
- Sync vendored schema + conformance suite from
gymbile/wpl@v1.6.0(wasv1.5.0).
Added
Contraindication tightening. Optional
severity(low | moderate | high) andaction: "require_clearance".- Cardio interval consistency.
CardioPrescription.intervals.work.duration/.rest.durationaccept a fullDurationobject alongside bare seconds. - Cardio intensity slots.
intensity.targetgains typed slots (zone,min_bpm/max_bpm,min_watts/max_watts,value+unitfor pace). Resistance extras.
Reps.amrap: bool,ExercisePrescription.to_failure: bool,Weight.metricenum (1RM | e1RM | training_max | daily_max).- Typed progress measurements.
Checkpoint.measurements[]items accept a free string or a typedMeasurementSpecwithMeasurementMetricenum andQuestionnaireenum. - Recovery typing.
RecoveryExercisegainsmodality,intensity_rpe,pnfblock, andbody_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 (aSubPlanActivitywhosesub_plan_refequals the containing plan'sid). Cross-plan cycles deferred pending asub_plansresolution map in the validate API. :cyclic_subplanadded toWPL.Validator.Error.codetypespec.
Changed
- Sync vendored schema + conformance suite from
gymbile/wpl@v1.5.0(wasv1.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(wasv1.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
oneOfschema failures, matching ajv's native behavior. Previously, an invalid inner field on (e.g.) anExerciseActivityproduced a genericoneOferror at the activity level instead of the specific (e.g.enum) error at the offending field. Surfaced by the round-3bad-muscle-groupconformance fixture; codified inerror-codes.md.
Changed
- Sync vendored schema + conformance suite from
gymbile/wpl@v1.3.0(wasv1.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(wasv1.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_schemawith a$schemaDraft-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/2returns aWPL.Validator.Resultstruct with structuredWPL.Validator.Errorentries (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/wplupstream.