# Adaptive Nutrition Workflow — Consensus Revision 14

## Status
Planning only; pending review, intent reconciliation, and explicit execution approval. This document incorporates Revision 13 and replaces ambiguous clauses identified by the pass-13 Architect and Critic. Product source remains unchanged.

## Chosen architecture
Use a separate customer-scoped append-only adaptive stream (`data/customers/<key>/nutrition-plans/events.jsonl`) and rebuildable index. Canonical check-in/trainer events are read-only observations. `CustomerSpec.plan` remains immutable baseline authority. A shared active-target resolver may select an explicitly approved/effective adaptive overlay without mutating the baseline. Deterministic code owns projection, eligibility, values, rounding, catalog selection, state, and delivery. LLM output is optional bounded prose only.

Gate A is schema/store/projector/shadow analytics with candidate values and delivery disabled. Gates B–D require user-confirmed policy values. D+1..D+28 may eventually activate/deliver; D+29..D+84 is shadow-only unless separately authorized; D+85+ is human_review.

## Source-day and correction fold contract
### Authoritative observation day
Add `observation_kst_day` to every new/updated canonical wizard event builder. It is captured from the WizardSession KST day at session start and remains unchanged across delayed save/correction. Legacy events lacking it use the root event's strict KST `occurred_at` date and are marked `legacy_day_derived=true`. A correction inherits its root's observation day; disagreement is invalid.

`d_plus = (observation_kst_day - plan.starts_on).days + 1` in Asia/Seoul. Dates before start have D+<=0 and are blocked. Fixtures include D+0, D+1, D+28, D+29, D+84, D+85.

### Per-root graph
Validate correction graphs per logical root, not per day. A correction follows `correction_of` transitively to exactly one root. Orphan, cycle, fork with multiple terminal heads, root/flow/day/customer mismatch, or digest error invalidates that root and blocks candidates. Independent roots on the same day are normal.

### Per-flow terminal record
For each `(customer, observation_day, flow, root_id)`, use the sole valid terminal correction head. Fold roots by flow, then merge fields with explicit authority:
- `MORNING`: bodyweight, sleep duration/quality, readiness/condition, appetite, stress, digestion/Bristol when collected.
- `NUTRITION`: calories, carbs, protein, fat, Meal entries, nutrition appetite/digestion/Bristol; it does not overwrite MORNING bodyweight/sleep.
- `WORKOUT`: customer-performed training evidence only.
- `TRAINER_SESSION`: coach-recorded schedule/session/performance evidence; it does not overwrite customer nutrition or bodyweight.
- `SAFETY_AUDIT`: union typed SafetyReason values; any held reason dominates all flows.

If two valid roots within the same authoritative flow/day provide the same singleton field with different values, mark `source_collision`, retain both source IDs, and block that metric/candidate. Missing stays missing; numeric zero stays zero. Complete nutrition day requires the terminal NUTRITION fold to contain calories and all three macros and pass internal energy tolerance. Normal morning+nutrition dual-flow, independent corrections, delayed-save, collision, fork/cycle/orphan, missing/zero fixtures are mandatory.

## Canonical wire and identity
Schema uses strict tagged payload unions and enums. Decimal wire values are canonical base-10 strings with fixed field scales; no exponent, NaN, or infinity. Calculations use unrounded Decimal values; display values are separately ROUND_HALF_UP. Energy is integer kcal, macros integer grams only after final compiler allocation.

Canonical bytes are UTF-8 JSON with sorted keys/compact separators. Each digest preimage excludes its own ID/digest/signature fields and includes `schema_version`, type, customer, source IDs/digests, policy/formula/catalog/baseline versions, revision, and payload. IDs are `sha256(type + ':' + canonical_preimage)`. Baseline version is SHA-256 of canonical immutable `CustomerSpec.plan`. Policy/catalog self-digests likewise exclude only their `digest` and approval signature fields. Callback data is `an1:<43-char base64url random token>` (47 bytes); token maps server-side to action/customer/revision/epoch and stays under Telegram's 64-byte limit.

