🧩 코칭 엔진 모듈 명세 · ⑤/⑦

📈 ⑤ 진척·커리큘럼 레이어

설문 없이 기록만으로 효과를 수치화하고, 12유닛 사다리·상태머신으로 행동을 가르친다

← 문서 허브 · 통합 아키텍처 지도 · 코칭 엔진 모듈 명세
🗺️ 이 문서는 통합 아키텍처 지도의 모듈 ⑤를 코드 단위로 펼친 명세입니다 — 비개발자 설명·구현·아키텍처 시각화·작동 예시·남은 문제. 코드 근거: lib/curriculum.ts·lib/curriculumUnits.ts·lib/coachWeekly.ts (2026-06-20 실측).
🧭

① 비개발자용 — 이 모듈이 무엇을 왜 하나

이 모듈은 편식 코칭을 '한 번의 조언'이 아니라 12개 수업으로 짜인 커리큘럼처럼 굴립니다(curriculumUnits.ts). 마치 학교 통지표처럼, 각 아이가 12개 수업(압박 내려놓기, 공복 리듬, 식탁 무대, 새 음식 노출 등) 중 지금 어디까지 와 있는지를 '시작 전 → 진행 중 → 유지 → 졸업'이라는 단계로 추적합니다. 각 수업에는 1·2단계 사다리(쉬운 행동 → 다음 행동)와 '이 정도면 통과'라는 판정 기준(passWhen), '다시 나빠졌다'는 재발 기준(relapseWhen)이 들어 있습니다. 중요한 점은 이 판정에 AI(LLM)를 전혀 쓰지 않는다는 것입니다. 전부 부모가 입력한 식사 기록과 칩(버튼) 답변을 순수 계산으로만 채점합니다.

작동 방식은 이렇습니다. 매일 한 번, 코칭 크론(자동 작업)이 어제까지의 식사 기록을 불러와 각 수업별로 '어제 그 행동을 했는가'를 1(했음)·0(안 했음)·판단보류(기록 없음)로 채점합니다. '기록이 없다'를 '안 했다'로 단정하지 않는 것이 핵심 원칙이라, 표본이 모자라면 함부로 점수를 매기지 않고 보류합니다. 보류가 생기면 코칭 편지 끝에 '어제 식탁 분위기 어땠어요?' 같은 칩 질문을 끼워 부모 답을 받아 표본을 채웁니다(probes). 모든 카운트는 '몇 번'이 아니라 '며칠'로 셉니다(같은 날 여러 끼 기록해도 하루로 묶어 부풀림 방지).

한 수업이 '통과 기준'을 일정 주(holdWeeks) 동안 채우면 다음 단으로 올라가고(전진), 마지막 단까지 채우면 한 주 '유지'를 본 뒤 졸업(mastered)합니다. 반대로 며칠을 코칭했는데도 신호가 0이면 '가르쳤는데 안 됨(정체)'으로 보고 다른 각도(다른 수업)로 피벗합니다. 그리고 너무 오래(18일) 한 수업에 매달려 진전이 없으면, 주당 1회 피벗 제한을 무시하고 강제로 다른 수업으로 전환(hardStall)합니다. 같은 수업을 이틀 연속 채근하지 않게 주당 피벗 횟수도 1회로 제한합니다.

