Files
HealthLog/prisma/migrations/0194_v1213_coach_plan/migration.sql
T
Marc-André Bombeck dcbac63fdf feat(coach): goal / if-then plan memory + locale-keyed dashboard snapshot
Add a CoachPlan model: durable, user-scoped if-then implementation plans
the Coach proposes and the user confirms. The background memory-refresh
pass extracts plans the user agreed to and writes them as "proposed"; only
the user-facing PATCH on /api/coach/plans/[id] activates one. The free-text
cue / action / target are encrypted at rest with the shared Bytes codec;
metric, status and dates stay plain so the injection picker ranks without a
per-row decrypt. Active plans fold into the Coach snapshot's memory block
and survive the prompt-budget drop alongside facts. List, confirm and
soft-delete routes are Coach-gated and narrow the owner from the session.

Key the dashboard-snapshot query cell by locale so a locale switch reads
freshly localised tile copy on its own cell instead of serving the prior
locale's prose until the slot goes stale. The zero-arg key stays
byte-identical, so every prefix-based invalidation keeps reaching the
locale-keyed cells; the offline-banner and preloader match on the prefix.

Migration 0194 (additive CREATE TABLE). Encryption-rotation registry and
script cover the three new Bytes columns.
2026-06-27 01:25:35 +02:00

35 lines
1.7 KiB
SQL

-- v1.21.3 (B1) — Coach goal / if-then implementation-plan memory.
--
-- Durable, user-scoped plans the Coach PROPOSES and the user CONFIRMS. The
-- extractor writes a row as status 'proposed'; only the user-facing PATCH
-- flips it to 'active'. Free-text fields (the if-cue, then-action, optional
-- target) are AES-256-GCM ciphertext stored as `bytea` via the shared Bytes
-- codec — the same shape as coach_facts.fact_encrypted. metric / status /
-- dates / source_conversation_id stay plain so the injection picker can rank
-- and filter without a per-row decrypt. status is an app-side closed enum
-- (proposed | active | met | abandoned), deliberately NOT a DB enum so this
-- migration stays purely additive. Soft-delete via deleted_at.
CREATE TABLE "coach_plans" (
"id" TEXT NOT NULL,
"user_id" TEXT NOT NULL,
"metric" TEXT NOT NULL,
"if_cue_encrypted" BYTEA NOT NULL,
"then_action_encrypted" BYTEA NOT NULL,
"target_encrypted" BYTEA,
"status" TEXT NOT NULL DEFAULT 'proposed',
"review_date" TIMESTAMP(3),
"source_conversation_id" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"deleted_at" TIMESTAMP(3),
CONSTRAINT "coach_plans_pkey" PRIMARY KEY ("id")
);
-- Injection picker scans a user's active plans newest-first; the status +
-- deleted_at columns are part of the predicate, so the composite index lets
-- the planner serve the read index-backed.
CREATE INDEX "coach_plans_user_id_status_deleted_at_idx" ON "coach_plans"("user_id", "status", "deleted_at");
ALTER TABLE "coach_plans" ADD CONSTRAINT "coach_plans_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;