Enums include outcomes `maintain|observe|calorie_adjustment_candidate|macro_redistribution_candidate|human_review`, confidence `insufficient|low|moderate|high`, and every lifecycle/delivery event listed below. `PolicyConfigV1` explicitly includes `emergency_weekly_rate_abs_percent`.

## Projector and confidence
Current window `[D-6,D]`, prior `[D-13,D-7]`, inclusive KST dates. Weight uses one folded MORNING weight per day. Mean/rate decisions use full Decimal precision; cards show two decimals. No imputation or silent outlier removal.

Confidence: insufficient on hard-integrity/authority failure or <4 current weight days; low with 4–5 current or <10/14 total; moderate with >=6 current, >=10 total, >=5 complete nutrition days, no unresolved anomaly; high with 7 current, >=12 total, 7 complete nutrition days, no anomaly. Safety always overrides confidence.

Adherence per metric is the mean of daily `observed/active_target*100` over complete observed days only; denominator and missing-day count are displayed. Overall adherence gate requires every policy-designated controlling metric to meet threshold. Missing days never count as zero. Cooldown anchors at the latest `adaptive_plan_effective.effective_from` that changed calories; proposals do not start cooldown. Protein g/kg uses the current-window full-precision mean weight; missing weight makes g/kg mode infeasible.

Hard-gate precedence: schema/correction → activation/consent/customer → typed safety/eating risk → policy/catalog/feature epoch → date permission → samples → adherence/cooldown → feasibility.

## Numeric compiler
All numeric settings are disabled until user approval. Observed rate is `(current_mean-prior_mean)/prior_mean*100`. Inside goal band => maintain. Outside with inadequate adherence/confidence => observe. Otherwise apply one configured calorie step toward the target, clamp by max absolute step, then calorie floor/ceiling; no valid change => human_review.

"One variable" means one policy decision changes total daily energy. Protein/fat are recomputed consequences under the same pinned policy; carbs absorb remaining energy. Protein uses configured absolute target or g/kg current mean and is clamped/rounded first. Fat is clamped/rounded second. Carbohydrate grams are solved last. All configured gram values and rounding steps are integers.

Daily exactness contract: `calories = 4*carbs + 4*protein + 9*fat`; therefore candidate calories must be feasible on the integer lattice. The compiler searches bounded allowed protein/fat values and carb grams in deterministic lexicographic distance from targets, constrained by calorie/macronutrient bounds. If no exact integer solution exists, human_review; it never inserts a fractional residual. Weekly total is the sum of seven exact daily totals.

Training category counts derive from seven dated schedule rows; high/medium/low category and priority are policy-configured. Daily allocations are chosen by a deterministic constrained search minimizing distance from configured category shares, tie-breaking high-priority training date, category rank, then chronological date. It must satisfy each exact daily calorie equation, weekly calorie target, macro bounds, and configured tolerance. No post-hoc residual mutation. Golden vectors cover boundaries, clamps, lattice infeasibility, ties, and cooldown.

## Active-target resolver
`resolve_active_nutrition_target(customer, kst_day)` is the sole target API used by reporting, adherence, grounding, cards, activation checks, and delivery validation.
1. Load immutable baseline plan/version.
2. Rebuild valid adaptive stream at current feature epoch.
3. Select at most one nonexpired, nonsuperseded `adaptive_plan_effective` whose revision digest, policy/catalog/baseline digests, consent, activation, safety, and effective interval remain valid.
4. If none, return baseline. Multiple overlays, graph error, revoked authority, or digest mismatch return blocked—not an arbitrary target.
5. Expiry, supersession, rollback epoch, or explicit deactivate returns baseline for future dates while preserving history.

## Compiler-constrained edits
Operators cannot replace numeric or meal payloads directly. `PlanOverrideV1` allows only bounded typed choices already permitted by policy: choose one compiler-produced alternative ID, select enabled catalog swap IDs, adjust effective date inside allowed window, and edit prose-only operator note. Every edit reruns the same snapshot gates, numeric compiler, meal compiler, and explanation renderer under current digests, producing a complete child revision. Direct calories/macros/portion/food/free-form explanation mutation is rejected. Approval revalidates exact compiler output and all live digests.

