Final-review fixes for Plan 2 (fit-rewriter). Every failure mode below now
surfaces as FitFormatError so Plan 3 can classify invalid FIT input as a
non-retryable activity error (spec 10.4).
- Range-check numeric values against the field's declared size before
struct.pack, so an oversized serial number or a 1-byte product field
raises FitFormatError instead of leaking a raw struct.error.
- Reject zero-size field definitions during parsing. A zero-size
device_info field 0 read back as device_index == 0 via
int.from_bytes(b"", ...), which could have let a paired sensor be
rewritten as an Edge 1030 Plus (spec 10.2).
- Add DeviceFieldValue.is_creator so callers can tell the creator
device_info record from sensor records instead of silently keeping
whichever record appeared last.
- Implement the missing spec 10.4 post-patch step: read the patched
buffer back and verify file_id 1/2/8 and creator device_info 2/4/27
hold the target values. A field that could not be written (e.g. a
product_name field too small for the target string) now fails the whole
conversion rather than producing a silent partial patch. Verification
runs before the output is written, so a half-rewritten file never lands
on disk.
- Use the field's actual endianness in _read_field_value's fallback path.
- Add curated re-exports in app/fit/__init__.py for Plan 3.
- Document _iter_data_fields' caller invariant (validate the container
first; end_offset is not clamped).
- Extend the preservation fixture with a product_name string field so the
zero-filling string write path is covered by the byte-preservation
proof, and test convert_fit_device against a 12-byte header.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- C1: drop module-level app singleton in app/main.py so importing the
package no longer validates Settings or creates DATA_DIR; run uvicorn
with --factory in the Dockerfile. pytest now collects and passes with
no ambient env vars.
- I2: add missing app/auth, app/security, app/web __init__.py so
setuptools discovers all five packages.
- I3: resolve the Jinja2 template directory relative to __file__ instead
of the process CWD.
- I4: add .gitignore covering .env, data/, .venv/, caches and build
artifacts so example deployment secrets cannot be committed.
- I5: assert UserRepository.list_enabled() excludes disabled users.
- M6: encode both operands before hmac.compare_digest in validate_csrf so
a non-ASCII token yields 403 instead of an unhandled 500.
- M9: remove unused relationship / HealthState imports.
- M11: make session cookie https_only configurable via SESSION_HTTPS_ONLY
(default unchanged: false).
- M13: dispose SQLAlchemy engines in the db_session and client fixtures.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add an explicit non-empty check before encrypting user-submitted
email/password fields in the create and update user routes, so a
request that bypasses the HTML `required` attribute gets a clean
400 instead of an unhandled ValueError from CredentialCipher.encrypt
propagating as a 500. Applies to all four credential fields on
create, and to the two email fields on update (the password-blank-
means-keep-existing behavior on update is unchanged).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds full CRUD for sync users (create/list/detail/edit) behind admin
auth and CSRF protection. Passwords are encrypted at rest and never
decrypted into a template context; only emails may be decrypted for
display on the edit form. Blank password fields on edit preserve the
existing encrypted password.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>