🥗 이 모듈이 영양·편식 정보를 다루는 원칙
결핍 판정: '잘 먹는 식재료'는 28일 안에 2회 이상 나오고 거부 목록에 없는 식재료로 셉니다(1회짜리나 거부한 건 안 침). 반복노출(노출 적금): 새 음식은 '말없이 식탁에 아주 조금씩' 올리는 걸 1단계로 보고 주 2회 이상 노출을 하한(exposeWeekly:2), 누적 8회 이상을 다음 단계 기준으로 잡습니다(exposeTotalForStep2:8 — 노출 효과 이론을 수치화). 노출 일수를 평생 누적해 모읍니다(폭주 방지로 최근 60개까지만). 괴식·위험 차단: 질감 수업은 'puree→mashed→finger→table 한 단계만 위로, 거부하면 즉시 후퇴'를 규칙으로 둬 무리한 점프를 막습니다. 어린이집 거부를 집에서 재시도하는 '연계' 수업은 거부 메모를 정제(cleanRefusal)해 부모 메모가 음식으로 오인되는 오염을 막습니다. 강요 금지: 압박·배부름·협상·식탁의 말 수업이 '한 입만/다 먹어/억지로' 강요 신호와 '먹으면 ~줄게' 보상 거래를 잡아내, 그게 0이 되어야 통과로 칩니다.
🧒 운영 예시(아린·5세) — 아린(콩류·채소가 적게 들어옴·소고기/계란은 잘 먹음·미역국 좋아함). [입력] 부모가 기록한 끼니에 콩류·채소가 거의 안 보이고, 어린이집 식단표엔 시금치 거부가 찍힘. [처리] 신호선택기①가 결핍을 보고 '새 음식 조금씩 노출' 유닛을 후보로 올리고, 부모 메모에 '한 입만/다 먹어'가 있으면 '압박 내려놓기'도 후보로. 이 모듈⑤이 매일 어제 기록으로 '새 음식을 식탁에 올렸는지'를 1/0/보류로 채점하고, 표본이 모자라면 편지에 칩 질문을 끼워 보충. 어린이집 거부 시금치가 4일 안에 집 메뉴로 다시 등장하면 '연계·리듬' 유닛이 +1. [결과] 한 수업이 18일째 진전 0이면 강제로 다른 각도(잘 먹는 미역국에 두부를 곁들이는 '음식 다리')로 피벗. 효과측정(computeProgress)은 최근 28일 vs 직전 28일을 비교해 '잘 먹는 식재료 종 수'가 늘고 거부율이 줄면 '편식이 좋아지는 중이에요'를 부모에게 보여줌.
⚙️

② 구현된 내용 — 함수·데이터흐름·입출력 (실측)

이 모듈은 코드상 세 파일에 산다: curriculumUnits.ts(12유닛 레지스트리·임계 상수·신호 추출기), curriculum.ts(진도 상태머신 — evolveRow·advanceProgress·pickPivot·fallbackPivot), coachWeekly.ts(candidateUnits·applyFocusFatigue·enrichWeeklyPlancurriculum 블록 산출).

A. 12유닛 레지스트리 — curriculumUnits.ts:94-416

UNITS: Record<UnitId, UnitDef>(L94) — 12개 유닛 각각이 { id·label·lever·minWeek·steps[]·probes[]·extract()·relapseWhen()·trigger() }. 전부 순수 함수, LLM 0콜, 모든 카운트는 '날짜' 단위 dedup(dates() 헬퍼). UNIT_IDS(L418)는 키 배열. 임계 상수는 전부 TH(L51-70)에 모아 리플레이로 보정: stallDays:6·coachedDaysForStall:3·maintenanceWeeks:1·relapseWindowDays:14·maxPivotsPerWeek:1·hardStallDays:18(이번 세션 신규)·focusMaxStallWeeks:2·exposeWeekly:2·exposeTotalForStep2:8.

유닛(id·lever·minWeek)사다리 step1 → step2 (passWhen)trigger 발동 신호
pressure-off · mixed · 1압박 메모 0일 → 부정 칩비율 ≤ 0.2pressureMemoDays≥2 → 2+
hunger-rhythm · environment · 1끼니 전 간식 0일 → 과식간식일 ≤ 2snackHeavy/preMeal ≥2 → 2
table-stage · environment · 1식탁비율 ≥ 0.4 → ≥ 0.6envBadPct≥0.4 & count≥4
exposure-savings · food · 2주간 노출 ≥ 2일 → 누적 hitDays ≥ 8missing>0 ‖ refused>0 → 1.5+
fullness-respect · mixed · 2완식강요 0일 → 30분초과율 ≤ 0.3mtOver30>0.5 & count≥4
parent-model · mixed · 2가족저녁 ≥ 3일 → ≥ 5일0 (Sonnet 재량·폴백만)
no-bargain · mixed · 2거래 메모 0일 → 0일+중립디저트 1회bargainMemoDays≥1 → 1.5+
table-talk · mixed · 3금지어 0일 → 객체대화 ≥ 0.7pressureMemoDays≥1 → 1
sensory-texture · texture · 3texModeIdx ≥ 2(finger) → ≥ 3(table)texLow & count≥3 → 2
food-bridge · food · 3주간 신규 ≥ 1 → ≥ 1(holdWeeks↑)newFood=0 & eaten>0 → 1.2
autonomy-part · autonomy · 3자기주도 ≥ 0.3 → ≥ 0.5selfPct<0.3 & count≥4 → 2+
link-rhythm · food · 3기관거부 집재노출 ≥ 1 → ≥ 2등원 & dcRefused>0 → 1.5