## Korean-safe rendering
LLM never renders food labels, portions, Meal quantities, dates, D+, or numeric values. Deterministic components render those structured fields. LLM may emit sentence fragments from a digit-free allowlisted vocabulary around opaque semantic placeholders; renderer substitutes complete deterministic clauses. Validator rejects any Hangul number word/measure phrase, Arabic/Unicode digit, nutrition unit, food alias, undeclared placeholder, or unknown lexical token. Tests include 세/두/한/반/몇, 공기/조각/스푼/그램, mixed Unicode digits, ranges, and spacing bypasses. Model failure uses a deterministic template.

## Live-state precondition
One coordinator method `revalidate_adaptive_transition(customer, revision_id, action)` runs immediately before edit, hold/release, approve, activate, reserve, transport, reconcile, and sent audit. It reloads committed registry/activation, consent, typed safety, current correction heads/projected source digest, baseline, policy/catalog digests, feature epoch, destination and exact topic. It recompiles the revision and compares canonical digest. Any change appends `plan_held` or `plan_superseded` with reason and prevents the transition. Tests inject each change between every lifecycle edge and cover concurrent races under the same lock.

## Lifecycle transition table
All transitions append adaptive events with actor, exact prior state/revision/digest, idempotency key, feature epoch, timestamp, and reason/result.
- shadow snapshot → `plan_proposed` only when Gate B enabled.
- proposed/revised → `plan_edited` via typed override and recompilation.
- proposed/revised → `plan_held`; held → `plan_released` creates child revision.
- proposed/revised → `plan_approved`; stale digest/idempotent replay rejects or returns same receipt.
- approved → `adaptive_plan_effective` after resolver exclusivity and live revalidation.
- effective → `delivery_reserved`; concurrent reservation returns existing reservation.
- reserved → `delivery_attempt_started` persisted before transport.
- crash before attempt-start permits cancellation/replacement reservation after revalidation.
- confirmed transport failure → `delivery_failed_confirmed`; explicit operator action may create `delivery_replacement_authorized` with new delivery ID.
- ambiguous provider result → `delivery_unknown`; no automatic resend. Explicit receipt lookup/reconciliation may append `delivered` or `delivery_confirmed_not_sent`.
- confirmed-not-sent permits operator-authorized replacement after revalidation.
- provider receipt conflict → held/human review.
- delivered → exactly one idempotent `sent_audited`.
- consent/safety/authority loss at any point appends held/superseded; no transport.

Terminal/projected state is derived only from adaptive events. Mutable transport storage is an execution cache, never authority.

## Adaptive transport adapter
Introduce `AdaptiveDeliveryRequest` and `AdaptiveDeliveryAdapter` around the generic receipt-returning Telegram transport—not the legacy DraftSelection API. Input: delivery ID, customer/destination, approved effective revision ID/digest, deterministic rendering digest/body, actor, feature epoch. Under one lock, adapter calls live revalidation, appends adaptive reservation/attempt events, invokes transport at most once, persists provider message receipt before `delivered`, then appends sent audit. Crash/recovery follows the transition table. Existing low-level transport and destination guards are reused; mutable legacy draft lifecycle is not fabricated. Crash matrix tests every boundary.

## Topic 59 and config
Exact route `(8693203710,-1004290459350,59)`, no fallback. Active config is the deployed config file; template is `activation/telegram-config.template.yaml`. Add typed `operator_review` with owner/chat/topic, `feature_epoch` monotonic integer, `analytics_shadow`, `operator_candidates`, `activation`, `delivery`, policy path/digest, catalog path/digest. Rollout increments epoch; rollback disables flags and increments epoch again, making old cards stale. Parents/directories mode 0700; files and locks 0600.

