Commit Graph
6 Commits
Author SHA1 Message Date
Marc-André Bombeck 9941db1549 refactor(insights): drop the readable cached-text column
Every write has gone to insights_cached_text_encrypted since 0411, and
the boot-time free-text backfill sealed the rows written before it.
Migration 0430 drops insights_cached_text and the backfill index that
served it. A host that skipped the intervening releases loses only a
cache of model output; the next warm writes a fresh, sealed payload.

The cipher module now reads and writes the sealed column only, the
backfill job loses its insights step, and consent withdrawal, the wipe
plan, the backup plan and the session projection follow the schema.
Backups never carried the column, and restore ignores unknown account
settings; a round-trip test now proves an old backup naming the field
still restores cleanly. The access guard pins exact counts for where
the sealed name may appear. Fixtures, the demo seed and the demo
migration script seed the sealed column instead of plaintext.
2026-10-21 19:24:21 +02:00
Marc-André Bombeck 63b395915e test: seed cached briefings with their generation moment
The today check now reads briefingGeneratedAt from the cached payload
and treats a payload without it as not from today. The integration
and e2e fixtures that seed a fresh briefing write it, as every real
generation does.
2026-10-05 22:28:10 +02:00
Marc-André Bombeck 2143445da2 feat(ai): show stored model text inside a shared record as the owner would
Serving stored text and starting model work are separate questions. A
delegate may not start model work on somebody else's record, but reads
the owner's stored briefing, period narrative, status notes, workout
notes and Coach unread signal exactly when the owner would: those
readers now resolve the record's own capability state, not the
viewer's. Generation, warms and enqueues still ask as the viewer, so a
delegate's read of a stale narrative queues nothing. A hidden
model-written narrative is no longer overwritten by a deterministic
warm while AI is unavailable.

The sharing front door reads the digest's briefing lead again, and an
integration case pins both halves.

Refs #975
2026-09-24 10:17:00 +02:00
Marc-André Bombeck 1f6a13e2f7 feat(ai): answer the status notes with a null note when AI is unavailable
The status family (the five metric cards, medication compliance, the
generic metric status and the biomarker assessment) asked the retired
switch and the `insights` module and refused with 403. The note is model
text and the card around it is data, so they are mixed reads now: while
the `statusText` capability is available the note is served and warmed
as before, with `ai` beside it; otherwise the answer is 200 with the
note null, `preparing: false`, `hasProvider` as provider presence only
and `ai` naming the reason, and neither the cache nor the warm queue is
touched. The generic metric status still reports `insufficient` from the
data. The mood card keeps its mood module gate.

Kept apart so it can be held back while the shipped iPhone app's
rendering of a 200 without a note is confirmed.

Refs #975
2026-09-24 09:30:23 +02:00
Marc-André Bombeck 9f1d1341f9 test(ai): state the mutation checks the AI route tests were run against 2026-09-24 09:22:50 +02:00
Marc-André Bombeck ce5a340931 test(ai): prove Insights data, mixed reads and actions against Postgres
Three files walk the AI state matrix through the real handlers: every
data route answers the baseline's data under each state, and an ECG
upload is stored with AI off; mixed reads null the model text, name the
reason and enqueue nothing, and the dashboard hides a cached briefing on
the next read after a switch flip; the briefing and Coach actions refuse
with the exact code, capability and reason before any provider or cache
write. Existing files that seeded provider-less accounts now give them a
provider where the briefing or the Coach is under test, and the sharing
front door marks the digest by a due checkup, since model text is not
served inside somebody else's record.

Refs #975
2026-09-24 09:19:43 +02:00