mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 06:06:10 +00:00
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.
This commit is contained in:
1 parent
ee704cacab
commit
dcbac63fdf
20 files changed
+2162
-14
No files matched your search
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* v1.21.3 (B1) — request schemas for the Coach goal / if-then plan surface
|
||||
* (`/api/coach/plans` + `/api/coach/plans/[id]`).
|
||||
*
|
||||
* Lives outside the route files so the OpenAPI registry can import it without
|
||||
* touching the route modules (route files may only export handlers + config)
|
||||
* and without dragging the Prisma-backed helper module into the generator
|
||||
* script.
|
||||
*
|
||||
* No `userId` field anywhere — the owner is always narrowed from the session.
|
||||
* The extractor is the only writer of plan TEXT; this surface only lets the
|
||||
* user CONFIRM (proposed → active), mark a plan met / abandoned, set a review
|
||||
* date, or soft-delete. It never accepts the encrypted free-text fields, so a
|
||||
* client can never inject or overwrite a plan's prose.
|
||||
*/
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { COACH_PLAN_STATUSES } from "@/lib/ai/coach/plans";
|
||||
|
||||
/** Optional `?status=` filter on the list endpoint. */
|
||||
export const coachPlansListQuerySchema = z.object({
|
||||
status: z.enum(COACH_PLAN_STATUSES).optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* The user-facing PATCH body. `status` is the only mutable lifecycle field; a
|
||||
* `proposed` plan moves to `active` (confirm), or a plan moves to `met` /
|
||||
* `abandoned`. `reviewDate` optionally pins / clears a check-in checkpoint
|
||||
* (null clears). Strict: unknown keys 422, so a client cannot smuggle a
|
||||
* userId, a metric, or the encrypted text.
|
||||
*/
|
||||
export const coachPlanPatchSchema = z
|
||||
.object({
|
||||
status: z.enum(COACH_PLAN_STATUSES).optional(),
|
||||
reviewDate: z.iso.datetime({ offset: true }).nullable().optional(),
|
||||
})
|
||||
.strict()
|
||||
.refine(
|
||||
(v) => v.status !== undefined || v.reviewDate !== undefined,
|
||||
"At least one of status or reviewDate must be provided",
|
||||
);
|
||||
Reference in new issue
Block a user