mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +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
@@ -4653,6 +4653,136 @@ paths:
|
||||
"401": *a1
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
/api/coach/plans:
|
||||
get:
|
||||
tags:
|
||||
- Insights
|
||||
summary: List the caller's Coach goal / if-then plans
|
||||
description: v1.21.3 (B1) — returns the durable plans the Coach has proposed for the caller, newest first, each
|
||||
decrypted on the fly. A plan is an "if-then" implementation intention tied to one metric, with an optional
|
||||
target. The Coach extractor writes a plan as `proposed`; only `PATCH /api/coach/plans/{id}` activates it. Pass
|
||||
`?status=` to filter (proposed | active | met | abandoned); omitted returns the non-terminal set (proposed +
|
||||
active). Coach-gated (`requireModuleEnabled("coach")`); a disabled surface 403s. Auth via cookie or Bearer; the
|
||||
owner is always narrowed from the session, never the body. Undecryptable rows are omitted rather than failing
|
||||
the read.
|
||||
parameters:
|
||||
- name: status
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- proposed
|
||||
- active
|
||||
- met
|
||||
- abandoned
|
||||
description: Filter to a single lifecycle status. Omit for the non-terminal set (proposed + active).
|
||||
responses:
|
||||
"200":
|
||||
description: The caller's plans.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/CoachPlansList"
|
||||
"401": *a1
|
||||
"403":
|
||||
description: Coach surface disabled.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorEnvelope"
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
/api/coach/plans/{id}:
|
||||
patch:
|
||||
tags:
|
||||
- Insights
|
||||
summary: Confirm or update a Coach plan's lifecycle
|
||||
description: v1.21.3 (B1) — confirm a proposed plan (status proposed → active) or mark it met / abandoned, and
|
||||
optionally set / clear a review date. The body carries ONLY lifecycle fields — never the metric or the encrypted
|
||||
free text — so a client can change a plan's status but never inject or overwrite its prose. A foreign / unknown
|
||||
/ already-deleted id maps to 404 (never 403) so the existence channel does not leak across accounts.
|
||||
Coach-gated. Auth via cookie or Bearer; the owner is narrowed from the session.
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: Plan id.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
enum:
|
||||
- proposed
|
||||
- active
|
||||
- met
|
||||
- abandoned
|
||||
reviewDate:
|
||||
anyOf:
|
||||
- type: string
|
||||
format: date-time
|
||||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||||
- type: "null"
|
||||
additionalProperties: false
|
||||
responses:
|
||||
"200":
|
||||
description: The plan after the lifecycle update.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/CoachPlanUpdated"
|
||||
"401": *a1
|
||||
"403":
|
||||
description: Coach surface disabled.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorEnvelope"
|
||||
"404":
|
||||
description: Plan not found or not owned by the caller.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorEnvelope"
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
delete:
|
||||
tags:
|
||||
- Insights
|
||||
summary: Soft-delete one Coach plan
|
||||
description: "v1.21.3 (B1) — soft-deletes a single plan owned by the caller. An unknown / cross-user / already-deleted
|
||||
id is an idempotent no-op returning `{ deleted: false }`, never revealing whether the id exists under another
|
||||
account. Coach-gated. Auth via cookie or Bearer."
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: Plan id.
|
||||
responses:
|
||||
"200":
|
||||
description: "The plan was soft-deleted (`deleted: true`) or the id matched nothing the caller owns (`deleted: false`)."
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/CoachPlanDeleted"
|
||||
"401": *a1
|
||||
"403":
|
||||
description: Coach surface disabled.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorEnvelope"
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
/api/coach/reminder-suggestions:
|
||||
post:
|
||||
tags:
|
||||
@@ -16736,6 +16866,141 @@ components:
|
||||
- data
|
||||
- error
|
||||
additionalProperties: false
|
||||
CoachPlansList:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
type: object
|
||||
properties:
|
||||
plans:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/CoachPlan"
|
||||
description: The caller's plans, newest first. Undecryptable rows are omitted from the list endpoint. No `status` filter
|
||||
returns the non-terminal set (proposed + active).
|
||||
required:
|
||||
- plans
|
||||
additionalProperties: false
|
||||
error:
|
||||
type: "null"
|
||||
meta:
|
||||
type: object
|
||||
properties:
|
||||
requestId:
|
||||
type: string
|
||||
additionalProperties: false
|
||||
required:
|
||||
- data
|
||||
- error
|
||||
additionalProperties: false
|
||||
CoachPlan:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
metric:
|
||||
type: string
|
||||
description: The metric this plan moves (e.g. WEIGHT, SLEEP).
|
||||
ifCue:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
description: Decrypted if-cue (the trigger). Null when the row's key id is no longer in the map.
|
||||
thenAction:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
description: Decrypted then-action. Null when the row's key id is no longer in the map.
|
||||
target:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
description: Decrypted optional target; null when none / undecryptable.
|
||||
status:
|
||||
type: string
|
||||
enum:
|
||||
- proposed
|
||||
- active
|
||||
- met
|
||||
- abandoned
|
||||
description: "App-side lifecycle: proposed | active | met | abandoned."
|
||||
reviewDate:
|
||||
anyOf:
|
||||
- type: string
|
||||
format: date-time
|
||||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||||
- type: "null"
|
||||
description: Optional check-in checkpoint; null when none.
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||||
updatedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||||
required:
|
||||
- id
|
||||
- metric
|
||||
- ifCue
|
||||
- thenAction
|
||||
- target
|
||||
- status
|
||||
- reviewDate
|
||||
- createdAt
|
||||
- updatedAt
|
||||
additionalProperties: false
|
||||
description: One Coach goal / if-then plan, decrypted server-side. The free-text fields read null only when the
|
||||
encryption key id has rotated out of the map.
|
||||
CoachPlanUpdated:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
type: object
|
||||
properties:
|
||||
plan:
|
||||
description: The plan after the lifecycle update.
|
||||
$ref: "#/components/schemas/CoachPlan"
|
||||
required:
|
||||
- plan
|
||||
additionalProperties: false
|
||||
error:
|
||||
type: "null"
|
||||
meta:
|
||||
type: object
|
||||
properties:
|
||||
requestId:
|
||||
type: string
|
||||
additionalProperties: false
|
||||
required:
|
||||
- data
|
||||
- error
|
||||
additionalProperties: false
|
||||
CoachPlanDeleted:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
type: object
|
||||
properties:
|
||||
deleted:
|
||||
type: boolean
|
||||
description: True when a plan owned by the caller was soft-deleted; false for an unknown / cross-user / already-deleted
|
||||
id (idempotent no-op).
|
||||
required:
|
||||
- deleted
|
||||
additionalProperties: false
|
||||
error:
|
||||
type: "null"
|
||||
meta:
|
||||
type: object
|
||||
properties:
|
||||
requestId:
|
||||
type: string
|
||||
additionalProperties: false
|
||||
required:
|
||||
- data
|
||||
- error
|
||||
additionalProperties: false
|
||||
CoachReminderSuggestionResultOk:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
Reference in new issue
Block a user