extract(rows, answers, today, opt)는 7일창 evidence를 산출하되 표본 부족이면 {signalToday:null}(판정 보류)를 반환합니다(minLoggedDays:3 등). probes[]는 표본 부족 시 편지에 띄울 칩 질문 — map에 칩 원문 → 신호 매핑이 들어가 있고, 칩 '원문' 비교가 규약입니다(L102 주석: 매핑 키로 비교하면 영원히 0이 되는 잠복 버그를 G-04에서 적발). CORE_ORDER(L424)는 코어 1→N 단일 소스 — 이번 세션(C)에 minWeek=3 트랙(texture·food-bridge·autonomy·link-rhythm)을 뒤에 편입해 3주차+ 아이의 확장 주 목표가 실제로 열리게 했고, coachWeekly 폴백·curriculum.fallbackPivot이 공용으로 참조합니다.

B. 진도 상태머신 — curriculum.ts

함수 (file:line)역할
evolveRow(p) · L35-93단일 유닛 하루치 진화. def.extract로 snapshot evidence 병합 → 상태별 전이. masteredrelapseWhen 연속 relapseWindowDays(14)relapsed(step=최종단-1 재개). maintenancemaintCoached==0&7주일 유지&pass=true → mastered. active/progressing면 사다리(passStreakDays/7 ≥ holdWeeks → step+1, 최종단 → maintenance). 이번 세션 신규: stallStreakDays(L89) = pass=true·step++이면 0 리셋, 그 외엔 coachedYesterday일 때만 +1(standby/코칭 전 적립 차단=거짓 강등 방지).
isProgressing / isStalled · L24-31진전 = 최근 stallDays(6) 내 1차 신호. 정체 = 무신호 stallDays+ 그리고 그 기간 코칭 일수 ≥ coachedDaysForStall(3) — '안 가르침'과 '가르쳤는데 안 됨'을 구분.
advanceProgress(p) · L158-246통합 상태머신(크론이 자녀별 하루 1회 호출 의도). ①mastered 전체 재발 스캔 → ②focus 활성화·진화(preEvolved 이중적립 가드) → ③standby 증거 적립(판정용) → ④오늘 전개 결정(DailyDecision{unit,step,mode,pivotTo}). mode: graduated→celebrate, maintenance→maintain, stepAdvanced→advance, progressing&!limping→deepen, stalled‖limping→pivot/observe. goalsAfter 반환(피벗이 goals.focus를 플립해야 영속 — 정적 goals가 다음날 되돌리는 버그를 B-26 리플레이가 적발).
limping/hardStall 분기 · L208-224이번 세션 핵심. limping = step 미전진 & passStreakDays==0 & coachedF≥3(가끔 약신호가 들어와 완전 무신호 isStalled엔 안 걸리지만 사다리 진전 0). hardStall = stallStreakDays ≥ TH.hardStallDays(18). hardStall이면 주1회 피벗 캡을 무시하고 pickPivot → null이면 fallbackPivot으로 강제 전환. limping이면 'deepen 위장' 금지하고 observe 고정(21일 deepen 고착 직접 원인 제거).
pickPivot(goals,progress) · L96-104standby goal 중 mastered/maintenance 아닌 첫 유닛.
fallbackPivot(focusId,progress,week) · L107-119이번 세션 신규. standby가 비어 pickPivot=null일 때 폴백 — CORE_ORDER에서 focus 이후(순환) 첫 '미이수 & minWeek≤주차' 유닛. 콜드스타트(goals standby 비어도) 음식 트랙 전환 보장. 순수·throw 0.
normalizeGoals / goalsOf · L122-147goals 정규화(상위 3·dedup·focus 1개 강제). 구닻(단일 lever) → LEVER_TO_UNIT으로 goal 승격(A-05 컷오버 호환).

