You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
Release v0.9.16 (2026-08-05)
Features & Improvements
Expose a stable package-level public API (FitFile, FitFileBuilder, exceptions, version constants)
via from fit_tool import ..., documented in the README. Existing deep imports remain supported. (#SHA-5)
Introduce a wire-layer MVP (fit_tool/wire) with raw header/record models and a
stateful decoder that keeps immutable definition snapshots. FitFile.from_bytes
decodes via the wire layer and projects to existing typed messages while keeping
public API behavior compatible. (#SHA-7)
Unify stream and in-memory FIT decoding on one state machine (FitDecoder
over the wire layer) so definition snapshots, developer-field registration,
and CRC handling stay aligned between FitFile.from_bytes and iter_*. (#SHA-8)
Add a composable validation API (validate_fit_file, FitFile.validate) with
WIRE / PROFILE / FILE_TYPE levels and report or raise modes. Builder strict=True now delegates to the same checks. (#SHA-9)
Split generated message construction into explicit create (MessageClass())
and decode (MessageClass.from_definition(...)) paths; MessageFactory uses the
definition factory. (#SHA-10)
Expand decode-time component / accumulator coverage to all Profile main-field
sources via a generated registry (fit_tool/profile/component_registry.py),
with nested expansion (e.g. compressed speed → enhanced speed) and modular
accumulator rollover. Subfield-gated components remain deferred to subfield work. (#SHA-15)
Fix Profile subfield resolution: match reference field values (AND across multi-ref maps), apply the active subfield's type/scale/offset/units, expand components declared on that subfield, and report ambiguous multi-matches as PROFILE validation errors (decode still uses the first match). (#SHA-16)
Retain unknown native field ids on known messages during decode as UnknownField (with raw_bytes for later PRESERVATION rewrite). Unedited to_bytes(preserve=True) remains bit-identical via wire_document. (#SHA-17)
Post-edit PRESERVATION: per-record dirty tracking (field mutations mark Record.dirty) so to_bytes(preserve=True) re-encodes only edited records and
copies source_bytes for the rest (unknown fields and other records survive).
Opt-in ConformanceLevel.PRESERVATION reports loss when unknown-field raw_bytes were cleared. Structural mark_dirty() / add / remove still force
a full projected re-encode. (#SHA-18)
Encode policies: explicit EncodeMode.PRESERVE / EncodeMode.CANONICAL on FitFile.to_bytes (legacy preserve= still works). Canonical rebuilds all
records with normalized sizes/CRCs; strict=True validates first and never
clamps invalid values. Policy matrix documented in README and design doc §6. (#SHA-19)
PROFILE validation supports selectable scopes (ProfileScope.CORE / DOMAIN / FULL). Default strict / DEFAULT_LEVELS remain CORE (developer fields +
ambiguous subfields). DOMAIN and FULL add data-driven native base-type and
closed-enum checks from a gen-exported field catalog
(fit_tool.profile.field_catalog) derived from the bundled Profile. FULL is
opt-in only via validate_fit_file(..., profile_scope=ProfileScope.FULL). (#SHA-20)
FILE_TYPE validation for Workout files: required workout / workout_step
messages and fields (num_valid_steps, step message_index / duration_type / target_type). Activity behavior unchanged; other file_id.type values (e.g. Course) still fail closed. SDK Workout fixtures
validate clean under FILE_TYPE. (#SHA-21)
FILE_TYPE validation for Course files: required course / lap / record / timer
events and fields (aligned with Garmin Course rules and real device exports).
Workout and other non-Activity/Course types still fail closed. (#SHA-22)
Protocol high-severity fixes: chained multi-segment FIT decode, trailing-byte rejection, compressed-timestamp reconstruction, known component expansion, and wire preservation encode for unedited buffer-decoded files. (#sha3)
Validate FIT header CRC when the header is larger than 12 bytes (CRC of the preceding header bytes in the final two bytes; gated by check_crc). (#sha3-medium)
Add bidirectional FIT interoperability coverage against the Garmin JavaScript
SDK at the bundled Profile version, plus corrected generated Profile scales. (#33)
Bug Fixes
Copy record-header local_id onto parsed definition and data messages, and fix FLOAT field encoding so fractional values are preserved and FIT invalid all-ones bit patterns round-trip as None. (#sha3)
Documentation
Move the detailed capability matrix and validation/encode notes from the
README into docs/CAPABILITY_BOUNDARY.md. The README now keeps a short
install, public API, and minimal examples section, with a link to that doc. (#readme-capability-docs)
Document the protocol fixture corpus and gap inventory under fit_tool/tests/data/README.md, with constructive golden tests for component
edges, unknown fields on known messages, and subfield-bearing workout steps. (#SHA-14)
Document the current capability boundary for readers: README Supported /
Partial / Not-supported matrix and a short “what this library still does not
claim” section now match the shipped code (PROFILE scopes CORE/DOMAIN/FULL,
Activity/Workout/Course FILE_TYPE validation, post-edit PRESERVE/CANONICAL
encode). The conformance design doc and residual checklist are re-synced so
public claims stay honest — full Garmin FIT / Profile conformance is still
not advertised until every Definition of Done item is met. (#SHA-23)
Document the maintainer release checklist and harden the tag-based PyPI/GitHub
Release workflow (version gate, v-prefix tags, changelog extraction, split
build/publish/github-release jobs). (#SHA-36)
Dependencies
Move profile-generation packages (openpyxl, inflection, plus jinja2) to an optional [gen] extra and drop unused bitstruct from the runtime install. (#sha6)
Upgrade the bundled Garmin FIT SDK Profile to 21.212.0. (#60)
Removals and Deprecations
Deprecate Record.from_bytes for full-file decode. Prefer FitFile.from_bytes / WireDecoder + compatibility; the method remains for isolated record pack/unpack tests. (#sha3-medium)
Miscellany
Raise typed Fit* errors on core parse/encode paths and improve CLI error exits;
align package metadata with Python 3.9+ and Ruff as a CI gate. (#sha3-hygiene)