# Adaptive Nutrition Workflow — Consensus Revision 13

## Status and executable boundary
Planning only. The implementation is split into release gates. Gate A may build schema/projector/shadow analytics with all personalized numeric proposals and delivery disabled. Gates B–D require the user-confirmed policy values listed under Intent Reconciliation. No executor may invent them.

Current pilot authority remains days D+1..D+28. D+29..D+84 may compute operator-only shadow analytics, but cannot produce an effective candidate or customer delivery until a separately approved post-pilot policy enables that window. D+85+ is human_review only. The existing 12-week plan remains the sole effective source; adaptive plans are versioned overlays and never mutate CustomerSpec.plan.

## Principles / drivers / decision
Deterministic policy owns calculations; LLM explains only validated data; operator topic 59 explicitly approves; all lifecycle data is append-only; unsafe/sparse/corrupt data suppresses every candidate number. Drivers: safety/auditability, corrected-data correctness, operator usability.

Choose a plan-specific lifecycle in a separate versioned `nutrition-plans/events.jsonl`, while reusing the canonical check-in stream read-only and the existing delivery outbox implementation. This beats extending draft events because current approved drafts are intentionally immutable, and it preserves downgrade/read compatibility. Fixed explanation templates are a fallback inside this architecture, not an alternative generator. Status quo/observe-only is the rollout default.

## Authority ordering
1. Safety/consent/activation/runtime boundaries.
2. Existing public doctrine and its medical/extreme-behavior exclusions.
3. User-approved private `adaptive-nutrition-policy.json` numeric bounds.
4. Active 12-week plan baseline.
5. Deterministic snapshot/decision/proposal.
6. LLM explanation, which cannot change 1–5.
7. Operator approval.

Personalized numbers are labeled `operator-approved deterministic advisory`, never attributed to public doctrine or 최코치. Policy versions are approved only by the registered operator and content-digested. Public doctrine continues to prohibit autonomous/published prescriptions.

## File and symbol map
Profile package:
- `contracts/adaptive-nutrition-event.schema.json`: separate event stream schema.
- `checkin_cli/adaptive_nutrition_models.py`: enums/payload models/builders/canonical serializer.
- `checkin_cli/adaptive_nutrition_store.py`: append/lock/rebuild and revision graph validation.
- `checkin_cli/adaptive_nutrition_projection.py`: correction-aware observation and trend projector.
- `checkin_cli/adaptive_nutrition_policy.py`: gates, decision table, numeric compiler.
- `checkin_cli/meal_catalog.py`: catalog validation and Meal slot compiler.
- `checkin_cli/customer_coaching.py`: additive policy/catalog bindings only.
- `checkin_cli/customer_grounding.py`: explanation prompt from immutable proposal only.
- `checkin_cli/customer_reporting.py`: adherence/evaluation projection only.
- `tests/test_adaptive_nutrition_{models,projection,policy,meal}.py`, plus reporting/grounding regressions.

Gateway:
- `gateway/platforms/nutrition_coaching.py`: plan-specific coordinator/state machine and existing outbox adapter.
- `gateway/platforms/telegram.py`: `an1:` callback namespace and topic-59 cards.
- `checkin_cli/operator_console.py`: read-only parity plus same coordinator actions; no independent state.
- `tests/gateway/test_adaptive_nutrition.py`, existing Telegram/nutrition regressions.
- active `config.yaml` and config template: fail-closed `operator_review`, feature flags, policy/catalog paths.
- `PILOT_RUNBOOK.md`, operator guide/HANDOFF: shadow, approval, recovery, rollback.

## Canonical contracts
All IDs are SHA-256-derived opaque IDs; all digests use canonical UTF-8 JSON (`sort_keys=true`, compact separators, no NaN); decimal arithmetic uses Decimal and ROUND_HALF_UP.

`TrendSnapshotV1`: snapshot_id, customer_key, kst_day, d_plus, window `[day-6,day]`, prior `[day-13,day-7]`, correction_head_ids, source_digest, formula_version, generated_at, distinct_day sample counts, missing dates, current/prior weight mean kg (2 decimals), delta kg (2), delta percent (2), nutrition complete-day count, mean calories/macros, active-plan adherence, sleep/readiness means, training categories, Bristol counts, concern flags, confidence and reasons.

Same-day authority is the terminal valid correction head for that KST day. Fork/cycle/orphan/multiple heads => `invalid_correction_graph`, confidence insufficient, all numeric proposal fields null. Partial corrections are cumulative session snapshots; no inheritance is guessed from event payloads. Outliers are retained and flagged, never silently removed. Missing differs from numeric zero. Complete nutrition day requires calories plus all three macros; weight completeness is separate.