C. 주간 후보·피로캡·plan_detail 산출 — coachWeekly.ts

함수 (file:line)역할
candidateUnits(p) · L142-162유닛 후보 산출 — UNITS[u].trigger(sig) + 재발 +3 / 전주 중단 +1, mastered·maintenance 제외, minWeek > week 제외(E-05 온보딩 게이트). 신호 0이면 CORE_ORDER 미이수 첫 유닛 폴백. 상한 5.
ONBOARDING_ARC · L166-170이번 세션 신규(C) — 온보딩 3주 아크: 1주차=신뢰 주(cap 1), 2주차=관찰 주(cap 2), 3주차=확장 주(cap 3). goalsCapForWeek(L172)가 위임. '온보딩 3주차 목표 없음'을 명명으로 실재화.
applyFocusFatigue(goals,history) · L178-189같은 유닛이 직전 2주(focusMaxStallWeeks) 연속 focus였고 그 2주 step 전진 0이면 3주째 강등(차순위 승격). focusHistory 비면 no-op.
enrichWeeklyPlan(synth,ctx) · L520-571이 모듈의 출력이 plan_detail.curriculum 블록(L560-568)으로 빚어지는 곳. focusUnit·focusLabel·standby[]·stalledOut·graduatedTo·lever 산출. graduatedTo = focus가 직전 주 focus(focusHistory[0])와 다르면 그 유닛(유닛 전환=진척 가시화). stalledOut = goals 중 status='stopped' 유닛.
거울·macro(인접 산출) · L464-516이번 세션 #5/#6/#7 신규 — mirrorLineFor 3변형 회전 + deficitDishFor 구체 dish 주입, buildMirrorSchedule(단일 결핍 격일 쿨다운=#5 거울 쿨다운, 결핍군 라운드로빈=#6 도입 회전), buildMacroTrack(저체중/성장더딤 탄단지 보강 = #7 macro, 격주 cadence). 진척 레이어와 같은 EnrichContext.goals를 공유하나 진도 판정 본체는 아님.

D. 상태 전이 매트릭스 (curriculum.ts:6-10 실측)

not_started ─활성화→ active ─신호→ progressing ─사다리(holdWeeks)→ step+1 …
            최종단 충족 → maintenance (코칭 중단·관찰) ─유지성공→ mastered
                                          └─신호붕괴→ active(재코칭)
active|progressing ─정체(stalled ‖ limping)→ pivoted (stop_reason=stalled|limping)
mastered ─relapseWhen 연속 14일→ relapsed (step=최종단-1 재개) ─활성화→ active
문헌·이론 근거(코드가 실제 구현)SOS Approach(질감 puree→mashed→finger→table 한 단계 위·거부 시 즉시 후퇴), Satter DOR(압박 내려놓기·배부름 존중), Satter Family Meals(가족저녁 3→5회), Wardle 2003 / USDA NESR(노출 누적 8회), 30분 룰(mealOver30Cap:0.3), WIC 2~3시간 간격(공복 리듬), food chaining(food-bridge 사촌 1축 변형 — ⑦ 네트워크 verifiedCousinsOf 연계), BMI 또래 퍼센타일(macroTrack 저체중/성장더딤).
🗺️

③ 설계 아키텍처 — 데이터 파이프라인 (그림)

입력 테이블 → 가공 함수 → 출력 컨텍스트키의 흐름을 그림으로. 굵은 화살표는 라이브 배선, 점선(⇢)은 미배선(read-only dead-end)입니다.

