A merge by a record manager now reads as its own sentence in the owner's
activity feed, the merged toast goes through the written-outcome toast,
and the export test doubles carry the alias and merge tables.
The level now sets, per record: the status-card regeneration gap after a
sync (frugal 24 h, standard the operator's gap, full 1 h) and after a hand
entry (frugal 6 h, otherwise 1 h); the status snapshot cap (10 000 compact
characters on frugal, 14 000 above); background reasoning for the briefing
and the period narratives (off on frugal); the daily briefing's form (a
short form on frugal that keeps every field and at most one
recommendation, the full contract on standard and full); and whether the
automatic document read may act (frugal reads on request only, the stored
switch is kept).
A deterministic simulation of a watch-synced day through the invalidator
counts 122 ingest-driven status generations on full (the v1.42 behaviour),
25 on standard and 2 on frugal, plus the nightly warm on each.
Refs #1217
Three levels (frugal, standard, full) with an operator ceiling and default
and a per-account choice within them, resolved in the capability layer for
the record and published on GET /api/auth/me as ai.usage: the level in
effect, the stored choice, the bounds, why (user, default, admin_cap), the
three options with whether each is allowed and an estimate of tokens per
day scaled from the record's last seven full days of coach_usage, and the
measured mean. A delegate sees the level, never the owner's spend.
PUT /api/auth/me/ai-usage stores the account's own choice or null for the
default, refusing a level above the ceiling with 422 ai.usage.aboveCeiling
and meta.maxLevel. The new supplementLabel capability needs standard or
above; below it the capability is unavailable with the new usage_level
reason, refused with 403 ai.usage.levelTooLow and meta.requiredLevel.
Refs #1217
The opener hint, the first-name clause, resolved follow-ups, the table rules
and the final-round line used to be written into the system prompt, so the
cached prefix (tools plus system) was rewritten on every turn and again on
the forced final round. They travel on the user side now: the per-turn lines
in the user message, the table rules as a note after the round that produced
the first table, the final-round reason as the last user line. The system
prompt depends on the locale, the Coach preferences and the about-me text
only.
Refs #1217
The status cards, the briefing, the derived assessments and the Coach
snapshot drop the two-space indentation from the JSON they hand the model.
The content is unchanged (the hash gate hashes the object, so stored hashes
stay valid). MAX_SNAPSHOT_CHARS is now measured on the compact string: 14 000
for the status cards and the Coach, which every snapshot that fitted the old
24 000 pretty-printed characters still fits, so the model reads the same data
for fewer tokens.
Refs #1217
A sync from a watch or an integration now waits the instance's sync gap
(AppSettings.aiSyncRegenGapHours, else AI_SYNC_REGEN_GAP_HOURS, else 6 h)
before it may regenerate a status card again; a hand entry keeps the hour.
Every caller of afterMeasurementMutation and invalidateStatusInsightsForTypes
names its source, so a new ingest path cannot skip the choice.
Refs #1217
POST /api/biomarkers/{id}/merge folds markers into one in a transaction,
converting readings where the registry knows the units and refusing the
whole merge where it does not; a dry run answers the same result and
writes nothing. Aliases appear on the marker and are edited through the
marker's PUT. Scan and document review rows name the matched marker or
offer up to three, and a chosen marker is remembered as an alias. The
doctor report, FHIR and MCP group readings by marker. Aliases and the
merge ledger travel through backup and restore.
A printed name now resolves to one of the account's markers by its own
name, then by an alias of the account, then by a catalog name for the same
analyte when the reading's unit fits. The catalog gains seeds for alkaline
phosphatase and total bilirubin and a checked list of common lab shorthand
in seven languages; names that mean two analytes stay out.
Every new endpoint of the contract exists, wrapped in apiHandler, with its
authentication, module gate and request validation, and answers
501 not_implemented until its behaviour lands. The OpenAPI document carries
the new operations and schemas and the additive response fields on the
changed routes. The four server feature flags are published as false.
contract-stub-inventory.test.ts registers the stubbed routes; each one
leaves the register with the change that fills it. The route, sharing,
session, security, module-gate and outcome inventories name the new routes
deliberately, and the error-code catalogue lists the codes the routes emit
today.
Migrations 0402 to 0410: AI usage levels, biomarker aliases and merges,
medication ingredients (with the supplement label run kind), life
circumstance fact kinds, AI sections and AUDIT-C, Dawarich context, the
pulse day-mean method, intraday hour buckets, the readable location drop
and five relation link tables. Every new column carries its reader or a
pending-consumer marker.
0409 refuses to run while any home, location period or stored day still
holds a readable location, and names the counts. Locations are read from
the sealed copy only; the encryption backfill no longer handles them, and
the demo seed writes sealed locations only.
The new tables are backed up and wiped, the new sealed columns are in the
rotation registry with labels of their own, the AI usage level and the
life-circumstance confirmations travel with the backup, and the wellbeing
score reads the mental-health screeners only.
The proxy no longer calls crypto.randomUUID itself, so it leaves the
client-random-id exemption list; the request-id helper uses the shared
randomId, which also works without a secure context.
The header is echoed on the response and written into every log line. Both
readers (the proxy and apiHandler) now accept it only when it matches
^[A-Za-z0-9-]{8,64}$ and otherwise use a fresh id.
The watchdog's first pass after a deploy records standing incidents as
announced without paging. Their end then sent a recovery note for
something the operator was never told about. Such incidents now carry
the epoch as their announcement time, which no real announcement can
have, and close without a note.
The extra-dose rule split a dose that HealthLog already recorded: a tap
at 08:00 and the same dose arriving from Health at 08:20 became two
doses and consumed stock twice, and a dose edited in Health (a new
record, a new time) became a second dose next to the first. The rule
now follows the calendar and the record: a take on another local day
is another dose; on the same day, a take logged in HealthLog converges
with the incoming record, a new record that names the slot as its
scheduled time is the slot's dose recorded again, and only an
unscheduled record more than a minute from the held one is an extra
dose.
An account whose backfill slice held it made the hourly job wait up to
eight minutes, and the sync after the wait ran as long as it needed:
past the fifteen-minute expiry pg-boss failed the job and retried it
beside the run still going. The pass now spends the job's budget: the
lock wait is capped at what is left, the sync's walks stop when the
budget is spent and leave the rest of the window to the next hour (the
cycle is not stamped), and an account reached after that is skipped.
A stop on the budget is not counted as a failure to retry.
The watchdog sent one page per account, so a provider outage or an
expired app credential paged once for every connected account. A pass
now collects its messages and sends them per provider at its end: more
than three accounts of one provider opening (or closing) an incident in
the same pass become one message naming the count, the reasons with
their earliest start and the accounts, and one recovery note. Every
incident in it counts as announced.
A failure streak reaching the alert threshold paged the operator even
when the watchdog had already announced an open incident for the same
connection. The ledger now checks for that announced incident first,
stamps the streak as announced and sends nothing; the watchdog's
recovery note still ends it.
The watchdog opened an incident for any degraded state that lasted a
day, so an Apple Health account whose phone app stopped running read as
no_new_data for good and paged. Incidents now open for action_required,
for provider errors and for the watchdog's own unexpected silence; stale
or stalled data alone never pages. The first pass after a deploy, with
no watch row yet, records what stands as already announced instead of
paging every long-broken connection at once.
A walk that reads without the edge bound passed over everything earlier
slices wrote on every slice, and once it crossed the edge the skipped
pages were counted against the walk's page ceiling. The skip now stays
counted for the whole walk, runs on the slice's clock and its own
three-minute limit, and saves the page token it stopped at (on a limit,
a rate limit or the slice budget) so the next slice re-reads that one
page and goes on. A saved place counts as progress only when it moves
forward; a token Google refuses, or one that no longer reaches the edge,
sends the walk back to the newest page as before.
Migration 0401 turns healthkit_type_syncs.type into text so the ledger can
hold MEDICATION_DOSE, and adds last_background_at (seeded from rows whose
latest arrival was a background one). POST /api/medications/intake/bulk
takes syncTrigger and records APPLE_HEALTH dose arrivals; the Apple Health
status read serves a MEDICATION_DOSE freshness entry and lastBackgroundAt
on every entry with arrival facts.
Oura returns detected periods the person dismissed (type rest) and ones
they deleted in the sleep collection. They were imported as sleep, with
their lowest heart rate, HRV and breathing rate, which added an extra
session to the day. They now map to nothing, and rows an earlier sync
stored for such a period are soft-deleted on the next sync through the
shared write tail.
A nap on the same wake day moved the night's latest instant to the end
of the nap and added its minutes, so the midpoint of a regular night
landed around noon. A few such days scored consistency at 0, and a nap
after the latest night scored timing at 0 as well. The night now carries
its main session (wake instant and asleep minutes), and the midpoint is
read from that alone. Sleep rhythm and chronotype read the same midpoint.
The guardian's read of a managed record's connections now carries the same
server-resolved health the owner's own envelope does, loaded for the record
and never for the guardian. The legacy state stays on the wire for one
transition release.
Their rings and straps upload only when the phone app is opened, so a day
without opening it is ordinary and must not page the operator. Wearables
whose cloud receives data on its own keep 24 h, Apple Health keeps 48 h,
and scales, cuffs and Strava never alert on silence.
A backfill noted progress only once a whole collection was read to the end.
A heart-rate history of years that one attempt could not finish (its budget,
a rate limit it could not wait out, a single failed request among thousands
of pages) started the next attempt from the newest page again, finished
nothing, and failed with "marker not stamped" on every attempt.
- Every spot-reading collection read page by page now records, after each
page it writes, the oldest sample it has written (backfillProgress.cursors).
The next attempt asks Google only for samples at or beyond that edge. The
edge is held back once a page's write fails, so no rows are skipped.
- The backfill runs in slices of five minutes or 500 pages. A slice that
finished a collection or moved an edge continues with the next one; only a
slice that moved nothing fails, and its error names each unfinished
collection and why the slice stopped. The slice ceiling is derived from the
collections and the page ceilings of the resumable ones.
- A rate limit Google will not lift within a minute ends the slice instead of
failing the walk, and the next slice starts five minutes later. Server
errors, timeouts and dropped connections are retried three times.
- The heart-rate stream is read last, so the light collections finish in the
first slice.
- The hourly sync waits up to eight minutes for a running slice instead of
skipping the account for as long as the import runs.
- Each slice annotates googleHealth.backfill.slice with pages, elapsed time,
stop reason, per-collection status and edges, and reports progress on the
job.progress line.
The visit form's lab picker offered the newest 200 results, the condition
picker the newest 100 conditions, the document pickers the newest thousand
documents and the document sheet's visit picker the newest 100 visits.
On a long record everything older could not be linked.
Each picker now opens on the records around the date of the thing being
linked, pages older and newer from there, and searches through the list
route itself. Records already linked always show, from the labels the
record's own read carries and from what was picked in the sheet.
The lab list takes a q filter over analyte and panel, the condition list
from/to/q/offset/sortDir, and the visit list an offset.
An hourly and a daily pass flag a continuous source that delivered on
most of the previous 14 days and has gone quiet, and open one operator
incident per connection when a connection stays degraded or needs action
for more than a day. The incident is announced once through the existing
admin alert path, adopted rather than repeated when the sync ledger
already paged, and closed with one recovery note. Scales, cuffs and
workout logs are watched for errors only, never for silence.
The admission drain ran each full-history import with no wide event in
scope, so every external call, warning and annotation the provider walk
recorded was dropped and a failing backfill left only its error message.
Each admitted run now opens a job.integration_backfill event with its kind,
identity, job id and slice, emitted when the run settles. The Health Connect
import worker gets the same treatment.
The external call list on one event is bounded so a long walk cannot hold
thousands of entries: failed and annotated calls keep the larger share,
the rest is counted in external_calls_omitted.
A HealthKit dose event binds to a slot by window band, and a weekly
schedule's band reaches a day either side of its slot. An extra dose
logged the day after a scheduled one landed on that slot and overwrote it
last-write-wins: the slot's time changed and the extra dose was stored
nowhere. An external dose record whose band slot already carries a
different take is now recorded as its own unscheduled dose.
The dose history now lets the take stored on a slot claim it before an
extra dose stored on its own instant, instead of following the order the
rows were read in, so the extra dose reads as off-schedule.
Fold the ledger state, liveness verdict, plan and scope gaps and the
provider reauth flag into ok / limited / degraded / action_required with a
reason code, the one action, the unavailable types and redacted
diagnostics. Keep the last ten sync attempts per connection for those
diagnostics, publish the state on the integrations envelope, every
per-provider status route and the Apple Health config read, and page the
operator once per failure streak instead of once a day.