Confidence: insufficient if any hard gate fails; low at 4–6 weight days current or <10/14 combined; moderate at >=4/7 and >=10/14 with >=5/7 nutrition; high only >=6/7 weight and >=6/7 nutrition with no anomaly. Confidence never bypasses safety.

Adherence is recorded/target * 100 only where target exists; target tolerance is not inferred. `within_target` is unavailable until policy supplies a tolerance. Sample counts are distinct KST days.

`PolicyConfigV1`: policy_id/version/digest/effective range, pilot/post-pilot permissions, goal-specific target weekly rate min/max, calories min/max, protein min/max g/kg or absolute mode, fat min/max, max calorie delta, cooldown days, adherence threshold/tolerance, high/medium/low schedule and spread, meal count, allowed rounding step, approver/event/time. Invalid or contradictory config disables candidates.

`PolicyDecisionV1`: decision_id, snapshot_id/digest, policy_id/digest, outcome enum, hard_gate_failures, soft_reasons, confidence, numeric_fields_suppressed.

`PlanProposalV1`: proposal_id, revision=1, prior_revision_id null, snapshot/policy/catalog IDs+digests, active baseline version, effective dates, weekly calories, each day category/calories/carbs/protein/fat, exact weekly reconciliation, Meal slots and catalog items, explanation status/text, proposal digest.

`PlanRevisionV1`: revision_id, proposal_id, revision number, prior_revision_id, complete replacement payload, operator reason, digest. `held|approved|effective|superseded` events reference exact revision/digest. Edit after approval creates a new revision and leaves approval historical but inapplicable; reapproval required.

Dedupe keys include event type + customer + primary ID + revision/state. Storage: `data/customers/<key>/nutrition-plans/events.jsonl`, mode 0600, lock file, append+fsync. Derived latest indexes are rebuildable and never authoritative.

## Projection/gate precedence
Hard gates evaluated in order: schema/correction integrity; customer/activation/consent; typed safety/eating-risk; policy/catalog validity; date permission; sample gates; cooldown/adherence; feasibility. First failure is primary, all failures retained. Any first three failures suppress every metric shown beyond observations and every candidate number.

Boundaries: 3/7 weight => observe; 4/7 still no calorie candidate unless 10/14; 9/14 => observe; 10/14 passes sample gate; 4/7 nutrition => no macro/carb cycle; 5/7 passes. Day 28 permits candidate/effective/delivery; day 29–84 shadow only; day 85 human_review. Consent revoked at any stage invalidates buttons and delivery.

Concern detection is deterministic typed input: any existing SafetyReason, eating-risk classifier match, Bristol 1/7 repeated >=2 of 3 recorded days, sleep <=5h repeated >=2 of 3, readiness <=2 repeated >=2 of 3, or absolute weekly weight-rate beyond configured emergency bound => human_review. No diagnosis.

## Numeric algorithm
Blocked until confirmed config exists. Observed weekly rate = `(current7_mean-prior7_mean)/prior7_mean*100`. If inside configured target band => maintain. Outside band but adherence below configured threshold => observe. Outside band, adequate adherence, cooldown elapsed => delta candidate = configured fixed step toward target, clamped by max delta and calorie bounds. One variable changes per revision.

Protein target is clamped to configured mode/range, fat next, carbohydrates receive remaining calories: `(calories-4*protein-9*fat)/4`. Negative/infeasible remainder => human_review. Weekly target = daily baseline*7 plus approved delta*7. High/medium/low shares are policy-configured and sum to 7 days; category deltas must net zero against weekly total. Round grams to configured step; distribute remainder in deterministic order high training date, medium date, low date, then chronological date. Recompute calories and put final residual (within one rounding step only) into carbs on the earliest high day; otherwise infeasible.

Golden vectors are committed only after policy values are confirmed; include exact lower/upper rate, adherence, cooldown, calorie clamps, infeasible floors and remainder ties.

## Meal catalog/compiler
`knowledge/operator-approved-food-catalog.json` is versioned, digest-pinned and operator-approved. Row: food_id, Korean label, serving grams/unit, carbs/protein/fat, source/ref/date/confidence, allergens, restrictions, cooking/equipment, budget tier, convenience/eating-out flags, enabled. Unknown allergen/restriction compatibility is fail-closed.

Compiler requires customer meal count, preferences, allergies/restrictions, budget, cooking access, training time and digestion constraints. Each Meal slot has macro ranges, timing tag and food IDs/servings. Search is deterministic by constraints then absolute macro error then food_id. Default, equivalent swaps, and fallback must each reconcile within configured per-meal tolerance and daily totals; no match => `meal_plan_no_match`, no LLM completion. LLM may render only compiled IDs/servings.

