Initialize EvolioHealth repository foundation

This commit is contained in:
2026-07-21 21:08:34 +02:00
commit 1f2cb70a83
15 changed files with 998 additions and 0 deletions
+45
View File
@@ -0,0 +1,45 @@
# EvolioHealth development instructions
These instructions apply to the entire repository.
## Source of truth
Before designing or implementing a change, read `docs/index.md`, the documents it routes to, and the existing code and tests. The specifications under `docs/` are authoritative. Do not silently contradict them. If a request conflicts with them, identify the conflict and update the specification only when explicitly authorized.
Use normative terms literally: **MUST** is mandatory, **SHOULD** requires a documented reason to deviate, and **MAY** is optional.
## Product boundaries
- **EvolioHealth Server** is a Go application that serves the business API and the bundled Flutter Web application.
- **EvolioHealth Companion** is an Android Flutter application.
- PocketBase is an internal persistence service. It MUST NOT be published on the host, exposed to the Internet, proxied, or called by a frontend.
- The Go server is the only public application service and the only client of PocketBase.
- `references.db` is a separate SQLite database, owned directly by the Go server and intended for replaceable reference data.
- Keep the stack lightweight. Do not introduce Redis, Kafka, Elasticsearch, Vault, S3, a message broker, or additional services without an explicit architectural decision.
## Security invariants
- Treat health records, measurements, photos, notes, exports, credentials, and device identifiers as sensitive.
- Enforce authorization server-side for every object. Never trust a client-supplied owner identifier.
- Administrators manage the instance but MUST NOT gain access to another user's health data or photos.
- Never log secrets, tokens, request bodies, health values, photo contents, or private notes.
- Stored personal data and private media MUST be encrypted by the application. Host-volume encryption remains the instance administrator's responsibility.
- Mobile secrets and device private keys MUST use Android Keystore. The local database MUST be encrypted.
- Password login requires TOTP. Passkeys are a passwordless alternative.
- Every security-sensitive change requires tests for cross-user isolation, failure paths, and audit events.
## Engineering rules
- Prefer simple, explicit, maintainable designs.
- Keep API clients independent from PocketBase schemas.
- Make synchronization offline-first, incremental, resumable, idempotent, and deletion-aware.
- Store canonical values independently from language and display units; retain original import values and units.
- Use UTC instants and retain the source timezone where local interpretation matters.
- Add or update tests for every functional change.
- Update relevant specifications when behavior changes.
- Documentation and code identifiers MUST be in English. The UI MUST support French and English.
- Build container images for `linux/amd64` and `linux/arm64`.
## Definition of done
A change is complete only when implementation, tests, authorization checks, migrations, error handling, documentation, and relevant security/privacy effects have been addressed. Never expose PocketBase as a shortcut.
+23
View File
@@ -0,0 +1,23 @@
# EvolioHealth specifications
EvolioHealth is a private, self-hosted platform for recording, synchronizing, and visualizing personal health, activity, body-measurement, and progress-photo data.
The product has two deliverables:
- **EvolioHealth Server**: Go business API, bundled Flutter Web application, internal PocketBase persistence, encrypted private media, and a replaceable SQLite reference database.
- **EvolioHealth Companion**: Android Flutter application with offline storage, Health Connect integration, and direct Bluetooth Low Energy support for the Xiaomi Mi Body Composition Scale 2.
This package is the initial product and architecture source of truth for implementation by Codex. Start with [the documentation index](docs/index.md).
## Fixed principles
- Privacy and strict user isolation are product requirements.
- PocketBase is never directly exposed.
- Deployment uses one `compose.yaml`; `APP_ENV=dev|prod` selects runtime behavior.
- Runtime containers are non-root, capability-free, and compatible with common Linux NAS/container hosts.
- Data ownership, export, correction, and deletion are first-class features.
- The initial mobile target is Android only.
## Status
This specification defines the intended first production architecture and staged delivery plan. Future exercise and nutrition catalogs are anticipated, but their complete user workflows are not required for the first usable release.
+17
View File
@@ -0,0 +1,17 @@
# ADR-001: Go application facade with private PocketBase
- Status: Accepted
## Decision
Run a public Go application container and a separate internal PocketBase container. Only the Go server is reachable through the reverse proxy. PocketBase has no host-published port and is attached only to an internal Docker network. Flutter clients call versioned Go business APIs exclusively.
The Go server accesses `references.db` directly but accesses PocketBase through its internal API, never by opening PocketBase's SQLite files.
## Rationale
This creates an explicit security boundary, hides generic collection APIs and the technical dashboard, centralizes validation/authorization/encryption, and decouples clients from persistence schema. It remains lightweight: the PocketBase process would consume resources whether embedded or separate, while container overhead is small.
## Consequences
The project must implement business endpoints and a service authentication mechanism. PocketBase convenience APIs cannot be exposed as shortcuts. Network topology and automated tests must continuously prove isolation.
+17
View File
@@ -0,0 +1,17 @@
# ADR-002: Application encryption and key lifecycle
- Status: Accepted
## Decision
The Go server encrypts sensitive stored records and all private media. A random master key is generated during first setup and persisted at `/config/master.key`; it is not supplied by environment variable. Purpose-separated subkeys are derived for record/media classes.
Administrators may download a backup copy after strong reauthentication. There is no key recovery. A voluntary UI action can replace the key using a crash-safe, verified re-encryption workflow. Password-encrypted logical backups remain portable across instances and master keys.
## Rationale
Application encryption protects data from casual volume/database disclosure while keeping deployment simple and enabling server-side charts, Web access, and operations. A file keeps the critical secret out of container environment inspection. Explicit replacement supports response to suspected exposure without making rotation a routine background risk.
## Consequences
Loss of the key loses encrypted data. Operators must secure `/config` and key copies. Implementing re-encryption and backups requires migration journals, authenticated encryption, failure testing, and careful plaintext handling.
+15
View File
@@ -0,0 +1,15 @@
# ADR-003: Offline-first synchronization
- Status: Accepted
## Decision
The Android Companion stores supported user data locally in encrypted SQLite and uses a transactional outbox plus incremental server pull. Synchronization is event-triggered and optionally periodic, not continuous. Every mutation is idempotent, versioned, resumable, and deletion-aware.
## Rationale
Health and measurement data must remain available without the home server or Internet. NAS instances may be temporarily unavailable, and aggressive continuous sync would waste battery and mobile data.
## Consequences
The system needs explicit conflict semantics, tombstones, source provenance, cursors, retry/error UX, and tests across multiple devices. Server state cannot simply overwrite local state using timestamps.
+92
View File
@@ -0,0 +1,92 @@
# Business API contract
## Rules
- Base path `/api/v1` and JSON unless streaming media/export.
- HTTPS only in production.
- Strict request/response schemas and bounded bodies.
- Stable opaque IDs, ISO-8601 UTC timestamps, explicit canonical units.
- Cursor pagination, not unbounded lists.
- `Idempotency-Key` for retriable creation/mutation and sync batches.
- Consistent error envelope: `code`, localized-safe `message`, `request_id`, optional field details; no internal stack data.
- Frontends never send PocketBase filters, collection names, admin tokens, or generic record operations.
## Initial endpoint families
```text
POST /auth/login
POST /auth/totp/verify
POST /auth/passkey/options
POST /auth/passkey/verify
POST /auth/refresh
POST /auth/logout
POST /auth/recover
GET /me
PATCH /me
DELETE /me
GET /me/sessions
DELETE /me/sessions/{id}
GET /me/devices
POST /me/devices/pairing
DELETE /me/devices/{id}
GET /me/passkeys
POST /me/passkeys
DELETE /me/passkeys/{id}
POST /sync/push
GET /sync/pull?cursor=...
GET /measurement-sessions
POST /measurement-sessions
GET /measurement-sessions/{id}
PATCH /measurement-sessions/{id}
DELETE /measurement-sessions/{id}
GET /workouts
GET /sleep
GET /heart-rate
GET /activity-summaries
POST /media/progress-photos
GET /media/{id}
DELETE /media/{id}
POST /avatar
DELETE /avatar
POST /exports
GET /exports/{id}
GET /exports/{id}/download
GET /references/version
GET /references/exercises
GET /references/foods
POST /admin/invitations
GET /admin/users
PATCH /admin/users/{id}/status
GET /admin/settings
PATCH /admin/settings
POST /admin/backups
POST /admin/restores
POST /admin/encryption-key/change
GET /admin/audit
```
This is a contract outline, not permission to implement all endpoints before domain models and OpenAPI schemas are reviewed.
## Authorization
User routes infer the principal and ownership. Admin routes authorize operational capabilities only. There is deliberately no admin endpoint for reading another user's health datasets or private media. Support actions revoke/reset access rather than impersonate.
## Media
Uploads use streaming multipart with pre-decode and post-decode limits. Downloads authorize before opening/decrypting, support safe bounded streaming/ranges only if encryption format permits, and emit private/no-store cache headers.
## Sync responses
Return per-operation status (`accepted`, `duplicate`, `conflict`, `invalid`, `forbidden`, `retryable`) and authoritative object version. One bad item does not ambiguously fail an entire batch; transactional group semantics must be explicit.
## OpenAPI
Maintain a checked-in OpenAPI document as executable API documentation once implementation starts. Generate clients only when generated code is reviewed and does not leak transport models into domain layers. Contract tests verify server and Flutter clients against examples and error cases.
+109
View File
@@ -0,0 +1,109 @@
# Architecture
## System context
```mermaid
flowchart LR
HC["Android Health Connect"] --> C["EvolioHealth Companion"]
S["Xiaomi scale via BLE"] --> C
C -->|HTTPS business API| RP["External reverse proxy"]
B["Browser"] -->|HTTPS| RP
RP --> G["EvolioHealth Server - Go"]
G -->|Private Docker network| PB["PocketBase"]
G --> R["references.db - read mostly"]
G --> M["Encrypted private media"]
G --> W["Bundled Flutter Web assets"]
```
The reverse proxy targets only the Go server. PocketBase has no host port, no public route, and no frontend credentials. The Go service performs authentication, authorization, input validation, encryption, sync semantics, media processing, auditing, and all business logic.
## Components
### EvolioHealth Server
A Go service in a minimal Alpine runtime image. It:
- serves `/`, the Flutter Web bundle, and `/api/v1`;
- owns the public authentication protocol and business API;
- communicates with PocketBase through its private API, never by opening `data.db`;
- directly opens `references.db` with prepared queries and read-only mode during normal operation;
- encrypts/decrypts sensitive fields and media;
- validates and re-encodes images as needed;
- performs backup, restore, reference updates, and voluntary master-key replacement;
- exposes non-sensitive liveness and readiness checks.
Use idiomatic standard-library Go where practical. Framework and dependency choices must be justified by maintenance and security value.
### PocketBase
PocketBase stores instance-owned mutable data: accounts, profiles, devices, sessions, sync metadata, measurements, workouts, encrypted health payloads, settings, and audit records. Its administrative UI and generic collection APIs are internal-only and not part of the supported operator workflow.
Defense in depth requires restrictive PocketBase collection rules and a dedicated service credential even though Docker networking isolates it. The service credential is provisioned without embedding it in client applications.
### Reference database
`/data/references/references.db` contains automatically replaceable, distributable data such as exercise definitions, muscle groups, equipment, foods, nutrients, templates, and dataset metadata. It contains no user data.
Updates use a signed manifest, HTTPS download, signature and digest verification, minimum-server-version check, SQLite integrity and schema validation, and atomic replacement with rollback to the last valid file. Arbitrary downloaded SQL MUST NOT be executed.
### Web and Companion
Flutter Web is bundled into and served by the Go image. It includes both user functions and the simplified administration experience. The Android Companion uses Flutter plus narrowly scoped Kotlin bridges where official Android APIs, Health Connect, WorkManager, Keystore, or BLE require them.
Clients depend only on versioned business contracts, never PocketBase record formats.
## Storage layout
Internal paths are fixed:
```text
/config/
master.key
instance.json
/data/
pocketbase/
references/references.db
media/avatars/
media/progress-photos/
backups/
temporary/
```
Operators choose Docker named volumes or bind mounts only by editing the `volumes` section of `compose.yaml`; storage-source environment variables are not supported.
## Networking
The application container joins a public-facing application network and an internal backend network. PocketBase joins only the internal backend network. It uses `expose`, never `ports`. The backend network is `internal: true`.
The server trusts forwarded headers only from explicitly trusted network ranges or proxy peers. In production it validates the effective HTTPS scheme and configured public origin. Caddy, Traefik, Nginx Proxy Manager, Synology reverse proxy, and conventional Nginx must work without requiring vendor-specific labels.
## Runtime and portability
Build in a Go builder image; run in minimal Alpine with CA certificates and `tzdata`. Publish a multi-architecture OCI manifest for `linux/amd64` and `linux/arm64`. Target Docker/OCI-compatible Linux hosts including OpenMediaVault, Synology Container Manager, Unraid, TrueNAS SCALE, Portainer, and standard Docker Compose. TrueNAS CORE/FreeBSD is not a target.
Both containers run as `${PUID:-1000}:${PGID:-1000}`, without privileged mode, Docker socket, host networking, host PID namespace, devices, or extra capabilities. Drop all capabilities, set `no-new-privileges`, prefer a read-only root filesystem, and use tmpfs for `/tmp`. Only required mounted paths are writable.
## Configuration boundary
Keep environment configuration minimal:
```env
APP_ENV=prod
APP_URL=https://example.invalid
TZ=Europe/Paris
PUID=1000
PGID=1000
```
The internal app port is fixed at 8080 and PocketBase at 8090. Host mapping belongs in Compose. SMTP, session duration, audit retention, photo policy, and functional settings belong in encrypted application settings managed through setup/admin UI.
`APP_ENV=dev|prod` changes runtime behavior but never exposes PocketBase. Production hides internals, enforces origin and transport rules, and uses conservative logging. Development may enable local CORS and detailed server logs but must preserve authentication and isolation.
## Reliability
- Handle `SIGTERM`, drain requests, stop new writes, and close SQLite cleanly.
- Use bounded request bodies, concurrency, queues, and memory.
- Use coherent SQLite backup APIs, not raw copying of active databases.
- Make migrations ordered, repeatable where appropriate, tested from supported previous versions, and transactional.
- Keep media writes atomic: write temporary, validate, encrypt, fsync as appropriate, rename, then commit metadata; compensate on failure.
- No in-process job may make the API unusable indefinitely. Long operations expose progress and resumable/checkpointed state.
+100
View File
@@ -0,0 +1,100 @@
# Data model
## Principles
- PocketBase owns mutable instance/user records.
- `references.db` owns replaceable public/reference catalogs.
- Private media bytes live outside PocketBase; PocketBase stores encrypted metadata and opaque file references.
- All user-owned objects include immutable `owner_id` assigned by the server.
- Records support stable IDs, `created_at`, `updated_at`, optional deletion tombstones, sync version, and source provenance.
- Sensitive payloads are encrypted before PocketBase persistence; indexed/searchable non-sensitive fields are minimized.
## PocketBase logical collections
### Identity and instance
- `users`: email identity, role (`administrator|user`), status, locale, units, timezone, encrypted profile fields.
- `invitations`: hashed token, email/role, expiry, inviter, consumed/revoked state.
- `passkeys`: user, credential ID, public key, counters, transports, label, timestamps.
- `totp_credentials`: encrypted secret metadata and activation state.
- `recovery_codes`: hashed code and consumed timestamp.
- `devices`: user, public signing key, name, platform/model, pairing method, status, last activity/sync.
- `sessions`: user/device, hashed refresh-token family data, expiry, rotation/reuse state.
- `instance_settings`: encrypted SMTP/settings, photo policy, session/audit policy, setup state.
- `audit_events`: normalized security-only events.
### Sources and synchronization
- `data_sources`: Health Connect provider/application, BLE scale, manual, import, calculated.
- `sync_cursors`: per user/device/source cursor and checkpoint.
- `sync_operations`: idempotency key, mutation type, object, version, status.
- `sync_errors`: sanitized retryable/permanent failure data.
### Health and activity
- `body_measurement_sessions`: measured UTC instant, source timezone, source, encrypted notes/context.
- `body_measurement_values`: session, definition code, canonical value/unit, original value/unit, side, quality/origin (`measured|estimated|manual|imported|calculated`).
- `weight_records`: source identifiers, measured time, canonical weight, impedance when available, provenance.
- `body_composition_records`: linked source/weight, derived values, algorithm/version, estimated flag.
- `heart_rate_series`: start/end, source, encrypted compressed sample block rather than one row per sample.
- `sleep_sessions`: interval, source, summary; `sleep_stage_blocks` for compact stages.
- `daily_activity_summaries`: date/timezone/source, steps, distance, calories and other totals.
- `workout_sessions`: type, interval, duration, distance, calories, average/max heart rate, power, cadence, source identity.
- `workout_sample_blocks`: compact timestamp-offset samples for heart rate, speed, power, cadence, distance.
- `source_associations`: non-destructive linkage between Kinomap workouts and compatible Pixel/Fitbit heart-rate series.
### Media and sharing
- `media_objects`: owner, purpose, encrypted storage ID/metadata, MIME, dimensions, bytes, checksum, key/schema version.
- `progress_photos`: measurement session, media object, view type, ordering, encrypted notes.
- `avatars`: user, full and thumbnail media references.
- `shares`: owner, selected records/media, scope, expiry/revocation. Public-by-link sharing is not required in V1.
- `exports`: owner, requested scope, status, expiry, encrypted artifact reference.
### Future private features
- `training_programs`, `program_sessions`, `exercise_entries` for user-specific plans and performance.
- `nutrition_entries`, `meals`, `user_foods` for private nutrition data.
## Measurement definitions
Stable codes, never translated labels, identify values: `neck_circumference`, `shoulder_circumference`, `chest_circumference`, `waist_circumference`, `abdomen_circumference`, `hip_circumference`, and sided arm/forearm/thigh/calf codes. Definitions describe canonical dimension/unit and valid sides/ranges; UI localization is separate.
Session time is stored once on `body_measurement_sessions`. Values do not duplicate the effective date. Updating the session date updates its chart placement without rewriting value history.
## Provenance and deduplication
Source records retain:
```text
source_type
source_application
source_record_id
source_record_version
recorded_at
source_modified_at
content_hash
```
Prefer uniqueness on `(owner_id, source_type, source_application, source_record_id)`. When no stable source ID exists, derive a documented fingerprint from normalized type, time interval, values, and source. Hashes aid deduplication but do not replace authorization or collision-safe identifiers.
Imported original values and units are retained alongside one canonical conversion. A change of display unit performs no database rewrite.
## Reference database
`references.db` has an explicit schema version and dataset version. Initial domains may include:
- exercises, translations, muscles, exercise-muscle links, equipment;
- program templates and source/version metadata;
- foods, portions, nutrients, food-nutrient values and locales;
- educational/reference content whose updates are signed and redistributable.
Rows have stable source namespace, source ID, version, deprecation state, and localized text tables. User customizations never modify this database; they reference stable IDs or copy a snapshot into PocketBase where durable semantics are required.
## Deletion
Synchronization uses short-lived tombstones so deletions propagate. After the recovery window, purge records, derived associations, media, and per-user key material. Cascades are explicit and tested. Minimal audit events remain without personal health content for their configured retention.
## Migrations
PocketBase migrations and Go-owned schema migrations are versioned in source, tested on realistic fixtures, and applied before accepting traffic. Reference schema compatibility is checked before atomic activation. Encryption payloads include schema/key versions so record migrations can be resumed safely.
+98
View File
@@ -0,0 +1,98 @@
# Deployment and operations
## Compose contract
Ship one `compose.yaml`. Operators launch it with their normal manager; no Compose profiles or mandatory override files. `APP_ENV=dev|prod` selects application behavior.
Conceptual topology:
```yaml
services:
app:
image: ghcr.io/example/evoliohealth-server:VERSION
user: "${PUID:-1000}:${PGID:-1000}"
environment:
APP_ENV: "${APP_ENV:-prod}"
APP_URL: "${APP_URL}"
TZ: "${TZ:-UTC}"
ports:
- "8080:8080"
volumes:
- evoliohealth_config:/config
- evoliohealth_data:/data
networks: [frontend, backend]
security_opt: ["no-new-privileges:true"]
cap_drop: [ALL]
pocketbase:
image: ghcr.io/example/evoliohealth-pocketbase:VERSION
user: "${PUID:-1000}:${PGID:-1000}"
expose: ["8090"]
volumes:
- evoliohealth_data:/data
networks: [backend]
security_opt: ["no-new-privileges:true"]
cap_drop: [ALL]
networks:
frontend:
backend:
internal: true
volumes:
evoliohealth_config:
evoliohealth_data:
```
The final implementation may split PocketBase into a dedicated volume/subpath to avoid both containers having unnecessarily broad write access. Prefer least privilege. PocketBase MUST NOT have `ports`, join `frontend`, or be targeted by the reverse proxy.
## Named volumes and bind mounts
The distributed Compose uses named volumes by default. Documentation shows users how to replace only the Compose volume entries with fixed host paths, for example:
```yaml
volumes:
- /volume1/docker/evoliohealth/config:/config
- /volume1/docker/evoliohealth/data:/data
```
Do not use environment variables for volume sources. Internal paths remain `/config` and `/data`. Bind-mount owners/permissions must match PUID/PGID.
## Reverse proxy
Proxy only `app:8080` or the host-published application port. Enable WebSocket/streaming support if future endpoints require it. Preserve host and scheme headers. Use a dedicated hostname rather than a subpath. Provide concise examples for Caddy, Traefik, Nginx/Nginx Proxy Manager, and Synology without making any one mandatory.
PocketBase is inaccessible from the host and Internet. Diagnostic access, if ever needed, must be a documented temporary local tunnel or operator-only command, never a normal Compose option.
## Initial installation
1. Configure volumes/bind mounts, PUID/PGID, APP_URL, APP_ENV, and TZ.
2. Start the stack through the chosen container manager.
3. Visit `/setup` immediately.
4. Create the first administrator, configure defaults and SMTP/photo policy.
5. Download and separately secure `/config/master.key`.
6. Create and verify an encrypted backup.
The simple open initial setup is an accepted product decision. Documentation MUST warn operators not to expose an unconfigured instance longer than necessary. The server must close setup atomically after the first administrator is committed and reject concurrent attempts.
## Health checks
- Liveness states only that the process event loop is responsive.
- Readiness verifies configuration/key availability, PocketBase reachability/migrations, reference DB validity, and required storage writability.
- Responses reveal no versions, paths, user counts, or secrets to unauthenticated clients.
## Backup and restore operations
The Web administration UI creates password-encrypted global backups with progress. A restore runs preflight validation, creates a rollback point when feasible, enters maintenance mode, imports transactionally/staged, verifies, and resumes traffic. Never accept a raw database replacement uploaded through the browser.
Operators should copy backups off-host and test restoration. The master-key copy is stored separately. Backup passwords are never stored by EvolioHealth.
## Upgrade
Use immutable semantic-version tags. Before schema-changing upgrades, require/encourage a verified backup. Apply migrations once under a distributed/single-instance lock, retain compatible rollback artifacts where possible, and refuse downgrade when schemas are incompatible.
Reference updates are independent of application image upgrades and signed. Provide manual update/check controls; automatic scheduling may be configurable later.
## Resource posture
No hard user cap. Publish measured minimum/recommended CPU, RAM, and disk only after benchmarks. Bound SQLite caches, Go memory, photo processing concurrency, sync batch size, and backup jobs. The design must remain useful on modest NAS hardware and avoid idle background work.
+92
View File
@@ -0,0 +1,92 @@
# Development plan
## Delivery principles
Build vertical, testable slices. Do not implement all future domains at once. Security and backup foundations precede production health-data use. Each phase includes documentation, migrations, automated tests, and a runnable Compose deployment.
## Phase 0 - Repository foundation
- Establish `server/`, `web/`, `companion/`, `docs/`, `compose.yaml`, and build tooling.
- Pin Go, Flutter, PocketBase, Alpine, and CI tool versions.
- Add formatting, lint, unit test, secret scan, dependency/image scan, SBOM, and multi-arch build skeleton.
- Create OpenAPI baseline and ADR process.
Acceptance: clean checkout builds/tests without external proprietary services; containers run non-root; PocketBase is demonstrably unreachable from host/public network.
## Phase 1 - Secure instance foundation
- First-run setup and atomic first-admin creation.
- Master-key generation/storage/download and startup validation.
- Go facade, private PocketBase, migrations, settings, audit framework.
- Password+TOTP, passkeys, invitations, recovery, session rotation.
- User-owned device pairing/management and authorization test matrix.
- Basic Flutter Web shell and Android secure local storage.
Acceptance: cross-user tests pass; setup closes after completion; token replay is detected; no sensitive log output; loss/invalid key fails safely.
## Phase 2 - Offline data and manual measurements
- Encrypted Companion local database, outbox/inbox sync, conflict handling.
- Measurement-session CRUD, metric/imperial conversion, French/English UI.
- Silhouette measurement map and per-metric charts.
- Correction, deletion, tombstones, personal CSV export baseline.
- Consistency guidance.
Acceptance: airplane-mode CRUD survives restart and syncs idempotently; changing locale/units preserves canonical data; user A cannot infer B's sessions.
## Phase 3 - Photos and media
- Android/Web preprocess, EXIF removal, server validation/re-encode.
- Encrypted media format/storage, avatars, timeline and comparison.
- Admin future-photo policy and user choices.
- JPEG export and quotas.
Acceptance: malicious/oversized fixtures fail within resource bounds; stored files are not recognizable plaintext; no public media URL; policy changes do not alter existing photos.
## Phase 4 - Health Connect
- Permission UX, background/history support, change-token import.
- Heart-rate series, sleep, activities, workouts, weight/body composition.
- Stable IDs/versions for data EvolioHealth writes.
- Kinomap workout and Pixel/Fitbit heart-rate association.
Acceptance: repeated import/write produces no duplicates; updates/deletions propagate; provider source remains traceable; background work obeys user network/frequency settings.
## Phase 5 - Xiaomi scale
- Verified BLE protocol and fixtures for Mi Body Composition Scale 2.
- Stable/final reading detection and duplicate suppression.
- Weight/impedance storage, versioned estimates, optional Health Connect write.
Acceptance: incomplete readings never become final records; reconnect/rebroadcast does not duplicate; outputs distinguish measured from estimated.
## Phase 6 - Operations
- Password-encrypted `.hbackup`, restore and automated restore tests.
- Voluntary crash-safe master-key replacement.
- Reference DB signed update/rollback.
- Admin operational UI, audit retention, health diagnostics.
- Reverse-proxy/NAS documentation and benchmarks.
Acceptance: restore to a fresh instance with a different master key preserves authorized user data; interrupted key change recovers deterministically; tampered backups/reference DBs are rejected.
## Phase 7 - Future domains
Add strength-training and nutrition only after reference licensing, schemas, update provenance, UX, and resource benchmarks are explicitly specified. Do not copy wger or nutrition datasets without verifying compatible licenses and attribution obligations.
## Required test layers
- Go and Dart/Kotlin unit tests for domain, crypto wrappers, conversion, deduplication.
- Integration tests with real PocketBase and SQLite migrations.
- API contract and malformed-input tests.
- Authorization matrix and IDOR regression tests.
- Mobile offline/sync and WorkManager tests.
- Health Connect fake/provider integration tests.
- BLE packet fixture and state-machine tests.
- Image corpus and resource-exhaustion tests.
- End-to-end setup, invite, login, pairing, measurement, photo, export, delete, backup/restore.
- Multi-architecture image smoke tests.
## Release gate
No production tag until migrations/rollback notes, threat-model delta, dependency and image scans, SBOM/signature, backup restore test, authorization suite, French/English checks, and operator upgrade instructions pass.
+23
View File
@@ -0,0 +1,23 @@
# Documentation index
## Reading order
1. [Product specification](product-specification.md)
2. [Architecture](architecture.md)
3. [Security and privacy](security.md)
4. [Data model](data-model.md)
5. [Synchronization and integrations](synchronization.md)
6. [Media and measurements](media-and-measurements.md)
7. [Deployment and operations](deployment.md)
8. [API contract](api.md)
9. [Development plan](development-plan.md)
## Architecture decisions
- [ADR-001: Go application facade with private PocketBase](adr/ADR-001-private-pocketbase.md)
- [ADR-002: Application encryption and key lifecycle](adr/ADR-002-encryption.md)
- [ADR-003: Offline-first synchronization](adr/ADR-003-offline-sync.md)
## Authority
When documents overlap, the more specific document governs. Security invariants always take precedence over implementation convenience. A later explicit product decision should be recorded by changing the relevant document and, when architectural, adding or superseding an ADR.
+59
View File
@@ -0,0 +1,59 @@
# Media and measurements
## Photo lifecycle
```mermaid
flowchart LR
I["Camera or imported image"] --> O["Correct orientation"]
O --> C["Crop if required"]
C --> Z["Resize and JPEG re-encode"]
Z --> X["Remove metadata"]
X --> U["Authenticated upload"]
U --> V["Server decode and policy validation"]
V --> E["Authenticated encryption"]
E --> F["Opaque private file"]
F --> D["Encrypted metadata in PocketBase"]
```
Android and Web should transform progress photos before upload to conserve bandwidth. The browser may use `createImageBitmap` and Canvas/OffscreenCanvas; Android uses a maintained image pipeline. Client transformation is never trusted as server validation.
Server processing is bounded against image bombs. Reasonable policy violations may be normalized server-side; invalid or extreme inputs are rejected. Plaintext temporary files have restrictive permissions, short lifetimes, and cleanup on success/failure/startup.
## Photo policy
During setup the administrator chooses an initial profile. Later settings affect future uploads only. Existing media is not recompressed. The administrator can allow per-user choices up to the maximum or enforce one profile.
Each stored photo records actual width, height, size, applied profile, capture/session time, view type, checksum, and processing version. The original camera file is not retained by default.
## Access and display
Media URLs are short-lived authenticated application routes or authenticated streams, never public filesystem paths. Verify owner/share authorization on every request. Prevent proxy caching; client caches must be private and encrypted/ephemeral as appropriate.
Timeline sorting uses the linked measurement session, not upload time. Comparison pairs matching view types and supports first/latest and arbitrary selected dates. No automatic face/body recognition in V1.
## Avatars
Avatar processing is fixed: square user-controlled crop, 512x512 JPEG, approximately 85% quality, 128x128 thumbnail, EXIF removal, server validation, encryption, and authenticated instance-only visibility. Users may instead use initials or a built-in graphic.
## Parametric silhouette
Prefer lightweight vector/SVG or Canvas geometry over a large bitmap catalog. Provide male and female anatomical bases with multiple structural templates and measurement-driven control points. The rendering engine consumes canonical session values and produces a reproducible view for that session.
Requirements:
- no face detail or biometric recognition;
- same measurements produce the same geometry for a given renderer version;
- missing values degrade gracefully and are visibly marked;
- old sessions can be rendered with current or recorded renderer version;
- silhouette selection and morphology labels do not modify stored measurements or health calculations;
- display a concise disclaimer that it is an indicative visualization.
## Measurement quality
Each value records origin and confidence context. Direct scale weight and impedance are `measured`; manual tape entries are `manual`; imported provider values are `imported`; formula outputs are `estimated` or `calculated` with algorithm version.
The UI avoids false precision, especially for bioimpedance-derived values and tape measurements. Charts show points and trends without implying medical significance. Corrections update an existing logical record and sync version rather than creating accidental duplicates.
## Export
Web-only personal export is generated after recent authentication. Stream generation to avoid excessive memory. CSV uses stable English field codes plus localized human-readable headers where appropriate, explicit units and ISO timestamps. Photo filenames include date, view, and a collision-safe suffix. Exports expire quickly, are encrypted while staged, downloadable once or for a short window, and audited.
+120
View File
@@ -0,0 +1,120 @@
# Product specification
## Purpose
EvolioHealth gives individuals and families control over their health and fitness history on infrastructure they choose. It is a wellness and personal tracking product, not a medical device and not a source of diagnosis or treatment.
## Users and roles
There is no arbitrary user limit. Capacity depends on host resources.
Only two roles exist:
- `administrator`: configures the instance, invites users, manages operational settings, creates global backups, restores the instance, reviews security events, and can disable accounts or revoke compromised sessions/devices.
- `user`: manages their profile, devices, personal data, sharing, exports, and deletion.
Administrators MUST NOT browse, decrypt, export, impersonate, or otherwise access another user's health records, private notes, measurements, or progress photos. Operational backups may contain encrypted records but do not confer application-level access.
## Onboarding
An unconfigured instance exposes a simple `/setup` workflow. It creates the first administrator, default language, instance timezone, optional SMTP configuration, and initial photo policy. After completion, `/setup` MUST be permanently closed unless the instance is genuinely reset.
The setup generates a cryptographically random master key at `/config/master.key`. The final screen MUST state its location and allow the administrator to download a copy after reauthentication. Existing data without a valid key MUST cause startup to fail; the server MUST NOT silently generate a replacement.
Additional accounts are invitation-only, preferably by email. An invitation is random, single-use, revocable, short-lived, role-bound, and does not disclose account existence to unauthorized callers. On first login the user chooses French or English, metric or imperial display, timezone, password and TOTP, and may register passkeys.
The profile MAY include display name, avatar, date of birth, height, biological sex (`male` or `female`) when needed for supported calculations and model selection, and a self-declared morphology. Morphology is descriptive and MUST NOT be presented as a diagnosis or deterministic prediction of training response.
## Authentication and devices
Password authentication always requires TOTP. Passkeys/WebAuthn are a passwordless alternative and multiple passkeys may be registered, including credentials stored in Bitwarden-compatible managers.
Each user MUST manage their own sessions, passkeys, and paired devices from Web and mobile. A new mobile device can be added through:
1. a short-lived, one-time QR pairing code generated by an already authorized device;
2. email, password, and TOTP;
3. a passkey.
The phone creates a device key pair; its private key remains in Android Keystore and only the public key is registered. Pairing authorization is durable until revoked, while access tokens remain short-lived. Revocation invalidates refresh credentials and the device key immediately when it next contacts the server.
The default Web idle session is two hours. Mobile access tokens should last about ten minutes and use rotating, replay-detecting refresh credentials without prompting every two hours.
## Core data
The first product scope includes:
- heart rate and resting heart rate;
- sleep sessions and stages;
- daily activity summaries;
- exercise sessions, with Kinomap assumed to synchronize through Health Connect for subscribed users;
- weight and impedance from direct BLE access to Mi Body Composition Scale 2;
- derived body composition values, explicitly labeled as estimates;
- manual body-measurement sessions;
- dated progress photos and profile avatars;
- charts, history, source attribution, correction, deletion, and export.
Future scope includes strength-training programs, exercise catalogs, nutrition catalogs, and correlation insights. Replaceable reference datasets belong in `references.db`; private programs and user actions belong in PocketBase.
## Measurement sessions
Manual measurements are grouped in a dated session. The timestamp, timezone, notes, and context belong to the session; individual values link to it. A session may contain weight, neck, shoulders, chest, waist, abdomen, hips, left/right arms, forearms, thighs, calves, and associated photos.
Users can create, edit, correct, and delete sessions and their values. The UI provides:
- an anatomically mapped male or female silhouette;
- current values positioned on the silhouette;
- history navigation by session;
- per-metric evolution curves and date-range filters;
- comparison of two sessions;
- an indicative parametric silhouette derived from measurements where feasible.
The generated silhouette MUST be described as indicative, not a faithful 3D reconstruction or medical analysis. Initial morphology may influence only an initial visual template; measured values remain authoritative.
The product should teach consistent measurement conditions: ideally morning, fasted, after using the toilet, before exercise, with consistent posture, tape position and tension. Photo guidance should recommend consistent lighting, distance, framing, and posture. These are consistency tips, not medical advice, and may be dismissed.
## Photos
Progress photos form a chronological timeline and may be categorized as front, back, left, right, or custom. Users can compare the first and latest matching view, any two selected dates, side-by-side, and with an before/after slider.
Photos are resized and re-encoded before transfer when possible, stripped of EXIF metadata, validated again by the server, encrypted individually, and stored outside PocketBase. Existing photos are never recompressed when an administrator changes the future-photo policy.
The administrator sets the instance photo policy in the Web administration UI during setup and later. Profiles should include economical, standard, and high-quality choices (for example 1280/80%, 1600/85%, and 2048/90%). The administrator may allow users to choose up to the instance maximum or lock a single policy.
Avatars have a fixed policy: square crop, 512x512 JPEG at approximately 85% quality, a 128x128 thumbnail, no EXIF, encrypted private storage, and authenticated access only.
## Offline and synchronization UX
The Companion remains fully useful offline for already synchronized data. Synchronization occurs:
- at application start when connectivity is available;
- manually;
- after local entry, edit, deletion, scale reading, or photo capture when possible;
- periodically for Health Connect according to user choice: manual only, every 6, 12, or 24 hours;
- over Wi-Fi only or Wi-Fi/mobile data according to user choice.
Android scheduling is opportunistic; exact execution times are not promised. No push notifications are required.
## Language, units, and time
French and English are supported initially. Metric and imperial display are supported. Changing language or display system MUST NOT rewrite stored measurements.
Values use canonical storage units (for example millimetres and grams) while retaining original imported value and unit. Unknown or ambiguous units are rejected or require confirmation. Instants are stored in UTC and retain source timezone when needed. `TZ`, such as `Europe/Paris`, is the instance default, not a replacement for user or record timezone.
## Export, sharing, rectification, deletion
User exports are available only from the Web application. A personal export includes human-readable Excel/LibreOffice-compatible CSV files and photos as JPEG. CSV files use explicit ISO dates, unambiguous numeric formatting, named units, and UTF-8.
Users may generate a share image containing only explicitly selected charts, values, periods, text, or photos. Sharing uses the operating-system share sheet; EvolioHealth does not publish directly to social networks. Generated images contain no email, server URL, hidden metadata, or identity by default.
All personal records can be viewed, corrected, and deleted. Account deletion requires recent authentication, a clear irreversible-warning screen, an offer to export first, and explicit confirmation. It revokes devices and sessions, deletes records, media, shares, and user encryption material, and retains only a minimal non-health security event. Backup retention and delayed purge limitations MUST be explained.
## Explicit exclusions for the initial release
- iOS application;
- medical diagnosis or treatment recommendations;
- direct simultaneous BLE connection to rowing equipment while Kinomap is active;
- automatic social-network posting;
- push notifications;
- facial or body recognition;
- direct frontend access to PocketBase;
- mandatory certificate pinning for arbitrary self-hosted domains.
+105
View File
@@ -0,0 +1,105 @@
# Security and privacy
## Baseline
Design and test against OWASP ASVS Level 2, OWASP MASVS, and the OWASP API Security Top 10. Perform a documented threat-model review before the first public production release. Security must remain proportionate and lightweight, but simplicity never justifies weakening isolation or cryptography.
## Threat model
Protect against credential stuffing, phishing, token theft/replay, broken object authorization, malicious uploads, injection, CSRF, XSS, compromised clients, accidental logging, enumeration, insecure backups, reference-update tampering, and cross-user data leakage. A fully compromised host administrator or rooted phone can defeat application protections; the product must disclose these boundaries.
## Transport and browser security
- Production accepts only an HTTPS public URL and secure effective requests.
- Require TLS 1.2+, recommend TLS 1.3 and HSTS at the reverse proxy.
- Trust `Forwarded`/`X-Forwarded-*` only from configured proxy peers.
- Web sessions use `Secure`, `HttpOnly`, and appropriate `SameSite` cookies.
- Protect state-changing cookie requests with CSRF tokens and origin validation.
- Apply strict CSP, frame denial, `nosniff`, restrictive referrer policy, and private/no-store caching where sensitive.
- CORS allows only configured origins and never combines wildcard origins with credentials.
- Secrets and tokens never appear in URLs.
- Certificate pinning is not mandatory because instances use arbitrary domains and certificate authorities.
## Authentication
Passwords are hashed with a current, parameterized password-hashing function such as Argon2id. Password rules favor length, breached-password screening when available without leaking the password, and rate-limited verification over arbitrary composition rules.
Password login requires TOTP. Setup presents both an `otpauth` QR code and the textual secret for compatible managers, plus single-use recovery codes. TOTP secrets are encrypted at rest. Recovery codes are hashed.
Passkeys use WebAuthn/FIDO2, require origin/RP-ID correctness, user verification where available, and support multiple credentials. Passkey login replaces password plus TOTP; sensitive account changes still require recent authentication.
Authentication and recovery responses must resist account enumeration. Login, TOTP, passkey, invitation, recovery, and pairing endpoints use layered rate limits by IP, account, device, and instance with bounded progressive delay.
## Mobile device trust
Each Companion installation generates a non-exportable signing key in Android Keystore where supported. Pairing registers its public key and a human-readable device entry. QR bootstrap secrets are random, account-bound, single-use, and expire within 5-10 minutes.
Access tokens are short-lived and audience-bound. Refresh tokens are opaque, high-entropy, stored securely, rotated on every use, bound to the device/session, and support reuse detection that revokes the token family. Sensitive sync or account operations may require a signed envelope containing method, path, body digest, timestamp, and nonce. The server enforces a short clock window and one-time nonce.
Users can list, rename, and revoke their own devices and sessions and disconnect all others. Administrators can revoke compromised access but cannot use the mechanism to impersonate users.
Root/bootloader-compromise detection is best-effort and never blocks use. Show a dismissible warning explaining that privileged software may bypass local protections.
## Authorization and tenant isolation
Every object has an immutable server-controlled owner. The server derives owner scope from the authenticated principal and ignores/rejects client owner assignment except explicit administrative metadata operations. Use opaque identifiers, but never rely on their unpredictability.
Tests MUST prove that user A cannot read, infer existence, modify, delete, export, share, or fetch media belonging to B. Administrators have operational permissions, not health-data access. Use indistinguishable `404`/`403` behavior where it reduces enumeration without harming legitimate diagnostics.
Sharing is explicit, granular by selected item/dataset, visible, revocable, and never implied by household membership or administrator status.
## Application encryption
Sensitive PocketBase fields and all private media are encrypted by the Go server before storage. Use a well-reviewed authenticated-encryption construction such as XChaCha20-Poly1305 or AES-256-GCM with unique nonces. Bind ciphertext to stable context using additional authenticated data: record type, record ID, owner ID, schema/key version.
`/config/master.key` is a cryptographically random master key generated during initial setup. Derive purpose-separated subkeys with HKDF for records, photos, thumbnails, avatars, settings/secrets, and backup metadata. The key is never stored in PocketBase, logs, images, or environment variables.
The setup completion page and later security settings allow a strongly reauthenticated administrator to download a key copy. Every key download is audited. Loss of the key makes encrypted data unrecoverable.
### Voluntary key change
An administrator may explicitly press **Change encryption key**. Require recent password+TOTP or passkey authentication and a recent verified encrypted backup. Explain that all protected records and media will be decrypted and re-encrypted and service may enter maintenance mode.
The operation generates a new key, writes a durable migration journal, processes bounded batches, verifies every new ciphertext, and preserves the old key until all records, files, and metadata validate. Writes are blocked or safely dual-handled during the operation. Only after a complete verification is the new key atomically installed and the old key securely removed. On failure, rollback leaves the old key and original data usable. A crash must resume or rollback deterministically.
## Mobile storage
- Store device keys and session secrets using Android Keystore-backed secure storage.
- Encrypt the local SQLite database with a maintained solution.
- Disable unencrypted Android backup for sensitive files or define encrypted backup rules explicitly.
- Never place sensitive data in ordinary preferences, clipboard, notifications, logs, analytics, or crash attachments.
- Allow biometric/local-code app locking and optional screenshot/recent-screen protection.
- Remove temporary plaintext images promptly.
## Upload and media security
Treat uploads as hostile. Enforce authenticated ownership, byte limits before decoding, pixel limits, time/memory bounds, magic-byte and decode validation, supported-format allowlists, re-encoding, EXIF removal, random storage identifiers, quotas, and rate limits. Reject malformed, polyglot, oversized, decompression-bomb, or unexpected files. Media is delivered only through authorized API routes with private/no-store cache behavior and never through enumerable static paths.
## Input and API hardening
- Strict request schemas; reject unknown fields where compatibility does not require them.
- Prepared SQL for `references.db`; no client-provided SQL or raw PocketBase filters.
- Mandatory bounded pagination and limited filter/sort vocabularies.
- Bounded JSON nesting, batch sizes, decompression, query time, and response size.
- Idempotency keys for retriable mutations and sync batches.
- Generic production errors with request IDs; details remain in sanitized internal logs.
## Audit
Audit security events, not health content: successful/failed login, TOTP/passkey use, recovery, email/password changes, device/session creation and revocation, passkey/TOTP changes, invitation, role change, export, account deletion, backup/restore, master-key download/change, reference update, and sensitive configuration changes.
An audit entry includes UTC time, normalized event type/result/reason, opaque account/device IDs, request ID, source IP, and simplified user agent. It never contains credentials, token material, OTP values, health values, notes, photos, or request bodies.
Users see their own security history. Administrators see instance security events without health data. Default retention is six months, configurable from 1-12 months, with clear privacy disclosure and automatic purge. Do not geolocate IP addresses automatically.
## Backups
Global backups are created by administrators; personal exports are created by users. A global `.hbackup` archive is encrypted with a user-entered strong password. Derive an archive key using Argon2id with random salt and stored parameters, then encrypt/authenticate the entire logical archive with a modern AEAD. Filenames and sensitive manifest data are inside the encrypted envelope. The password and server master key are not included.
Backups contain consistent PocketBase data, encrypted/private media in portable logical form, configuration required for restore, schema/version metadata, checksums, and audit data according to policy. `references.db` may be omitted because it is redistributable. Restore validates format, authenticity, checksums, compatibility, quotas, and ownership before committing. Import on an instance with a different master key decrypts the archive and encrypts data under that instance key.
Backups must be restorable in automated tests. Explain that deleted data may persist until encrypted-backup retention expires.
## Supply chain and operations
Lock dependency versions, scan source/dependencies/images/secrets, publish an SBOM, sign release images, avoid floating `latest` in production examples, and keep release builds reproducible where practical. Security-sensitive authentication, authorization, crypto, upload, and backup changes require focused review and negative tests.
+83
View File
@@ -0,0 +1,83 @@
# Synchronization and integrations
## Offline-first model
The Companion writes user actions to an encrypted local SQLite database first, then appends an outbox operation in the same transaction. Reads render local state immediately. Server changes arrive through incremental pull and are merged into the local model.
Sync is incremental, batched, resumable, idempotent, deletion-aware, and safe across multiple devices. It never assumes continuous connectivity.
## Protocol
Each mutation carries an operation ID, device ID, object ID, expected/base version, client timestamp, payload hash, and deletion state. The server records operation IDs and returns the prior result when retried. Server versions are monotonic per object.
Recommended flow:
```mermaid
sequenceDiagram
participant C as Companion
participant A as Go API
participant P as PocketBase
C->>A: push batch with idempotency keys
A->>A: authenticate, verify device, authorize, validate
A->>P: apply accepted mutations transactionally
P-->>A: versions and cursor
A-->>C: per-operation results
C->>A: pull changes after cursor
A-->>C: bounded changes plus next cursor
C->>C: merge and checkpoint transactionally
```
Do not delete outbox work until the acknowledgement is durably committed locally. Use exponential backoff with jitter and a retry ceiling; permanent validation/auth conflicts remain visible to the user.
## Conflicts
- Immutable source samples deduplicate by source identity and version.
- User-edited records use optimistic concurrency. On divergent edits, preserve both candidate values or request an explicit choice rather than silently overwriting sensitive data.
- Deletion wins only when based on a version at least as new as the competing update; otherwise surface a conflict.
- Server-assigned ownership, security settings, and role changes never use client last-write-wins.
## Scheduling and connectivity
Attempt sync at startup, on manual request, and after data entry/edit/delete, scale measurement, or photo capture when connectivity exists. Health Connect periodic choices are manual, 6h, 12h, or 24h, with Wi-Fi-only versus any-network preference.
Use Android WorkManager with battery/network constraints and acknowledge that schedules are approximate. Coalesce work, transfer compact batches, stop retry loops when the server is unavailable, and show last success/error without notifications.
## Health Connect
Use the official Android Health Connect SDK through a maintained Flutter plugin only if it exposes required semantics; otherwise implement a small Kotlin bridge. Request only data categories actually used and guide users to manage permissions.
Support reading relevant heart rate, resting heart rate, sleep, activity, workout, distance, calories, weight, and body composition records when providers publish them. Background and historical read permissions are requested only with clear user-facing justification.
Store Health Connect change tokens/cursors locally and handle insertions, updates, and deletions. Preserve provider package/source metadata.
### Writing scale data
With explicit user opt-in, write supported Xiaomi scale results to Health Connect. Use a stable `clientRecordId` derived from the local measurement ID and increment `clientRecordVersion` on correction. Re-syncing the same local record MUST update/idempotently reconcile rather than create duplicates.
Weight is measured. Impedance stays in EvolioHealth when no interoperable record exists. Fat, water, bone, basal metabolic rate, and similar outputs are clearly tagged as estimates/calculations with formula version. Only write supported record types and do not misrepresent estimated values as directly measured.
For edits/deletion, ask whether the action affects EvolioHealth only or also the record originally written by EvolioHealth to Health Connect. Never mutate third-party source records silently.
## Kinomap
Assume users rely on a Kinomap subscription and Kinomap publishes workouts through Health Connect. Do not open a competing live BLE connection to the rowing machine. Import the Kinomap exercise as the primary session and associate temporally compatible Pixel Watch/Fitbit heart-rate series without destroying source separation.
Historical TCX/PWX/GPX import may be added later with explicit unit and provenance parsing.
## Xiaomi Mi Body Composition Scale 2
The Companion connects directly via BLE. Implementation must be based on verified advertisements/services and test captures for the exact scale, not undocumented assumptions. It should obtain stable weight, impedance, measurement-stable/final flags, and available diagnostics.
Requirements:
- scan only while the user initiates or enables the measurement workflow;
- identify the intended scale and avoid accepting nearby devices accidentally;
- distinguish incomplete/unstable from final readings;
- prevent duplicate final measurements from repeated advertisements;
- assign the reading to the authenticated local profile with explicit confirmation when ambiguity exists;
- version and test composition formulas and label outputs as estimates;
- save locally before attempting Health Connect or server sync.
## Sync observability
Users see last server sync, last Health Connect import, pending operation count, network policy, and sanitized failures. Administrators see service health and aggregate queue/error counts but not personal values. Logs carry request and operation IDs without payload content.