Mood has been a first-class module here for a long time, with entries,
structured tags, rated factors and notes. The bridge to the external moodLog
service had nothing left to add, and its Settings card was withdrawn in
a56e6fe61 on the promise of a dedicated removal. This delivers it.
Removed: the hourly `moodlog-sync` queue, cron and handler binding; the
inbound webhook and its shared-secret handling; the reverse push and its four
call sites; the pull-sync module; the per-user credential routes and the
instance-wide toggle; the entry on the `/api/integrations/status` envelope,
the `IntegrationKey` union, the cadence map and the display-name table; the
boot-time secret rotation pass; 22 message keys per locale.
Migration 0271 drops the five `users` credential columns and
`app_settings.mood_log_global`, clears the ledger row for the provider, and
deletes the retired pg-boss schedule so no cron keeps enqueuing work no
handler drains.
Mood data is untouched. `mood_entries` is not named anywhere in the migration
and cannot be reached from it: the dropped columns carry no index, constraint
or foreign key, and `mood_entries.user_id` references `users(id)`, not any of
them. Rows the bridge imported keep `source = 'MOODLOG'`, and every read path
- list filter, source badge, exports, backups - still resolves that value.
`src/lib/validations/moodlog.ts` held the native mood-entry schemas alongside
the bridge's own; it is now `validations/mood.ts` and carries only the former.
One-shot prettier pass so the new format:check gate starts from a clean
baseline. Formatting only — no behaviour change. Generator-owned and
vendored artifacts stay untouched via .prettierignore.
v1.4.15 Phase B2, criterion 4. Every IntegrationStatus failure path
now writes a single AuditLog row with a documented schema, and this
commit lights up the real-Postgres regression guard for it:
action : "integrations.sync.failed"
userId : <subject>
details : {
integration: "withings" | "moodlog",
kind: "transient" | "reauth_required",
errorCode: string | null,
message: string, // plaintext (not encrypted —
// audit table is admin-only)
attemptNumber: number, // 1..N consecutive
state: "error_transient" | "error_reauth"
}
Successes are NOT audited — that path would be far too noisy
(every webhook-triggered sync is a row, ~ten times a day per active
user) and the integration_statuses.lastSuccessAt column already
tracks the same signal at one row per integration per user.
The integration test in tests/integration/integration-status.test.ts
provides 8 cases against a real Postgres testcontainer:
- failure writes both the integration_statuses row AND a matching
AuditLog entry, with the structured meta validated field-by-field
- lastError is encrypted at rest (column never matches plaintext)
- getIntegrationStatus() decrypts cleanly on read
- success clears the streak AND does NOT write an audit row
- admin Telegram alert fires once per burst (24h re-alert window)
- reauth_required parks the integration so isReauthRequired() is true
- markReconnected() clears the parked state without writing a fresh
success — that's the next sync's job
- markDisconnected() preserves the row as a tombstone but wipes
error state
- CASCADE delete on User removes the IntegrationStatus row
Setup file additions: integration_statuses added to the truncate-list
in tests/integration/setup.ts (already shipped under commit 4db72a8;
this commit only adds the test file that consumes it).
Tests: 11 / 39 integration pass. The dispatcher is module-mocked so
the test doesn't actually hit Telegram — we only assert that the
helper INVOKED the dispatcher with the right payload.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>