## Privacy and consent withdrawal
Immediate revocation blocks projection, mutation, cards, and delivery through live revalidation. The current manual runbook procedure remains the approved mechanism; the plan does not claim an automated customer-delete command. Update its inventory to include adaptive event stream, derived index, transport cache/outbox entries, Telegram cards where deletable, operator exports, active backups, and restored copies. Record owner, legal-hold decision, retention deadline, deletion method, backup expiry, verification digest/counts, and a PII-free immutable deletion receipt. Shared indexes/outboxes remove only the customer's entries atomically without rewriting append-only audit evidence; audit payloads retain IDs/reason codes only. Restore procedure must reapply tombstones before service start. Delivery enablement requires current G5 withdrawal/deletion and retention/backup evidence covering these paths.

## Files and order
Profile: new schema/models/store/projector/policy/meal catalog modules; additive observation-day builder changes; active-target resolver in customer reporting/grounding; parity console. Gateway: adaptive coordinator/adapter in `nutrition_coaching.py`, `an1:` cards in `telegram.py`. Config/template/runbook/HANDOFF updated. Order: contracts → observation-day compatibility → store/graph → projector → resolver → disabled policy → meal compiler → safe renderer → lifecycle/adapter → topic UI → migration/docs.

## Migration and rollback
Preflight checks backup, permissions, source graph, config/policy/catalog digests and manual privacy evidence. Additive/idempotent migration creates 0700 parents and 0600 stream/index/lock, records source/output counts and canonical digests, and writes no effective plan. Feature epochs are durable in config plus adaptive epoch event. Rollback increments epoch and disables delivery→activation→candidates→analytics; old cards reject. Old binaries ignore the separate stream. Partial lines, bad digests, lock contention, restore+tombstone, and canonical rebuild byte equality are tested.

## Verification and acceptance
Focused profile tests cover schemas/IDs/digests, ordinary morning+nutrition same day, delayed saves, per-flow corrections, fork/cycle/orphan/collision, missing/zero, D+0/1/28/29/84/85, 3/4 and 9/10 weight, 4/5 nutrition, confidence, adherence missingness, g/kg reference weight, exact-lattice compiler, category ties, policy/catalog self-digest negatives, compiler-constrained edits, Korean numeral bypasses, resolver expiry/supersession/rollback.

Gateway tests cover all feature-flag combinations, callback <=64 bytes, topic/auth negatives, stale epoch/digest, every live-state intervening change, hold/release/edit/reapprove, concurrent activation/reservation, definite/no-attempt/unknown/confirmed-not-sent/receipt-conflict recovery, replacement authorization, one attempt/no blind resend/one sent audit, and console parity.

Migration/privacy tests cover permissions, idempotency, corruption, deletion inventory, backup expiry, restore tombstone, and no PII logs. Full profile and gateway suites plus compileall run. Manual Telegram proof is required before delivery enablement.

Acceptance: same source/versions produce byte-identical rebuilds; ordinary multi-flow days project correctly; all hard gates suppress candidate numbers; resolver has one unambiguous target; every operator revision is compiler-valid; deterministic structured rendering prevents invented quantities; exact integer macro/energy invariants hold or human_review; adaptive lifecycle alone reconstructs delivery state; no customer receives an unapproved/stale revision; disabled mode preserves current flows.

## Premortem
Sparse/noisy data causes unsafe cut → gates/shadow. Cross-flow merge rejects normal day → explicit per-flow fold/golden fixture. Operator edit bypasses compiler → typed override/recompile. LLM invents Korean quantity → structured deterministic rendering. Correction/consent changes after proposal → live transition revalidation. Wrong/duplicate delivery → exact route, adaptive adapter, no blind resend. Deletion gap → explicit manual inventory/backup tombstone evidence.

## Intent reconciliation still required before Gates B–D
1. Goal mode and target weekly weight-rate range.
2. Adherence threshold/tolerance, calorie step/max/floor/ceiling, cooldown, emergency bound.
3. Protein mode/range, fat range/floor, integer rounding/tolerance.
4. High/medium/low schedule categories and policy shares.
5. Meal count/preferences/allergies/restrictions/budget/cooking access/catalog owner.
6. Whether D+29..D+84 must remain shadow-only.
