mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
feat(integrations): add parked state + per-kind failure counters schema
Two additive columns on integration_statuses driving B4 + B7:
- consecutive_failures_by_kind (jsonb) — bucketed counter
{ transient, reauth_required, persistent } so a transient hiccup
followed by a persistent failure no longer resets the persistent
streak counter. The single-column consecutive_failures stays for
one release as a fallback; the alert ladder reads Math.max across
buckets for backward compatibility.
- persistent_failure_started_at (timestamp) — start of the current
persistent failure streak. Once >24h elapse with the persistent
bucket still non-zero, the status writer flips state to 'parked'.
The state column stays free-form; 'parked' is recognised at the
application layer. Existing rows have NULL in both new columns and
are back-filled on the next write.
This commit is contained in:
1 parent
937daaad04
commit
bd538b02c2
2 files changed
+59
-11
No files matched your search
@@ -0,0 +1,46 @@
|
||||
-- v1.4.43 W14 — IntegrationStatus parking + per-kind failure counters.
|
||||
--
|
||||
-- Two additive columns on `integration_statuses`:
|
||||
--
|
||||
-- 1. `consecutive_failures_by_kind` (jsonb)
|
||||
-- A bucketed counter that increments only the bucket matching the
|
||||
-- failure kind. The single-column `consecutive_failures` integer
|
||||
-- stays in place for one release as a fallback (callers reading
|
||||
-- the alert-ladder still use `Math.max(...buckets)`); it will be
|
||||
-- removed in v1.4.44 once every reader is migrated.
|
||||
--
|
||||
-- Default shape: { "transient": 0, "reauth_required": 0,
|
||||
-- "persistent": 0 }. A `null` value means the
|
||||
-- row predates this migration — the status writer back-fills it
|
||||
-- on the next write by bucketing the old `consecutive_failures`
|
||||
-- value into the current `state`.
|
||||
--
|
||||
-- 2. `persistent_failure_started_at` (timestamp)
|
||||
-- Wall-clock timestamp of the first persistent failure in the
|
||||
-- current streak. Cleared whenever the persistent bucket resets
|
||||
-- to zero. Once the gap between `now()` and this timestamp
|
||||
-- exceeds 24h with the persistent bucket still non-zero, the
|
||||
-- status writer flips `state` to `"parked"` — the integration
|
||||
-- stops retrying and the UI surfaces a "Paused — reconnect
|
||||
-- manually" pill that hits `/api/integrations/withings/resume`
|
||||
-- to clear the park.
|
||||
--
|
||||
-- Idempotent guards (`IF NOT EXISTS`) match the 0067 / 0070 / 0071
|
||||
-- pattern so reruns are safe.
|
||||
--
|
||||
-- The `state` column is a free-form string today (deliberate per the
|
||||
-- 0029 migration comment — "adding new sentinels in v1.5 doesn't
|
||||
-- require a migration"). Adding `"parked"` as a recognised value is
|
||||
-- therefore a no-op at the schema level; the application layer owns
|
||||
-- the enum widening.
|
||||
--
|
||||
-- Reversibility: down migration is `ALTER TABLE ... DROP COLUMN IF
|
||||
-- EXISTS` on both new columns + a single UPDATE that clears any
|
||||
-- `state = 'parked'` rows back to `'error_transient'`. Both columns
|
||||
-- are nullable so existing rows keep working.
|
||||
|
||||
ALTER TABLE "integration_statuses"
|
||||
ADD COLUMN IF NOT EXISTS "consecutive_failures_by_kind" JSONB;
|
||||
|
||||
ALTER TABLE "integration_statuses"
|
||||
ADD COLUMN IF NOT EXISTS "persistent_failure_started_at" TIMESTAMP(3);
|
||||
+13
-11
@@ -1342,17 +1342,19 @@ model MedicationComplianceRollup {
|
||||
// `integration` values are bare strings ("withings", "moodlog") rather
|
||||
// than an enum so future integrations don't require a migration.
|
||||
model IntegrationStatus {
|
||||
id String @id @default(cuid())
|
||||
userId String @map("user_id")
|
||||
integration String // "withings" | "moodlog"
|
||||
state String @default("connected") // "connected" | "error_reauth" | "error_transient" | "disconnected"
|
||||
lastSuccessAt DateTime? @map("last_success_at")
|
||||
lastAttemptAt DateTime? @map("last_attempt_at")
|
||||
lastError String? @map("last_error") // AES-256-GCM encrypted (may contain token-fragment metadata)
|
||||
consecutiveFailures Int @default(0) @map("consecutive_failures")
|
||||
alertedAt DateTime? @map("alerted_at") // last time admin Telegram alert fired (idempotency guard)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
id String @id @default(cuid())
|
||||
userId String @map("user_id")
|
||||
integration String // "withings" | "moodlog"
|
||||
state String @default("connected") // "connected" | "error_reauth" | "error_transient" | "disconnected" | "parked"
|
||||
lastSuccessAt DateTime? @map("last_success_at")
|
||||
lastAttemptAt DateTime? @map("last_attempt_at")
|
||||
lastError String? @map("last_error") // AES-256-GCM encrypted (may contain token-fragment metadata)
|
||||
consecutiveFailures Int @default(0) @map("consecutive_failures") // v1.4.44 will drop; per-kind counter below is the source of truth
|
||||
consecutiveFailuresByKind Json? @map("consecutive_failures_by_kind") // v1.4.43 W14 — { transient, reauth_required, persistent }
|
||||
persistentFailureStartedAt DateTime? @map("persistent_failure_started_at") // v1.4.43 W14 — start of the current persistent streak (drives >24h park)
|
||||
alertedAt DateTime? @map("alerted_at") // last time admin Telegram alert fired (idempotency guard)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
|
||||
Reference in new issue
Block a user