Releases: PulsHealth/pulshealth
Release list
0.2.0
Upgrading: move the checkout to v0.2.0 (git pull, or
git checkout v0.2.0), set PULS_VERSION=0.2.0 (or track latest), then
make pull up; migrate applies 014_device_tokens.sql and re-runs
099_read_roles.sh. No .env changes are required — the shared
PULS_TOKEN keeps working exactly as before, and PULS_MULTI_USER defaults
to off. The checkout step is not optional: 0.2.0's ingest records
device_token_id on every batch, a column only 014 adds, so the new
images on a 0.1.0 checkout answer every upload with a 500 (the app keeps its
anchors and retries, so nothing is lost, but nothing syncs either).
Added
- Per-device tokens (
make devices ARGS='issue --user <uuid> --name <label>',list,rename,revoke; SRV-8). Each is stored only as its
SHA-256, bound to one user, revocable on its own and stamped with its last
use. A request that presents one acts as that user:X-User-IDmay be
absent or equal, anything else is 403 before the body is read. Every
batchesrow now records which device wrote it (device_token_id, NULL
for the shared token) and the per-batch log line carriestoken_id.
Migration014_device_tokens.sql. - Per-request user scoping on the product API (SRV-11, the API side;
the web viewer's switcher and the MCP server'suserargument below
ride on it). Every
/v1route takes an optionaluser=<uuid>query parameter; absent, the
request is answered forPULS_USER_IDexactly as before.GET /v1/users
lists the users the deployment answers for — name, e-mail,createdAt,
lastSync,batches,uploadedSamplesfrom thebatcheslog — plus
defaultandmultiUser./openapi.jsondescribes the parameter on
every scoped operation. PULS_MULTI_USER(.env, defaultfalse) decides whetheruser=may
name anyone but the default. Off, another user is 403multi-user reads are disabled, never a quiet answer for the default user; a value that
is not a UUID is 400; neither charges the auth-failure limiter. Turning it
on means the one staticPULS_API_TOKEN— the tokendocs/ai.mdsays to
hand to a ChatGPT Action — reads every user on the server, so it stays
off until you want that.puls-export --user <uuid>(default$PULS_USER_ID, else none) picks
whose data to export, and a 403 is explained the way a 401 is.- web: a user switcher when the database holds more than one user;
?user=<uuid>picks one (SRV-11). - MCP:
list_users, and auserargument on every tool;PULS_USER_ID
pins an instance to one person (PULS_MCP_USER_IDfor the Compose
service). Needs the product API'suserparameter and/v1/users(SRV-11). GET /v1/summary?range=7d|14d|30d|90don the product API: the last N
calendar days as one short markdown page (activity, heart, sleep,
workouts, body, coverage) for pasting into a chat that has no MCP
connection;format=jsonfor the numbers. The MCP server exposes it as
get_summary(AI-6).
Changed
PULS_TOKENis optional.PULS_ALLOW_SHARED_TOKEN(defaulttrue) turns
the shared token off once every phone has its own; emptyPULS_TOKENdoes
the same. Ingest logs its auth mode at startup and warns when nothing at
all could authenticate.scripts/bootstrap.shandmake pairingaccept
that mode instead of dying on an empty token.- A device-token lookup that fails because the database is unreachable is
503authentication unavailable, never 401, and is not charged to the
auth-failure limiter; neither is a 403 user mismatch. - The
grafanarole loses SELECT ondevice_tokens(revoked by
099_read_roles.shon every run). - The
api_readerrole gains SELECT onbatches(for/v1/users; the
table holds no credential). No operator action:099_read_roles.sh
re-runs on the nextdocker compose up -d. - web: Next.js 16.3.5 (from 16.3.4).
0.1.0
The first tagged release, and the one that first publishes
ghcr.io/pulshealth/{ingest,api,mcp,web} — before it, a compose install had
nothing to pull and had to build from the checkout. Everything below shipped
together; there is no earlier release to diff against.
Added
- The Puls Sync Protocol, v1. One gzipped NDJSON
POSTplus optional read
endpoints, specified indocs/protocol/with JSON Schemas, a fixture corpus
with expected outcomes, an offline schema checker (tools/protocol-check)
and a Python reference receiver. The receiver's smoke test doubles as a
conformance runner against any implementation:
smoke_test.py --url <url> --token <token>. - The reference backend. Go ingest and product API over PostgreSQL 17 /
TimescaleDB, with Grafana dashboards for the data and for ingest health.
Ingest is idempotent per sample UUID, upserts aggregates and activity rings,
and connects as a scoped DML-only role rather than the superuser. - Schema migrations that apply themselves. The
migrateservice runs
before every app service ondocker compose up -d, records each file in
schema_migrationswith a checksum, refuses an edited or missing applied
file, and requires an explicitbaselinefor a database that predates it. - A one-command quickstart.
scripts/bootstrap.shgenerates the secrets,
starts the stack and prints the pairing QR;makewraps the rest.--lan
trades TLS for a phone on the same Wi-Fi, on request only. - Read-only MCP server (
server/mcp), 11 tools over the product API in
stdio and streamable-HTTP modes, so Claude Desktop, Claude Code, Cursor and
ChatGPT can answer questions from the data. It never touches Postgres. - Export.
GET /v1/exportstreams CSV or JSONL;tools/puls-exportis a
dependency-free CLI over it. - Product API endpoints an analyst asks for first: daily sleep, bounded raw
samples, workout series, state of mind, activity rings, daily metrics that
resolve the iPhone/Watch double-count. - One type vocabulary.
docs/protocol/catalog.jsonis rendered from the
SwiftHealthTypeCatalog, and the web catalog is generated from the JSON, so
the two published lists cannot drift. - Self-hosting safety rails. Auth-failure rate limiting on both ingest and
the product API (failed attempts only, never successful ones), a/healthz
on each that answers from a two-second cache rather than the pool, an
optional password on the web viewer, loopback binds by default, and an
opt-in backup service with a documented restore drill. - Open-source hygiene. Apache-2.0 with
NOTICEandTRADEMARK.md,
SECURITY.md,CONTRIBUTING.mdwith DCO sign-off, a code of conduct, issue
and PR templates,AGENTS.md,llms.txt, and a CI gate that fails on
owner-specific content in tracked files.