┌─ 입력 테이블 ──────────┐      가공 함수(순수·LLM0)          출력 컨텍스트키
│ meal_logs            │──┐
│  (slot·menus·refused │  │   ┌──────────────────────┐
│   note·environment·  │  ├──▶│ UNITS[u].extract()   │── evidence ──┐
│   ate_well·meal_time·│  │   │  (7일창·날짜dedup)   │              │
│   autonomy·texture)  │  │   └──────────────────────┘              ▼
│ coach_questions      │──┤                              ┌────────────────────┐
│  (context.unitProbe) │  ├──▶ parseProbeAnswers ──┐    │ evolveRow()        │
│ child_unit_progress  │──┤        (칩→ProbeAnswer)│───▶│  passWhen/relapse  │
│  (status·step·       │  │                        │    │  stallStreakDays++ │
│   evidence·          │  │                        │    └─────────┬──────────┘
│   last_signal_at)    │──┘                        │              ▼
│ weekly_plans.goals   │───────────────────────────┘    ┌────────────────────┐
│  (goals·ledger)      │                                │ advanceProgress()  │
└──────────────────────┘                                │  limping?hardStall?│
                                                        │  pickPivot /       │
   buildCandSignals(rows) ──▶ candidateUnits ──▶ goals  │  fallbackPivot     │
   (① 신호선택기가 호출)        applyFocusFatigue      └─────────┬──────────┘
                                                                  │
       ┌─────────────── 출력 ───────────────────────────────────────┘
                                     
  decision{unit,step,mode}   goalsAfter[]  ⇢⇢  weekly_plans.goals (upsert)
       │                                        [배선 약함]
       ⇢⇢  편지 부모행동 텍스트  [⚠ DEAD-END: curriculum_progress write ≈0건]

  growth_logs ──▶ bmiTrend / buildMacroTrack ──▶ macroTrack.phrase (홈·거울)
  meal_logs (28일) ──▶ computeProgress ──▶ progress(홈 진척카드 verdict)  [라이브]
  enrichWeeklyPlan ──▶ plan_detail.curriculum{focusUnit·standby·stalledOut·graduatedTo·lever}
읽는 법 — 라이브로 부모에게 직행하는 출력은 computeProgress(홈 진척 카드)plan_detail.curriculum(어드민 가시화)입니다. 반면 상태머신의 핵심 출력인 decision(오늘의 step·mode)과 curriculum_progress write는 편지에 닿는 배선이 약합니다 — ⑤가 12유닛 통지표를 '측정'하지만 그 step·mode가 편지 본문으로 흐르는 경로가 dead-end에 가깝습니다(§⑤ 천장 참조). 이번 세션에 goalsAfter 영속·plan_detail.curriculum 가시화는 배선됐으나, curriculum_progress 자체의 안정적 write/read 루프는 여전히 약합니다.
🧪

④ 작동 예시 — 아린(5세) before/after

실제 입력 → 이 모듈 통과 → 출력의 구체 한 케이스. 두 흐름을 보입니다: (가) 효과측정(computeProgress·라이브)과 (나) 상태머신 피벗(advanceProgress·hardStall).

📥 INPUT
// (가) computeProgress(rows, "2026-06-16") — 홈 진척카드
rows: [
 {log_date:"2026-06-15", ingredients:["소고기","감자","계란"],
  refused:null, ate_well:true,  duration_min:22},
 {log_date:"2026-06-14", ingredients:["미역","두부","계란"],
  refused:"콩나물", ate_well:true, duration_min:28},
 {log_date:"2026-05-20", ingredients:["감자","계란"],
  refused:"시금치", ate_well:false, duration_min:41},
 {log_date:"2026-05-18", ingredients:["소고기"],
  refused:"브로콜리", ate_well:true, duration_min:38}
]

// (나) advanceProgress — focus=table-stage 24일째
goals: [{unit_id:"table-stage", priority:1, status:"focus"},
        {unit_id:"exposure-savings", priority:2, status:"standby"}]
progress["table-stage"]: {
  status:"active", step:1,
  evidence:{ envTablePct7d:0.14,        // 임계 0.4 미달
             passStreakDays:0,
             stallStreakDays:18 },       // ⭐ 18일째
  last_signal_at:"2026-06-05" }
