Commit Graph
6601 Commits
Author SHA1 Message Date
Marc-André Bombeck bd8ea9d86b feat(ai): gate document, lab and medication extraction on their capabilities
Reading a stored document (suggest, summary, extract, index, chat,
reindex), scanning a lab report and turning medication text into a
schedule now answer under documentAi, labsOcr and medicationExtract. The
operator's master switch and the new reading-documents switch stop the
work and the egress; a module off for the record, a delegate inside
somebody else's record, and a missing receipt refuse with the capability
envelope. Existing no-provider codes keep their status.

The document pick and the labs pick re-check the capability for the
provider they picked, immediately before anything is sent, so a job
enqueued before a switch flipped finds no provider and falls back to the
provider-free text layer. Consent follows the document rule for all
three: any provider that leaves the machine needs an ai_extraction or
ai_full receipt, answered for the provider actually picked.

The fenced Coach chat and attachments answer under coach and documentAi.
Uploads are always stored and indexed; the background summary is only
enqueued while documentAi is open. The capability probes report the
capability beside the provider answer and keep a read offered when only
the receipt is missing. medications/extract keeps its 503 for no provider
and adds ai.provider.none, and its budget refusal gains the
coach.budget.exceeded code.
2026-09-24 08:44:57 +02:00
Marc-André Bombeck a19900c854 feat(ai): re-check a capability at the wire
requireAiCapability answers at the top of a route, before a provider is
picked, and a job enqueued before an operator switch flipped runs long
after any route answered. aiEgressRefusal asks again about the providers a
caller is about to send to: the capability for the record (the request's
own resolution, or the job's for the record it names) and, for exactly
those providers, whether a consent receipt of the kinds the capability
table declares is active.

A pickDecides gate option leaves no_provider and consent_required to that
pick, for routes that read the chain in their own order.
2026-09-24 08:44:49 +02:00
Marc-André Bombeck f434ba7d29 test(ai): follow the retired Correlations switch and the closed default
The Coach gate inventory lists the correlations route with the routes
no assistant switch owns. The seeded-question test gives the switch
read a settings row, since a read that fails no longer opens every
switch.
2026-09-24 07:59:49 +02:00
Marc-André Bombeck fccc44f7f1 test(ai): run the 0343 data steps against seeded rows
The Coach availability fold and the AI analysis carry-over for people
who hid the Coach are read out of the migration and run twice against
rows shaped like an upgrading instance, including a malformed
preference blob and the concurrency token.
2026-09-24 07:54:59 +02:00
Marc-André Bombeck b7a72489ed test(ai): pin the account payload's capability block against Postgres
Each state changes one layer against a fully set-up account and checks
the capabilities it owns: master switch, Reading documents, no
provider, the operator's key with and without consent, hiding the
Coach, the AI analysis opt-out, the workouts module, the single Coach
switch, a delegate, and a guardian limited to the operator's key.
2026-09-24 07:53:28 +02:00
Marc-André Bombeck cc4934897c docs(api): describe the AI capability block and refusal meta
AccountPayload gains ai with the AiCapabilities, AiCapabilityState,
AiUnavailableReason and AiProviderState components. The feature-flags
route is marked deprecated, drops correlations and documents
documentAi. The error envelope's meta documents capability, reason and
module, lists the assistant.disabled switches, and the catalogue
carries the three new ai codes.
2026-09-24 07:51:16 +02:00
Marc-André Bombeck 464522f315 feat(ai): publish capabilities on the account payload
GET /api/auth/me gains ai: the state of every capability and the
provider block, resolved for the active record and masked to the
grant like moduleAccess. The authority comes from the switch the
payload already resolved, so a delegate reads not_permitted_for_record
and a guardian is limited to the operator's key.

useAiCapability is the web reader and fails closed until the payload
arrives. GET /api/feature-flags becomes a deprecated projection of the
operator switches with a Deprecation header and a link to its
successor.
2026-09-24 07:49:14 +02:00
Marc-André Bombeck 7c757520f1 feat(ai): capability loader, gates and refusal envelope
loadAiCapabilityInputs reads the switches, module access, provider
presence and active consent kinds for one record in parallel, memoised
per request, and fails closed to check_failed. requireAiCapability,
getAiCapability and aiCapabilityForJob resolve for the request's
record, masked to the active grant like moduleAccess, or for a job's
record under the worker's authority.

apiHandler renders AiUnavailableError as one envelope with errorCode,
capability, reason and module. Existing codes stay: the operator
family keeps assistant.disabled.<switch>, modules keep module.disabled,
consent keeps consent.ai.required. New codes are ai.record.notPermitted,
ai.provider.none and ai.unavailable.
2026-09-24 07:46:12 +02:00
Marc-André Bombeck 1be2392c81 feat(ai): one provider presence probe
probeProviderChain describes the chain that would serve a record
without decrypting, building a client or refreshing a token: which
entries hold a credential, in chain order, whether each reads images,
the in-browser OCR opt-in, and where the credential comes from. It
applies the provider-work authority, appends the central Codex only
behind the opt-in, and falls back to the legacy provider only for an
empty chain. hasAnyConfiguredProvider and resolveProviderAvailability
now answer through it.
2026-09-24 07:39:56 +02:00
Marc-André Bombeck a7888a9011 feat(ai): replace the Correlations switch with Reading documents
The operator switches become master, Coach, Daily Briefing, Status
notes and Reading documents. Correlations goes: it gated statistics,
not anything a model writes. Reading documents covers the document
vault, lab scans and medication extraction.

The Coach module's operator availability now follows the Coach
switch, so there is one place to turn the Coach off server-wide. The
migration folds a stored Coach availability of false into the switch,
and gives everyone who hid the Coach the AI analysis opt-out, since
hiding the Coach no longer stops nightly briefing and status notes.

A switch read that fails now fails closed. The insights module is
described as AI analysis in every locale, and the switch guard now
keeps both retired switches out and freezes the callers of the old
operator-only gate.
2026-09-24 07:36:47 +02:00
Marc-André Bombeck c8a01ac3ed feat(ai): resolve AI capabilities from their layers
A pure resolver turns operator switches, module access, provider-work
authority, provider presence and consent receipts into one state per
capability, reporting the outermost reason. The document provider rank
moves to its own module so the resolver and the document routes read
the same order.
2026-09-24 07:26:33 +02:00
Marc-André Bombeck a5fdf675e1 feat(ai): add the capability vocabulary and table
One frozen table names every AI capability with the operator switch,
owning modules, consent rule, modality and provider order that decide
it. The reason enum is listed in precedence order. No I/O, so the
resolver, gates, contract and guards can all depend on it.
2026-09-24 07:23:57 +02:00
Marc-André Bombeck aa58495cbe Merge pull request #1007 from muhdusama/medfix/apple-health-scheduled-mirror
fix: allow scheduled Apple Health medication mirrors
2026-09-20 23:47:32 +02:00
muhdusama 24ff031f7a fix: preserve legacy compliance response contract 2026-09-21 07:17:13 +10:00
Marc-André Bombeck 50717c567a docs: keep README screenshots on one line (#1022) 2026-09-20 20:15:57 +02:00
dependabot[bot] edb613bf2b chore(deps): bump docker/setup-buildx-action from 4.3.0 to 4.4.1 (#1020)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 4.3.0 to 4.4.1.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/37fe631027851001ddb9b187196cc803df7f5f0e...f87e5991a6d7451dcb8d9637bfbc97413f497069)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: 4.4.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-20 19:00:55 +02:00
dependabot[bot] 9402e48936 chore(deps): bump docker/build-push-action from 7.3.0 to 7.4.0 (#1019)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 7.3.0 to 7.4.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/53b7df96c91f9c12dcc8a07bcb9ccacbed38856a...c3c9e263c25d99ce0380d002d59b67737d91b0dc)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: 7.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-20 19:00:44 +02:00
dependabot[bot] 2c640683a7 chore(deps): bump undici from 8.10.0 to 8.10.2 (#1018)
Bumps [undici](https://github.com/nodejs/undici) from 8.10.0 to 8.10.2.
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v8.10.0...v8.10.2)

---
updated-dependencies:
- dependency-name: undici
  dependency-version: 8.10.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-20 19:00:31 +02:00
dependabot[bot] fd8431ae47 chore(deps-dev): bump eslint-config-next from 16.2.12 to 16.3.5 (#1015)
Bumps [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) from 16.2.12 to 16.3.5.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Commits](https://github.com/vercel/next.js/commits/v16.3.5/packages/eslint-config-next)

---
updated-dependencies:
- dependency-name: eslint-config-next
  dependency-version: 16.3.5
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-20 18:24:50 +02:00
Marc-André Bombeck 271cebeead Merge pull request #1014 from MBombeck/MBombeck-patch-1
Enhance README with additional images
2026-09-20 17:50:30 +02:00
Marc-André Bombeck 6b1b061b0b Enhance README with additional images
Added images for insights, resting pulse, medications, and coach chat to the README.
2026-09-20 17:22:36 +02:00
Marc-André Bombeck 749f3682c0 Add assets 2026-09-20 17:18:05 +02:00
muhdusama 41d47c3184 fix: mark mirror adherence not applicable 2026-09-20 21:48:20 +10:00
muhdusama c223b6d77d fix: scope Apple Health schedule exemption 2026-09-20 20:46:47 +10:00
Marc-André Bombeck ac59e68a54 Merge pull request #1012 from MBombeck/docs/app-store-links
docs(readme): point at the App Store listing
2026-09-20 11:52:47 +02:00
Marc-André Bombeck 325e94a8f9 docs(readme): point at the App Store listing
The iOS client left public beta. The badge row, the navigation lines and
the iOS section named TestFlight only, which now reads as the single way
in. Both routes stay, with the price and the free-code offer stated
rather than left for someone to discover on the store page.
2026-09-20 11:36:27 +02:00
Marc-André Bombeck 0195c59667 Merge pull request #1010 from MBombeck/release/v1.38.25
Release v1.38.25
v1.38.25
2026-09-20 11:10:30 +02:00
Marc-André Bombeck 4f31b4b025 chore(release): v1.38.25 — readings that never arrived, and an import that said no 2026-09-20 10:53:24 +02:00
Marc-André Bombeck 6291c95b5a Merge pull request #1009 from MBombeck/fix/import-export-xml-name
fix(import): find the export XML whatever iOS named it
2026-09-20 00:33:54 +02:00
Marc-André Bombeck c405f15ef0 fix(import): find the export XML whatever iOS named it
The archive member was matched against the English lowercase spelling
alone. iOS writes that file name in the phone's language and some
versions capitalise it, so an ordinary export could arrive as
Export.xml or a translated name and was refused with a message telling
the reader their export was invalid.

The basename now matches in any capitalisation, and a single XML member
at the top of the archive is taken as the translated spelling. Apple's
clinical-document file never qualifies: it sits beside the real export
and would parse to nothing. When no member qualifies the error lists the
XML members the archive does hold, so the next report arrives with the
answer in it.

Refs #1008
2026-09-20 00:14:26 +02:00
muhdusama 30e454fb83 fix: allow scheduled Apple Health mirrors 2026-09-20 00:26:05 +10:00
Marc-André Bombeck c183d1862e Merge pull request #1003 from MBombeck/fix/cycle-symptom-severity-null
fix(cycle): accept a symptom logged without an intensity
2026-09-18 19:33:22 +02:00
Marc-André Bombeck 6113855b19 chore(openapi): regenerate after the symptom severity change 2026-09-18 19:10:39 +02:00
Marc-André Bombeck bff083f532 fix(cycle): accept a symptom logged without an intensity
Picking a symptom without tapping an intensity is how most of them are
logged. The sheet sends severity null for those, the schema took a number
or nothing at all, and the day log came back 422 with two issues for two
symptoms. All the sheet could say was "Could not save. Try again.", and
the cycle saved fine as soon as the symptoms were removed, which sent
people looking in the wrong place.

Nothing else disagreed: the link row has always stored an unrated symptom
as a plain presence, and the writer already maps a non-number to NULL.
Only the gate at the door refused it, on both the create and the patch
path, and no test touched either schema.

Refs #1001
2026-09-18 18:51:38 +02:00
Marc-André Bombeck 1fe90f3fab Merge pull request #1000 from MBombeck/fix/hk-percent-tolerance
fix(apple-health): take a percent from a fraction or an already-scaled client
2026-09-18 16:58:59 +02:00
Marc-André Bombeck 9d9e8570f7 fix(apple-health): take a percent from a fraction or an already-scaled client
Shipped iOS builds multiply oxygen saturation and body fat by 100 before
upload. The importer scaled them again, so 0.97 was stored as 9700, the
plausibility guard rejected the row as out of range inside an HTTP 200,
and the client treats that skip as final and moves its HealthKit anchor
past the sample. Every reading of those two types was lost, silently, on
every install that syncs them.

The contract says values travel raw and the server scales, and that is
what the OpenAPI description publishes. One comment in the schema said
the opposite, that the client must multiply by 100 first, and that is
the line the app was built against. It is corrected here.

The five percent-shaped HealthKit identifiers now take either spelling:
a value above the fraction ceiling is already a percent and passes
through, anything at or below it is scaled. The domains do not overlap,
and the per-type range guard still rejects what is impossible either
way. Current app builds stop losing readings without an app release.

The tests pin both spellings, the range after conversion, and that the
tolerant converter covers the percent class and nothing else: waist
circumference uses the same factor to turn metres into centimetres, and
a blanket change made a 1.02 m waist into 1.02 cm.

Refs MBombeck/healthlog-iOS#7
2026-09-18 16:40:11 +02:00
Marc-André Bombeck 244d0e4b69 Merge pull request #999 from MBombeck/release/v1.38.24
Release v1.38.24
v1.38.24
2026-09-16 18:48:07 +02:00
Marc-André Bombeck 5188b28ea6 chore(release): v1.38.24 — Insights loads without the assistant, and the database stays current 2026-09-16 18:27:51 +02:00
Marc-André Bombeck bf728ff552 Merge pull request #998 from MBombeck/dependabot/npm_and_yarn/types/node-26.5.1
chore(deps-dev): bump @types/node from 26.1.1 to 26.5.1
2026-09-16 15:47:54 +02:00
Marc-André Bombeck c2ec2b76cf Merge pull request #994 from MBombeck/dependabot/npm_and_yarn/sonner-2.0.8
chore(deps): bump sonner from 2.0.7 to 2.0.8
2026-09-16 15:47:26 +02:00
dependabot[bot] 34182cbb4f chore(deps-dev): bump @types/node from 26.1.1 to 26.5.1
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 26.1.1 to 26.5.1.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 26.5.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-16 13:23:10 +00:00
dependabot[bot] 4ac11c71cb chore(deps): bump sonner from 2.0.7 to 2.0.8
Bumps [sonner](https://github.com/emilkowalski/sonner) from 2.0.7 to 2.0.8.
- [Release notes](https://github.com/emilkowalski/sonner/releases)
- [Commits](https://github.com/emilkowalski/sonner/compare/v2.0.7...v2.0.8)

---
updated-dependencies:
- dependency-name: sonner
  dependency-version: 2.0.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-16 13:10:36 +00:00
Marc-André Bombeck 629731120a Merge pull request #997 from MBombeck/dependabot/npm_and_yarn/jose-6.2.12
chore(deps): bump jose from 6.2.10 to 6.2.12
2026-09-16 15:09:43 +02:00
Marc-André Bombeck c33a88b282 Merge pull request #993 from MBombeck/dependabot/npm_and_yarn/napi-rs/canvas-1.0.9
chore(deps): bump @napi-rs/canvas from 1.0.7 to 1.0.9
2026-09-16 15:09:20 +02:00
Marc-André Bombeck 19ba97e3fc Merge pull request #990 from MBombeck/dependabot/npm_and_yarn/sax-1.6.1
chore(deps): bump sax from 1.6.0 to 1.6.1
2026-09-16 15:08:31 +02:00
dependabot[bot] 9db3770ea3 chore(deps): bump @napi-rs/canvas from 1.0.7 to 1.0.9
Bumps [@napi-rs/canvas](https://github.com/Brooooooklyn/canvas) from 1.0.7 to 1.0.9.
- [Release notes](https://github.com/Brooooooklyn/canvas/releases)
- [Changelog](https://github.com/Brooooooklyn/canvas/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Brooooooklyn/canvas/compare/v1.0.7...v1.0.9)

---
updated-dependencies:
- dependency-name: "@napi-rs/canvas"
  dependency-version: 1.0.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-16 12:06:47 +00:00
dependabot[bot] fade5d467e chore(deps): bump sax from 1.6.0 to 1.6.1
Bumps [sax](https://github.com/isaacs/sax-js) from 1.6.0 to 1.6.1.
- [Commits](https://github.com/isaacs/sax-js/compare/v1.6.0...v1.6.1)

---
updated-dependencies:
- dependency-name: sax
  dependency-version: 1.6.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-16 12:06:47 +00:00
dependabot[bot] 453c1141a7 chore(deps): bump jose from 6.2.10 to 6.2.12
Bumps [jose](https://github.com/panva/jose) from 6.2.10 to 6.2.12.
- [Release notes](https://github.com/panva/jose/releases)
- [Changelog](https://github.com/panva/jose/blob/main/CHANGELOG.md)
- [Commits](https://github.com/panva/jose/compare/v6.2.10...v6.2.12)

---
updated-dependencies:
- dependency-name: jose
  dependency-version: 6.2.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-16 11:54:00 +00:00
Marc-André Bombeck 6f562c05ca Merge pull request #992 from MBombeck/dependabot/npm_and_yarn/nodemailer-10.0.9
chore(deps): bump nodemailer from 9.1.1 to 10.0.9
2026-09-16 13:53:51 +02:00
Marc-André Bombeck 78996e6ba5 Merge pull request #996 from MBombeck/ci/pin-trivy-installer
ci(security): pin and verify the Trivy installer
2026-09-16 13:46:41 +02:00