## Explanation validator
Allowed numeric tokens are generated from proposal values plus date, D+, Meal indices, counts and approved percentages. Normalize Unicode digits, commas, decimals, ranges and Korean units. Every numeric token must map to an allowed `(value,unit,semantic field)`; ambiguity, unknown unit/value, altered range, invented food, >500 chars or malformed output rejects. Prompt injection in check-in/catalog text is inert quoted data. Rejection/model outage uses deterministic Korean template and never changes proposal.

## Operator state machine
`shadow -> proposed -> revised* -> held|approved -> effective -> delivery_reserved -> delivered -> sent_audited -> evaluated -> superseded`. Hold records reason and hides approve/send until release creates a new revision. Approved cannot be edited in place. Effective activation is separate from customer send. Callback namespace `an1:<opaque-token>:<action>` with actions view/edit/hold/release/approve/activate/send/reconcile. Stale/replayed/wrong actor/topic/customer/digest callbacks reject with actionable text. Adaptive actions require configured topic 59 with no owner-DM fallback. Send visible only for approved+effective exact revision. Telegram and loopback console call identical coordinator methods.

External exactly-once claim is replaced by verifiable guarantees: one authorized transport attempt per reservation; ambiguous provider result remains pending and is never automatically resent; receipt reconciliation is explicit; exactly one canonical sent audit per delivery ID.

## Migration/rollback/privacy
Preflight validates backup, permissions, existing event/correction graph, config/catalog digests and topic authority. Dry-run rebuild records source count/digest and derived count/digest. Additive migration creates empty stream/index and is idempotent. No backfilled proposal/effective plan. Cutover flags: analytics_shadow, operator_candidates, activation, delivery. Stale cards include feature epoch and reject after cutover/rollback.

Rollback disables writers/actions in reverse order; readers retain new audit data. Old binaries never read the separate stream, preserving downgrade. Backup/restore and corruption tests cover partial final line, bad digest, permissions and lock contention. Consent withdrawal blocks processing/delivery and invokes existing retention/deletion process for personalized plan stream and backups; metrics contain only pseudonymous IDs/statuses.

## Release stages and exact exit gates
A Contracts/projector shadow: schema/model/store/projector tests and synthetic 14-day fixtures; zero writes outside separate stream; no candidate fields. B Policy/catalog: only after user-approved config/catalog; golden vectors pass and shadow mismatches against manual calculations are zero across fixture corpus. C Operator-only: topic-59 auth negatives, stale/concurrent callback tests, model numeric validator/fallback, no customer delivery capability. D Activation/delivery: explicit approval, outbox crash matrix, one attempt/no blind resend/reconcile tests and manual test-account receipt.

Commands: profile `python -m pytest -q tests/test_adaptive_nutrition_models.py tests/test_adaptive_nutrition_projection.py tests/test_adaptive_nutrition_policy.py tests/test_adaptive_nutrition_meal.py` then full `python -m pytest -q`; gateway `.venv/bin/python -m pytest -q tests/gateway/test_adaptive_nutrition.py tests/gateway/test_nutrition_coaching.py tests/gateway/test_telegram_physique_checkin.py` then full gateway suite; `python -m compileall -q` both trees. Manual proof captures topic 59 card, wrong-topic rejection, approved customer message and transport receipt without PII.

## Premortem/observability
Sparse data cut: prevented by gates and shadow. LLM invention: validator/fallback. Wrong/duplicate send: topic/digest/reservation. Additional: doctrine misattribution, policy/catalog corruption, correction fork, stale callbacks, deletion failure. Metrics: gate reasons, sample coverage, versions/digests, model rejection/fallback, operator states, outbox states and projection mismatches; no raw meals/PII.

## Acceptance
Deterministic canonical outputs; correction heads only; all hard gates suppress numbers; exact boundary fixtures; weekly/daily reconciliation; catalog-only plans; numeric-safe explanation; topic 59 only; append-only reconstruction; one attempt/no blind resend/one sent audit; disabled flags preserve current 198-profile/57-Telegram baseline.

## Intent Reconciliation Required
Confirm: (1) target weekly rate by goal, (2) calorie step/max/cooldown, (3) protein/fat bounds, (4) adherence threshold/tolerance, (5) high/medium/low spread and schedule, (6) meal count/catalog constraints, and (7) whether D+29..84 remains shadow-only. Until confirmed, only Gate A is executable.