coachedDays:{ "table-stage":12 }
pivotsThisWeek: 1   // 주1회 캡 이미 소진
week: 6
📤 OUTPUT
// (가) computeProgress → progress(홈 카드)
{ hasComparison:true,
  metrics:[
   {key:"variety", recent:3, prior:1, unit:"종", improved:true},
   {key:"refusal", recent:33, prior:100, unit:"%", improved:true},
   {key:"enjoy",   recent:67, prior:50, unit:"%", improved:true},
   {key:"dur",     recent:28, prior:40, unit:"분", improved:true}],
  improved:4, total:4,
  verdict:"편식이 좋아지는 중이에요 👏" }

// (나) advanceProgress → BEFORE(hardStall 전·구코드)
//   limping → pivotsThisWeek(1) ≥ cap(1) → pickPivot 호출 안 함
//   → mode:"deepen"  ❌ 24일째 같은 식탁무대 deepen 고착

// (나) AFTER(이번 세션 hardStall:18)
//   stallStreakDays(18) ≥ hardStallDays(18) → 캡 무시
//   pickPivot(standby=exposure-savings) → "exposure-savings"
decision: { unit:"exposure-savings", step:1, mode:"pivot",
            pivotTo:"exposure-savings" }
goalsAfter: [{unit_id:"table-stage", status:"stopped",
              reason:"limping"},
             {unit_id:"exposure-savings", status:"focus"}]
✅ EARNED(이번 세션 실측 성과) — (가) 효과측정은 라이브로 작동: 아린의 잘 먹는 다양성 1→3종·거부율 100→33%가 '편식이 좋아지는 중이에요'로 부모 홈에 렌더됩니다. (나) hardStall:18+fallbackPivot 배선으로 24일 식탁무대 deepen 고착이 코드 레벨에서는 끊깁니다 — limping이 'deepen 위장' 못 하게 막고(L240), 캡이 소진돼도 18일이면 강제 피벗. goalsAfter가 focus를 플립해 다음날 되돌림도 막습니다.
⚠ 그러나(천장) — 위 (나)의 피벗 도착지가 exposure-savings(음식)이면 다행이나, standby도 비고 fallbackPivotCORE_ORDER 순환에서 다시 table-stage 계열로 회귀하면 '→table-stage' 제자리 피벗이 됩니다(랄프위검 실측: table-stage ↔ exposure-savings 핑퐁, 06-11≡06-18 메타 쌍둥이). 더 근본은 (나)의 출력 decision.step이 24일 내내 1단에 고착passWhen이 한 번도 충족 안 되어 step++가 0회(아래 §⑤).
🚧

⑤ 남은 문제점 · 해야할 일 (랄프위검 47점·2026-06-20 실측 천장)

연속성 평가 47점에서 드러난 이 모듈의 천장을 미화 없이. 각 항목: 증상 → 근본원인 → 해야할일(owner). 성과(EARNED)도 사실이면 명시하되, 실측 고착은 정직하게.

증상근본원인 (file:line · 분류)해야할일 (owner)
★ step이 24일 내내 1·2단 완전 고착 — 5일째→24일째 전진 0. passWhen이 단 한 번도 충족 안 됨. '다음:' 졸업 마일스톤 0회. 부모가 '제자리걸음'을 가장 직접 체감. curriculumUnits.ts:170-171(table-stage passWhen) · curriculum.ts:78-86
ROOT · 최우선
passWhen=envTablePct7d≥0.4인데 실측 0.14. evidence 0.4를 채울 양성 신호 입력 자체가 안 들어옴passStreakDays 영원히 0 → step++ 영구 0.
P0-D 신호 입력 게이트 완성(owner: 코칭엔진)child_daily_state 환경신호 입력 경로를 닫아 table-stage step 전진을 가능케. 지금은 피벗만 되고 졸업 0. 칩 답(ts-env '식탁에서 화면 없이')이 extract 표본으로 실제 적립되는지 end-to-end 검증.
table-stage ↔ exposure-savings 졸업 없는 핑퐁 — 진척 아닌 정체. 06-11≡06-18, 06-12≡06-19 7일 간격 메타 쌍둥이(같은 두 유닛 사이 왕복). curriculum.ts:107-119(fallbackPivot) · L223-239
DESIGN_GAP · high
pivot이 unit 토글만 수행하고 누적 진도 좌표가 없음. CORE_ORDER 순환이 두 미이수 유닛 사이를 무한 왕복. pivot 도착지가 출발 유닛과 동류면 '진척 좌표 미생성'.
단일 진행 좌표(누적 진도바) 신설(owner: 코칭엔진) — 유닛 토글이 아니라 'mastered 졸업' 경로를 가진 누적 좌표. fallbackPivot이 같은 stage로 회귀하면 '진척 좌표 미생성'을 버그로 분류(현재는 정상 흐름으로 통과). 핑퐁 카운터를 두어 N회 왕복 시 다른 lever 강제.
정체 해소 메커니즘이 정체를 못 끝내는 역설 — hardStall-18 강제피벗이 발동해도 step이 안 오름. 06-11엔 16일째 조기 발동 관찰(18 미만). curriculum.ts:222-227 · curriculumUnits.ts:57,89
CODE_BUG · high
pivot이 step++가 아니라 unit 토글만. hardStallDays:18 카운터 기준이 '유닛 연속 정체일'인지 '전체 일자'인지 혼선 — stallStreakDayscoachedYesterday일만 +1이라 달력일과 어긋나 16일째 발동 가능.
hardStall 카운터 기준 명문화(owner: 코칭엔진)stallStreakDays가 '유닛 연속 코칭 정체일'임을 주석·테스트로 고정하고, 16일 조기발동이 의도인지(코칭일 18≈달력일 21) 회귀 테스트로 확정. pivot이 누적 진도 좌표를 전진시키도록 §2 좌표와 묶기.
passWhen 충족 = P0-D 신호 의존인데 실데이터 미입력child_daily_state 미입력 → 모든 step 전진의 상류가 막힘(천장의 단일 뿌리 = '아린 확신liked 0개'와 동일 계열). curriculumUnits.ts:154-179(extract 표본 게이트) · route 배선
DESIGN_GAP · 최우선
extract가 표본 부족이면 null(보류)을 정확히 반환하나, 그 보류를 메울 환경/care 5단계 실데이터가 아직 안 쌓임. 측정기는 정상, 입력이 빈 것.
P0-D부터 착수(owner: 코칭엔진·다음 세션) — care 5단계·환경칩 실데이터가 쌓이면 대부분 자연해소. 그 전 단독 수정: advanceProgress 결과를 curriculum_progress에 안정 write(현재 ≈0건)하고 편지 부모행동 텍스트로 step.behavior를 흘려보내는 read 루프 완성.
✅ 이번 세션 EARNED(사실) — ⒜ hardStallDays:18+fallbackPivot+limping observe 고정으로 '21일 deepen 고착'의 코드 직접 원인은 제거(stalledTarget 21일보다 한 사다리 빨리 끊음). ⒝ CORE_ORDER에 확장 트랙 편입(C)으로 3주차+ 확장 주 목표가 실제로 열림. ⒞ ONBOARDING_ARC로 '온보딩 3주차 목표 없음' 명명 실재화. ⒟ stallStreakDays coachedYesterday 게이트로 거짓 강등 차단. ⒠ computeProgress 효과측정은 라이브로 부모 홈에 검증된 진척 렌더. ⒡ plan_detail.curriculum{focusUnit·standby·stalledOut·graduatedTo} 어드민 가시화 배선.
정직 요약 — 천장의 단일 뿌리는 '아린 확신 liked 0개 / P0-D 환경신호 미입력' 하나다. ⑤의 상태머신·임계·피벗 로직은 정밀하게 작동하지만, 그 측정기를 채울 양성 신호가 아직 안 들어와 step 전진이 영구 0이다. 다음 세션 = P0-D 신호 입력 게이트부터. 그게 닫히면 핑퐁·step 고착·hardStall 역설이 대부분 동시 해소된다(대부분 care 5단계 실데이터로 자연해소).