@ai-dev-methodologies/rlp-desk 0.15.3 → 0.15.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/README.md +34 -4
  3. package/docs/rlp-desk/failure-modes.md +191 -0
  4. package/package.json +10 -3
  5. package/src/node/MANIFEST.txt +3 -0
  6. package/src/node/prompts/prompt-assembler.mjs +2 -2
  7. package/src/node/run.mjs +70 -3
  8. package/src/node/runner/campaign-main-loop.mjs +97 -13
  9. package/src/node/util/debug-log.mjs +10 -6
  10. package/src/node/util/lifecycle-metrics.mjs +102 -0
  11. package/src/scripts/lib_ralph_desk.zsh +66 -0
  12. package/src/scripts/run_ralph_desk.zsh +23 -3
  13. package/docs/plans/bug-report-overhaul-backlog.md +0 -49
  14. package/docs/plans/bug-report-overhaul-v0.md +0 -238
  15. package/docs/plans/bug-report-overhaul-v1.md +0 -319
  16. package/docs/plans/native-agent-revert.md +0 -184
  17. package/docs/plans/polished-gliding-toucan.md +0 -234
  18. package/docs/plans/pr-e-phase-c1-blocked-recovery-hygiene-v0.md +0 -233
  19. package/docs/plans/spicy-booping-galaxy.md +0 -717
  20. package/docs/plans/strategic-review/rlp-desk-strategic-review.md +0 -125
  21. package/docs/plans/v0.15-stabilization-phase-a-prep.md +0 -130
  22. package/docs/plans/v0.15-stabilization-plan.md +0 -178
  23. package/docs/plans/v0.16-real-llm-sv-gate-spec.md +0 -177
  24. package/docs/rlp-desk/internal/verification-policy-gap-analysis.md +0 -523
  25. package/docs/rlp-desk/internal/verification-strategy-research.md +0 -2097
  26. package/docs/rlp-desk/plans/cozy-gliding-trinket.md +0 -53
  27. package/docs/rlp-desk/plans/frolicking-churning-honey.md +0 -253
  28. package/docs/rlp-desk/plans/keen-sauteeing-snowflake.md +0 -245
  29. package/docs/rlp-desk/plans/mutable-booping-corbato.md +0 -163
  30. package/docs/rlp-desk/plans/rlp-desk-0.11-handoff-7fixes.md +0 -352
  31. package/docs/rlp-desk/plans/rlp-desk-0.11.1-tmux-pane-disappearance.md +0 -260
  32. package/docs/rlp-desk/plans/rlp-desk-elegant-papert-agent-a8cd695ffca2a3ad8.md +0 -84
  33. package/docs/rlp-desk/plans/rlp-desk-elegant-papert.md +0 -270
  34. package/docs/rlp-desk/plans/rlp-desk-tmux-flywheel-routing.md +0 -730
  35. package/docs/rlp-desk/plans/toasty-whistling-diffie-agent-a6814625642e956da.md +0 -201
  36. package/docs/rlp-desk/plans/toasty-whistling-diffie.md +0 -117
  37. package/docs/rlp-desk/plans/validated-snacking-crayon.md +0 -204
  38. package/examples/calculator/.claude/ralph-desk/logs/loop-test/iter-001.worker-output.log +0 -0
  39. package/examples/calculator/.claude/ralph-desk/logs/loop-test/iter-001.worker-prompt.md +0 -38
  40. package/examples/calculator/.claude/ralph-desk/logs/loop-test/iter-001.worker-trigger.sh +0 -28
  41. package/examples/calculator/.claude/ralph-desk/logs/loop-test/session-config.json +0 -25
  42. package/examples/calculator/.claude/ralph-desk/logs/loop-test/status.json +0 -10
  43. package/examples/calculator/.claude/ralph-desk/logs/loop-test/worker-heartbeat.json +0 -1
@@ -1,260 +0,0 @@
1
- # rlp-desk 0.11.1 — Tmux session/pane lifecycle resilience (ralplan v3)
2
-
3
- > v3: Codex Critic ITERATE 흡수 (7 patches): 단일 5s 권위 timeout, mid-iter pane death 감지, SESSION_NAME `$$` + rand 충돌 회피, destroy-unattached 한계 명시, shasum 대체 체인, mkdir atomic lock, self-V mechanical fixture (grep-only 금지).
4
- > v2: Architect 1차 ITERATE 흡수 — ground-truth 검증으로 bug premise 수정. 실제 session-config.json pane 필드는 정상 기록, 진짜 결함은 tmux session 자체 사라짐.
5
-
6
- ## Context
7
-
8
- 소비자 handoff `coordination/handoffs/2026-04-26-rlp-desk-tmux-pane-disappearance-bug.md` (P0) + ground-truth 검증.
9
-
10
- ### 보고된 증상 vs 실제 ground truth
11
-
12
- | 항목 | 보고 | 실제 (검증 후) |
13
- |---|---|---|
14
- | session-config.json pane 필드 | 4 fields = null | leader=%1007, worker=%1016, verifier=%1017 (정상) |
15
- | tmux pane lifecycle | %1014/%1015 사라짐 | session `ai-blog-system-624` 자체가 사라짐 → 모든 pane 함께 사망 |
16
- | process state | 살아있음 | runner pid 83304 살아있음 (tmux session 만 dropped) |
17
-
18
- **진짜 문제**: tmux session 의 lifetime 이 wrapper terminal / claude-code session 의 lifetime 과 묶여 있어서, 외부 close 시 session 이 사망 → 모든 pane id 가 stale 됨.
19
-
20
- ### 검증 evidence (현재 시각)
21
-
22
- ```
23
- $ tmux ls | grep ai-blog
24
- ai-blog-system-625 (ai-blog-system-624 부재)
25
-
26
- $ cat .../blog-v31-flywheel-telemetry/runtime/session-config.json | jq .panes
27
- { "leader": "%1007", "worker": "%1016", "verifier": "%1017" }
28
- ```
29
-
30
- → pane id 는 작성 시점엔 valid 였으나 session 이 사라져 pane 도 dead.
31
-
32
- ## 근본 원인 (revised)
33
-
34
- **session lifecycle ↔ wrapper lifecycle decoupling 부족**:
35
-
36
- 1. **H1 (확인)** — runner 가 `tmux new-session -d -s "$SESSION_NAME"` 으로 detached session 생성. 그러나 wrapper 가 nohup 으로 spawn 시 wrapper 자신의 terminal close 가 자식 tmux client 도 함께 끊고, attached client 가 0 이 되면 일부 환경에서 session GC 됨 (특히 tmux server 재시작 / 사용자 manual kill).
37
- 2. **H2 (확인)** — wrapper duplicate spawn race (96581 + 83265) 로 두 wrapper 가 동일 desk 의 다른 mission 진입. 한 쪽 cleanup 이 다른 쪽 session 영향.
38
- 3. **H3 (가능성 낮음, 폐기)** — pane id 캡처 시점 race. 실제 file 검증 결과 pane id 는 valid → 캡처 자체는 성공.
39
-
40
- → H1 + H2 가 주범. 보고된 H3 (캡처 race) 는 실제 ground truth 와 모순되어 폐기.
41
-
42
- ## RALPLAN-DR
43
-
44
- **Principles**:
45
- 1. **Fail loud, not silent** — session/pane 사망 시 명시 alert (next iter 진입 직전 detect)
46
- 2. **Defense-in-depth** — H1 + H2 동시 차단 (단일 fix 부족)
47
- 3. **Backward-compat** — 기존 single-mission 인터랙티브 운영 그대로
48
- 4. **Self-verification mechanical** — 변경 코드 직접 invoke + grep anti-tautology
49
-
50
- **Decision Drivers**:
51
- 1. session 이 외부 영향으로 사라져도 wrapper / 사용자 가 즉시 인지
52
- 2. duplicate wrapper spawn 시 second-mover 가 명시 reject
53
- 3. 작성된 session-config 가 "live" 와 "stale" 구분 가능
54
-
55
- **Viable Options**:
56
-
57
- - **A (채택)** — 3-pronged + Architect ITERATE 흡수:
58
- - **R12 — Pane lifecycle monitor** — 3 검증 시점: (a) `create_session()` 직후, (b) main loop 매 iter 진입 직전, (c) 매 worker/verifier `send-keys` 직후 wait-loop 진입 직전. 각 pane `#{pane_dead}` + session `has-session` 확인. dead 발견 시 즉시 BLOCKED with `reason_category=infra_failure` + recoverable=true + suggested_action=restart. **단일 권위 timeout: 5s 총 — 1초 간격 5회 polling 후 fail (Critic 불일치 해소)**.
59
- - **R13 — Detached session protection** — RLP_BACKGROUND=1 이면 `tmux set -t "$SESSION_NAME" destroy-unattached off` 적용해 attached client 0 일 때도 session 유지. `tmux new-session` exit code 명시 검증, fail 시 dedicated 새 이름 (`${SESSION_NAME}-bg-$(date +%s)`) 으로 retry 1회. **NEW-3: SESSION_NAME 이미 SLUG 포함하므로 중복 suffix 안 함**.
60
- - **R14 — Project-scoped runner lockfile** — `RUNNER_LOCKFILE_PATH="$DESK/logs/.rlp-desk-runner-$(echo "$ROOT" | shasum | cut -c1-8).lock"`. 동일 project root 에서 duplicate runner spawn 차단, 다른 project 의 동시 runner 는 허용. stale pid (`kill -0` fail) 시 갱신 + log 안내.
61
- - B — R14 only (race 차단으로 충분) — H1 (session GC) 잔존 → 폐기.
62
- - C — skip background mode — wrapper API breaking → 폐기.
63
-
64
- **Pre-implementation gate (NEW-4)**: 본 plan 채택 전, 위 ground-truth 검증 (실제 session-config.json 파일 + `tmux ls` 출력) 완료. 실제 결함 = session 사망 + lockfile 부재 두 축으로 확정.
65
-
66
- ## 해결 계획
67
-
68
- ### Fix R12: Pane lifecycle monitor + bounded retry
69
-
70
- **대상**: `src/scripts/lib_ralph_desk.zsh` 신규 helper + `src/scripts/run_ralph_desk.zsh` main loop 진입점
71
-
72
- **변경**:
73
- 1. `lib_ralph_desk.zsh` 신규:
74
- ```zsh
75
- _verify_pane_alive() {
76
- local pane_id="$1"
77
- [[ -z "$pane_id" ]] && return 1
78
- local dead
79
- dead=$(tmux display-message -p -t "$pane_id" '#{pane_dead}' 2>/dev/null)
80
- [[ "$dead" == "0" ]]
81
- }
82
- _verify_session_alive() {
83
- local session="$1"
84
- [[ -z "$session" ]] && return 1
85
- tmux has-session -t "$session" 2>/dev/null
86
- }
87
- ```
88
- 2. `run_ralph_desk.zsh` 3 검증 시점에 helper 호출:
89
- ```zsh
90
- _r12_check_lifecycle() {
91
- local site="$1" # "create" | "iter_start" | "post_send"
92
- local _attempts=0
93
- while ! _verify_session_alive "$SESSION_NAME" || \
94
- ! _verify_pane_alive "$LEADER_PANE" || \
95
- ! _verify_pane_alive "$WORKER_PANE" || \
96
- ! _verify_pane_alive "$VERIFIER_PANE"; do
97
- (( _attempts++ ))
98
- if (( _attempts >= 5 )); then
99
- log_error "[r12:$site] tmux session/pane dead after 5×1s polling (5s total budget). session=$SESSION_NAME panes leader=$LEADER_PANE worker=$WORKER_PANE verifier=$VERIFIER_PANE"
100
- tmux list-panes -a -F '#{session_name}:#{pane_id} dead=#{pane_dead}' 2>&1 | head -20 >> "$DEBUG_LOG"
101
- write_blocked_sentinel "tmux session/pane dead during $site" "${CURRENT_US:-ALL}" "infra_failure"
102
- exit 1
103
- fi
104
- sleep 1
105
- done
106
- }
107
- ```
108
- 호출: `create_session` 끝, main loop 진입 직전, 모든 `paste_to_pane`/`send-keys` 직후 wait-loop 시작 전.
109
- 3. **단일 권위 timeout: 5s 총** (5회 × 1s polling), 다른 모든 "3 retries"/"4s" 표현 제거.
110
-
111
- **검증 (us024)**:
112
- - AC1: `_verify_pane_alive` + `_verify_session_alive` helper 정의
113
- - AC2: create_session + main loop iter 진입 + post-send-keys 3 시점에서 caller 가 helper 호출
114
- - AC3: behavioural — 죽은 pane id fixture → exit 1 with `infra_failure` sentinel
115
- - AC4 (Critic): mid-iter pane kill fixture — worker pane 을 send-keys 직후 외부에서 kill → 다음 wait-loop 진입 시 R12 가 5s 안에 BLOCKED with `reason_category=infra_failure`
116
-
117
- ### Fix R13: Detached session protection + new-session exit-code verify
118
-
119
- **대상**: `src/scripts/run_ralph_desk.zsh:744` `create_session()`
120
-
121
- **변경**:
122
- 1. `tmux new-session -d -s "$SESSION_NAME"` 실행 후 즉시 `$?` 검증:
123
- ```zsh
124
- if ! tmux new-session -d -s "$SESSION_NAME" -x 200 -y 50 -c "$ROOT" 2>/dev/null; then
125
- if tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
126
- if [[ "${RLP_BACKGROUND:-0}" == "1" ]]; then
127
- # daemon mode: 충돌 회피 (Critic NEW-3: epoch + pid + rand 4-digit 까지 강화)
128
- SESSION_NAME="${SESSION_NAME}-bg-$(date +%s)-$$"
129
- while tmux has-session -t "$SESSION_NAME" 2>/dev/null; do
130
- SESSION_NAME="${SESSION_NAME}-$(awk 'BEGIN{srand();print int(1000+rand()*9000)}')"
131
- done
132
- tmux new-session -d -s "$SESSION_NAME" -x 200 -y 50 -c "$ROOT" || die "tmux new-session retry failed: $SESSION_NAME"
133
- fi
134
- else
135
- die "tmux new-session failed and session does not exist: $SESSION_NAME"
136
- fi
137
- fi
138
- ```
139
- 2. RLP_BACKGROUND=1 이면 새/재생성된 session 마다 즉시 `tmux set-option -t "$SESSION_NAME" destroy-unattached off` 호출 — attached client 0 일 때도 session 유지.
140
- **한계 명시 (Critic R13)**: 이 옵션은 best-effort. **수동 `tmux kill-session` 또는 tmux server 재시작에는 보호 안 됨**. 둘 중 하나가 발생하면 session 은 사라지며, R12 (lifecycle monitor) 가 다음 검증 시점에서 BLOCKED 처리한다.
141
-
142
- **검증 (us025)**:
143
- - AC1: `tmux new-session` 실패 시 dedicated 이름으로 retry 1회 (RLP_BACKGROUND only)
144
- - AC2: RLP_BACKGROUND=1 시 `destroy-unattached off` 호출 grep
145
- - AC3: SESSION_NAME 변경 시 session-config 의 session_name 가 최종 이름 반영
146
-
147
- ### Fix R14: Project-scoped runner lockfile
148
-
149
- **대상**: `src/scripts/run_ralph_desk.zsh:231` 부근 (LOCKFILE_PATH 정의)
150
-
151
- **변경**:
152
- 1. 신규 변수 — shasum 대체 체인 (Critic R14 portability):
153
- ```zsh
154
- ROOT_HASH=$(printf '%s' "$ROOT" | { shasum 2>/dev/null || sha1sum 2>/dev/null || cksum; } | awk '{print substr($1,1,8)}')
155
- RUNNER_LOCKFILE_PATH="$DESK/logs/.rlp-desk-runner-$ROOT_HASH.lock"
156
- RUNNER_LOCKDIR="${RUNNER_LOCKFILE_PATH}.d"
157
- ```
158
- 2. 기존 `LOCKFILE_PATH` (per-SLUG) 그대로 유지 — concurrent same-slug 차단
159
- 3. **mkdir atomic lock 패턴 (Critic R14 race fix)** — check-then-write race 차단:
160
- ```zsh
161
- if ! mkdir "$RUNNER_LOCKDIR" 2>/dev/null; then
162
- existing=$(jq -r '.pid' "$RUNNER_LOCKFILE_PATH" 2>/dev/null || echo 0)
163
- existing_slug=$(jq -r '.slug // "unknown"' "$RUNNER_LOCKFILE_PATH" 2>/dev/null || echo unknown)
164
- if [[ "$existing" -gt 0 ]] && kill -0 "$existing" 2>/dev/null; then
165
- log_error "duplicate rlp-desk runner detected on this project root. existing pid=$existing slug=$existing_slug, this attempt slug=$SLUG. exiting."
166
- echo " Recover with: rm -rf '$RUNNER_LOCKDIR' '$RUNNER_LOCKFILE_PATH' (after confirming pid $existing is not active)" >&2
167
- exit 1
168
- fi
169
- # stale: 다른 wrapper 가 이미 stale 청소 중일 수 있음 — atomic mkdir 재시도
170
- rm -rf "$RUNNER_LOCKDIR"
171
- mkdir "$RUNNER_LOCKDIR" 2>/dev/null || {
172
- log_error "failed to acquire runner lock after stale cleanup; another wrapper raced ahead. exit 1"
173
- exit 1
174
- }
175
- log " stale runner lockfile cleaned (pid $existing dead) — acquired"
176
- fi
177
- printf '{"pid":%s,"slug":"%s","root":"%s","started_at":"%s"}\n' \
178
- "$$" "$SLUG" "$ROOT" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$RUNNER_LOCKFILE_PATH"
179
- ```
180
- 4. cleanup trap 에서 own_slug 확인 후 `RUNNER_LOCKDIR` + `RUNNER_LOCKFILE_PATH` 둘 다 rm:
181
- ```zsh
182
- if [[ -f "$RUNNER_LOCKFILE_PATH" ]]; then
183
- own_slug=$(jq -r '.slug' "$RUNNER_LOCKFILE_PATH" 2>/dev/null)
184
- [[ "$own_slug" == "$SLUG" ]] && rm -rf "$RUNNER_LOCKDIR" "$RUNNER_LOCKFILE_PATH"
185
- fi
186
- ```
187
-
188
- **검증 (us026)**:
189
- - AC1: `RUNNER_LOCKFILE_PATH` 변수 정의 + project root hash
190
- - AC2: 동일 root 에서 alive duplicate runner → exit 1 + 명시 메시지
191
- - AC3: stale pid 시 lockfile 갱신 (no exit)
192
- - AC4: 다른 root (다른 hash) 의 동시 runner 는 허용 (multi-project parallelism preserved)
193
- - AC5: cleanup trap 이 own_slug 일치 시만 삭제
194
-
195
- ### Self-verification scenario (mechanical, real fixture)
196
-
197
- `tests/test_self_verification_0_11_1.sh` — **grep-only 금지 (Critic Self-V)**. 각 함수가:
198
- 1. 임시 desk fixture (mktemp dir + plans/PRD + memos/)
199
- 2. 실제 helper 직접 invoke (zsh -c source) 또는 mini runner 진입
200
- 3. 구체 process exit code + 생성된 파일 / log line 검증
201
- 4. anti-tautology 보조 grep — primary 가 아닌 secondary
202
-
203
- ```bash
204
- test_r12_pane_dead_blocks() {
205
- # 1) 가짜 dead pane id 로 _verify_pane_alive 호출
206
- # 2) tmux new-session 으로 alive session 만든 후 일부러 kill
207
- # 3) helper 가 false 반환하는지 + 호출자가 exit 1 + sentinel 작성하는지 확인
208
- zsh -c "source $LIB; _verify_pane_alive '%99999'" && fail "expected dead detection"
209
- # ... real fixture run + assert sentinel.md exists with reason_category=infra_failure
210
- }
211
- test_r13_session_disambiguation() {
212
- # 1) tmux new-session -d -s "test-session-fixture" alive
213
- # 2) RLP_BACKGROUND=1 + SESSION_NAME="test-session-fixture" 으로 create_session-like 진입
214
- # 3) 실제 새로 생긴 session 이름이 ${name}-bg-... 인지 + alive 인지 확인
215
- }
216
- test_r14_lockfile_duplicate_reject() {
217
- # 1) RUNNER_LOCKDIR mkdir
218
- # 2) ${LOCK}/pid file 에 alive pid 작성 (sleep & 으로 백그라운드)
219
- # 3) 두 번째 mkdir 시도 → exit 1 + stderr 에 "duplicate" 출력 검증
220
- }
221
- test_r14_lockfile_other_root_allowed() {
222
- # 1) ROOT=/tmp/r1 인 lockfile 존재
223
- # 2) ROOT=/tmp/r2 의 hash 가 다름 → 두 번째 mkdir 성공
224
- }
225
- ```
226
- 각 함수 종료 시 (a) exit code 검증, (b) 생성된 sentinel/log 파일 존재 확인, (c) 패치된 함수가 호출되었음을 grep 으로 secondary 증명.
227
-
228
- ## 변경 대상 파일
229
-
230
- ```
231
- src/scripts/run_ralph_desk.zsh # R12 caller, R13 create_session 가드, R14 lockfile
232
- src/scripts/lib_ralph_desk.zsh # R12 _verify_pane_alive, _verify_session_alive
233
- src/governance.md # §7e (lane 옆) 신규 §7h "Tmux session lifecycle"
234
- tests/test_us024_pane_lifecycle.sh
235
- tests/test_us025_session_disambiguation.sh
236
- tests/test_us026_runner_lockfile.sh
237
- tests/test_self_verification_0_11_1.sh
238
- ```
239
-
240
- ## 검증
241
-
242
- 1. **LOW** — `zsh -n`, `node --check` (~10s)
243
- 2. **MEDIUM** — us024–026 신규 (~30s)
244
- 3. **CRITICAL** — us017–023 + us012–016 + us001/us007 무손실 (~3min)
245
- 4. **자가검증 매핑** — 4 함수 mechanical anti-tautology
246
-
247
- ## ADR
248
-
249
- - **Decision**: R12 (pane/session monitor + bounded retry) + R13 (detached session protection + new-session verify) + R14 (project-root-hashed lockfile). Bug report 의 null-field 주장은 ground-truth 와 모순되어 폐기, 진짜 결함 (session lifecycle GC + duplicate wrapper) 에 집중.
250
- - **Drivers**: visual feedback 회복, duplicate wrapper 안전, multi-project 병렬 보존.
251
- - **Alternatives considered**: R14 only (H1 잔존), skip background (API breaking), `--isolated-session` flag (over-engineering).
252
- - **Consequences**:
253
- - 기존 single-mission 인터랙티브 영향 없음 (R13 dedicated 이름 retry 는 RLP_BACKGROUND only)
254
- - duplicate wrapper 시 second-mover 명시 차단, 사용자 명령으로 lockfile 복구 가능
255
- - 매 검증 시점 최대 5s 추가 (단일 권위 budget: 5×1s polling). 최선 케이스는 0s (첫 시도 alive).
256
- - 다른 project 동시 runner 는 hash 분리로 그대로 동작
257
- - **Follow-ups**:
258
- - tmux pane lifecycle dashboard
259
- - mission-level pane 격리 옵션 (`--isolated-session`)
260
- - bug-report contract: 다음번부터 consumer 가 evidence 파일 (실제 session-config.json + tmux ls 출력) 첨부
@@ -1,84 +0,0 @@
1
- # Architect Review — rlp-desk-elegant-papert (ralplan v3)
2
-
3
- 대상: `docs/plans/rlp-desk-elegant-papert.md` (314 lines, v3 초안)
4
- Scope: Architect 검토만. 신규 계획 작성 금지, 약점·누락·모순 표면화.
5
-
6
- ---
7
-
8
- ## 1. Steelman Antithesis (가장 약한 가정)
9
-
10
- **A. 카테고리 6종은 wrapper-friendly가 아니라 wrapper-confusing.**
11
- plan L60-66은 `spec/dep/context/infra/repeat/unknown` 6종을 채택하지만, L82-87 매핑표에서 verifier verdict 4종(spec/implementation/integration/flaky)을 강제로 6종에 욱여넣는다. 결과: ① `implementation→repeat` 매핑은 의미 변환 (코드 결함이 "재시도 누적"으로 둔갑) — wrapper가 `repeat`를 보고 "재시도 하라"고 잘못 결정할 위험. ② `dep`는 verifier verdict에서 절대 발생 안 함 (integration→dep 단방향). ③ `unknown` fallback이 plan L66 "반드시 매핑 시도"로 명시되지만, 6종 중 어느 것도 fit 안 되는 케이스(예: 사용자 취소, OOM)가 unknown으로 폭주하면 wrapper는 결국 unknown 한 버킷만 분기하는 degenerate 정책으로 귀결. **결론: 카테고리 6종이 정말 분류축으로 기능하는지, 아니면 verifier 출처와 leader 출처를 통일하려다 의미만 흐려졌는지 plan 안에서 답이 없다.**
12
-
13
- **B. Sentinel schema "2.0"은 backward-compat 보증이 아니라 마케팅 라벨.**
14
- plan L107-118 sentinel 라인 추가 순서: `BLOCKED:` → `Reason:` → `Category:` → `Schema-Version: 2.0`. 그러나 lib_ralph_desk.zsh:470 기존 파서는 `grep -m1 -E '^[Rr]eason:'`로 1번째 매치만 본다 — 무관. 진짜 위험은 **wrapper-side 1-line 가정 파서**(외부 컨슈머)와 `head -1` shell 패턴. plan은 "신규 필드 무시 가능"이라고만 적었지 wrapper 파서 깨짐 케이스의 grep/awk 패턴 enumeration이 없다. v1↔v2 sentinel을 한 디렉터리 안에 동시에 둘 수 있는 시나리오(replay 후 v2 sentinel + 잔존 v1 status.json)가 있는데, schema_version으로 분기 안내가 없다.
15
-
16
- ---
17
-
18
- ## 2. Real Tradeoff Tensions
19
-
20
- **T1. Sentinel multi-line 확장 vs 외부 wrapper grep 패턴.**
21
- campaign-main-loop.mjs:337의 writeSentinel은 이미 RC-1/2에서 2번째 줄 `Reason:`을 추가했다(L342-344 확인). plan은 3번째 줄 `Category:`, 4번째 줄 `Schema-Version:`을 더한다. 1줄→2줄 전환은 7 commits에서 이미 risk 흡수했지만, **3→4줄 확장이 매번 1줄 risk를 누적**한다는 점이 plan에 빠짐. 대안 옵션 B(`failure-classifier.json` 분리)를 L40에서 "두 파서 변경"이라며 탈락시키지만, 실제로는 sentinel 1줄 약속 + sidecar JSON 한 파일이 wrapper grep을 깨지 않는 minimum-surface가 될 수도 있다. plan은 이 tradeoff를 sentinel-내부 vs sidecar 둘 다 비교하지 않고 sentinel-내부만 채택했다.
22
-
23
- **T2. Schema version "field-presence" vs "explicit version".**
24
- plan L120 "v1 환경 wrapper가 v2 출력을 읽어도 신규 필드 무시하면 그만" — 이는 **field-presence detection**이고, L107-109 `schema_version: "2.0"` 명시는 **explicit version**이다. 두 모델을 동시에 쓰면 파서가 "schema_version 미존재 = v1 vs schema_version=2.0 = v2"인지 "필드 존재 여부로 판정"인지 헷갈린다. migration script(L118 `migrate-state-v1-to-v2.zsh`)는 explicit-version을 가정하는데, lib L470의 정상 코드는 field-presence(`grep -m1 Reason:`)에 가깝다. **두 모델을 plan 안에서 골라야 한다.**
25
-
26
- **T3. Lane enforce를 informational WARN으로 두면 self-verification gate가 못 잡는다.**
27
- plan L135 mtime audit를 "informational WARN, BLOCKED 아님"로 결정. CLAUDE.md "Self-Verification Gate" CRITICAL 시나리오는 PASS/FAIL만 판정하는데, WARN은 어느 쪽인가? plan us016(L258)은 "mtime WARN 이벤트 fixture"만 검증하고 worker가 PRD를 수정한 시나리오를 BLOCKED으로 전이시키지 않는다. 결과: lane 위반이 governance §7¾ "위반 시 BLOCKED" 약속과 plan §1f L132 "위반 시 IL-2 감점, leader가 BLOCKED(category=spec)으로 전이"와 모순(plan 본문이 자기 자신과 충돌).
28
-
29
- **T4. `clean` 명령이 fresh-context 약속과 어떻게 정합되는지 불명.**
30
- plan L172-178 clean은 PRD/test-spec 보존, memos/sentinel/status 삭제. 그러나 governance §1f fresh-context 원칙은 "Worker/Verifier 매 iteration 새 프로세스, MCP 비활성"이지 PRD 보존을 금지하지 않는다 — 그러므로 L181 "Replay 후 재시도는 fresh-context 약속을 깨지 않음"은 본질적으로 truism. 진짜 risk는 **replay 후 us_fail_history와 consecutive_failures가 어떻게 되느냐**다(memory 초기화로 0이 되면 cb_threshold 6 재진입, 무한 replay 가능). plan에 history 카운터 reset 정책이 없다.
31
-
32
- ---
33
-
34
- ## 3. Principle Violations / 누락
35
-
36
- **V1. CLAUDE.md "Local File Sync" 표 갱신이 plan L248 한 줄로 처리됨.**
37
- "신규 clean_ralph_desk.zsh + 3 신규 blueprint 자동 wildcard 포함"이라고 적었지만, CLAUDE.md 실제 표(읽음)는 wildcard가 아니라 명시 enumeration이다 (`docs/blueprints/*` 만 wildcard, runtime zsh는 5개 명시 라인). `clean_ralph_desk.zsh`는 **표 안에 신규 라인으로 추가 + diff -q 검증 라인 추가**가 필요한데 plan은 표 패치를 명시하지 않았다.
38
-
39
- **V2. `install.sh` 패치 누락 (CRITICAL).**
40
- install.sh L34-39를 읽음 — runtime zsh는 명시적 curl로 한 줄씩 다운로드. plan L172 신규 `clean_ralph_desk.zsh`는 **install.sh에 curl 라인 추가 안 하면 npm 외부 사용자(curl 설치) 환경에서 누락**된다. plan 어디에도 install.sh 패치가 없음. 마찬가지로 `migrate-state-v1-to-v2.zsh`(L118, repo 루트)도 install.sh 권유 다운로드 대상인지 미정의.
41
-
42
- **V3. `package.json#files` 영향 미검토.**
43
- 신규 `src/scripts/clean_ralph_desk.zsh`, `scripts/migrate-state-v1-to-v2.zsh`(L245), 3 blueprint 파일이 npm tarball에 포함되는지 plan에 언급 없음. MEMORY.md `feedback_verify_before_publish.md` 룰 — "publish 전 postinstall.js/install.sh/CLAUDE.md 3곳 동기화 검증" — 의 install.sh 한 곳을 위반.
44
-
45
- **V4. KISS 위반 — P2#7 단일 파일 통합이 sub-PR 한계 초과.**
46
- plan L144-156은 cost-log.jsonl과 campaign.jsonl을 통합하면서 "deprecation 노트 추가, 기간 유지". sub-PR E(L276)에 P2#7+#8+#9 묶여 있는데, ① cost-log deprecation, ② event_type 7종 enum, ③ replay 신설을 한 PR에 넣는 건 review fatigue 유발. PR 분할 전략이 KISS와 충돌.
47
-
48
- **V5. governance §1f "세 채널" → "네 채널" 승격이 명시 안 됨.**
49
- plan L42는 "§1f 세 채널에 카테고리도 함께 명시"라고만 적음. 그러나 카테고리는 sentinel/status/console/report 4 채널로 흐른다 (L74 report, L77 sentinel, L78 stderr, L80 status). §1f 본문 텍스트가 "세 채널"인지 "네 채널 + 카테고리 cross-cutting"인지 plan에 정확한 §1f 패치 문장이 없다.
50
-
51
- **V6. P3#10/#11 "spec-only"가 governance §8 일관성을 깰 위험.**
52
- plan L186-209는 §8에 Adaptive Iter Cap + Gate-fail Trigger 두 신규 섹션 추가 + CLI flag stub. CLI flag가 동작 안 하는 채로 governance에 명세만 들어가면, 사용자가 `--adaptive-cap strict`를 켰을 때 silent no-op. plan us019(L261)은 "CLI 플래그 stub 파싱 OK"만 검증하지 stub이 사용자에게 "not implemented" 경고를 내는지 검증 없음 — silent failure 0 원칙(L30) 자기위반.
53
-
54
- **V7. RC-3 docs(`multi-mission-orchestration.md`)와 P1#4 emit의 위치 미상.**
55
- plan L96 "flywheel-signal.json 작성처(prompt-assembler / flywheel runner) — 현재 어떤 prompt가 flywheel signal을 만드는지 추적 후"라고 적었다 — **추적 자체가 plan 단계에서 끝나야 하는데 implementation 단계로 미뤘다.** sub-PR-B(L274)가 "추적 + emit + 테스트"를 한 번에 하면 추적 결과에 따라 surface가 폭증할 수 있다.
56
-
57
- ---
58
-
59
- ## 4. Synthesis (file/line 수준 plan 보강안)
60
-
61
- - **L60-66 카테고리 표 아래에** "wrapper recovery 매핑 가이드" 1단락 추가: 각 카테고리에 권장 wrapper 액션(retry/escalate/abort) 명시 → T1-A의 의미 충돌 해소. unknown은 "wrapper가 abort 권고"로 명시.
62
- - **L82-87 매핑표를 양방향 명시**: verifier→category와 category→wrapper-action 두 표 분리. `implementation→repeat` 표기는 폐기하고 `implementation→implementation`(spec 6→7종 확장) 또는 `implementation→spec`(코드 결함을 spec drift로 분류) 중 택1 — 어느 쪽이든 plan에서 결정.
63
- - **L107-120 사이에** "Schema detection 단일 모델 채택" 라인 추가: explicit-version만 사용, field-presence는 폐기. lib_ralph_desk.zsh:470 grep을 "schema_version 검사 후 분기"로 패치하는 항목을 P1#5 대상에 명시.
64
- - **L131-135 Lane enforce 모순 해소**: §7¾ 본문에 "informational WARN"과 "BLOCKED 전이"의 트리거 차이를 표로 정의 (worker가 PRD에 1자 추가=WARN, AC 의미 변경=BLOCKED). 모순 텍스트(IL-2 감점 + BLOCKED + WARN)를 한 결정 트리로 통일.
65
- - **L172-181 clean 명령에** `--reset-history` 플래그 추가 명시 + us_fail_history 초기화 정책 명시 (T4 해소). 기본값은 "보존" (무한 replay 방지) — wrapper가 명시적으로 의도해야 reset.
66
- - **L215-246 변경 대상 파일 표에** 다음 라인 추가:
67
- - `install.sh` — 신규 zsh curl 라인 (V2)
68
- - `package.json` (files 필드) — 신규 zsh + blueprint 포함 검증 (V3)
69
- - `CLAUDE.md` Local File Sync 표 — 명시 enumeration 추가 (V1)
70
- - **PR 분할 전략 L268-279에** PR-E를 P2#7과 P2#8+#9 둘로 더 분할 권고 (V4) — 단일 file consolidation은 review 단위 1 PR이 적절.
71
- - **us019(L261)에** "CLI flag stub이 사용자에게 'not implemented in v2.0' WARN을 출력하는지" AC 추가 (V6).
72
- - **P1#4 emit(L93-100)을** "추적은 본 plan 산출물, emit은 sub-PR-B"로 분리 — 추적 결과 prompt 파일 path를 plan 안에 미리 기재 (V7 risk 차단).
73
-
74
- ---
75
-
76
- ## References
77
-
78
- - `src/node/runner/campaign-main-loop.mjs:337-345` — writeSentinel 현재 형태 (Reason 1줄, Category 추가 시 라인 누적)
79
- - `src/scripts/lib_ralph_desk.zsh:470-472` — sentinel 파서 `grep -m1 ^[Rr]eason:` (field-presence)
80
- - `src/scripts/lib_ralph_desk.zsh:709-719` — write_blocked_sentinel 현재 (zsh 측, mjs와 라인 형식 정합 필요)
81
- - `src/scripts/lib_ralph_desk.zsh:440-453` — appendIterationAnalytics campaign.jsonl 현재 record (event_type 부재, 신규 필드 추가 시 backward-compat 영향)
82
- - `install.sh:31-52` — 명시적 curl 다운로드, 신규 zsh 추가 시 패치 필수
83
- - `CLAUDE.md` Local File Sync 표 — runtime 5개 + reference 8개 명시 enumeration
84
- - `docs/plans/rlp-desk-elegant-papert.md:60-66` (카테고리 6종), `:82-87` (매핑), `:107-120` (schema), `:131-135` (lane), `:172-181` (clean), `:248` (sync 표 한줄), `:268-279` (PR 분할)
@@ -1,270 +0,0 @@
1
- # rlp-desk 0.10.1 Multi-mission Autonomy + Sentinel JSON + Lane Enforcement (ralplan v6)
2
-
3
- ## Context
4
-
5
- 소비자 fix prompt(2026-04-25, "rlp-desk 0.10.1 Fix Request"):
6
- - P0-A [BLOCKER] Multi-mission autonomy — 옵션 B `next_mission_candidate` 표준 권고
7
- - P0-B [HIGH] tmux SV skip — **이미 main에 머지됨 (PR #4 RC-1)**
8
- - P0-C [HIGH] PRD per-us cross-US lint — **이미 main에 머지됨 (PR #4 RC-2)**
9
- - P1-D [MEDIUM] BLOCKED sentinel JSON taxonomy
10
- - P1-E [MEDIUM] Lane separation runtime enforcement
11
-
12
- 현재 상황 (정확):
13
- - main HEAD = `d288dce` (PR #4 squash 머지). main의 `lib_ralph_desk.zsh`는 markdown 첫 줄(`# Campaign Blocked`).
14
- - feature branch `fix/rc1-tmux-sv-skip-and-rc2-prd-cross-us-lint`에 commit `a7f917a`가 있어 src/ working tree는 이미 `BLOCKED: <us_id>` 첫 줄로 통일된 상태(Risk D fix). 즉 src/와 main이 다름 — 본 plan은 a7f917a를 main으로 가져오는 단일 PR에 R2/R3/R4를 더해 통합.
15
- - 본 작업은 a7f917a + P0-A + P1-D + P1-E를 새 PR로 main에 머지(사용자 명시: cherry-pick 안 함, solo project).
16
-
17
- 본 v6는 v4의 보수적 결정을 새 요구의 wrapper 친화성 우선으로 갱신 + Architect/Critic의 9개 ITERATE 패치를 모두 흡수한 결과다.
18
-
19
- ## 5개 P 항목 vs 현재 상황 매핑
20
-
21
- | 항목 | 처리 |
22
- |---|---|
23
- | **P0-A** Multi-mission autonomy (옵션 B) | **채택, 본 PR R2** |
24
- | **P0-B** tmux SV skip | **DONE — main `d288dce`** |
25
- | **P0-C** PRD per-us lint | **DONE — main `d288dce`** |
26
- | **P1-D** Sentinel JSON taxonomy | **채택, 본 PR R3** (JSON sidecar + 6 domain 카테고리) |
27
- | **P1-E** Lane runtime enforcement | **절충 채택, 본 PR R4** (WARN-default + `--lane-strict` opt-in) |
28
- | **Risk D** zsh sentinel 첫 줄 통일 | **본 PR R1** (a7f917a 통합) |
29
-
30
- 본 PR scope: R1 + R2 + R3 + R4. PR 분할 옵션은 §28 alternative 참조.
31
-
32
- ## RALPLAN-DR 요약
33
-
34
- - **Principles**:
35
- 1. **Wrapper-first contract**: sentinel JSON sidecar + 명시적 categories + recoverable + suggested_action.
36
- 2. **Backward-compat first**: 기존 markdown sentinel 보존, JSON은 sidecar 신규.
37
- 3. **Silent failure 0 (정정)**: BLOCKED은 4채널 표면화 (sentinel/status/console/report). lane WARN-mode도 silent 아님 — analytics 이벤트 + audit log + log_warn 3 채널 emit. "Silent"는 wrapper가 발견 못 하는 상태를 의미하지, "non-blocking"이 아님.
38
- 4. **No silent stubs**: 모든 신규 옵션은 동작 명세 + 검증 시나리오 동반.
39
- - **Decision Drivers**:
40
- 1. Consumer wrapper 코드 단순화 (`scripts/launch_sustained_flywheel.sh` 400 lines → ~100 lines 가능).
41
- 2. mission chain이 spec staging만으로 가능 (코드 수정 0).
42
- 3. wrapper가 BLOCKED reason을 regex 우회 패턴 없이 분기.
43
- - **Viable Options 비교 — Sentinel taxonomy 형식 (P1-D)**:
44
- - **A. JSON sidecar (`<slug>-blocked.json`) + markdown sentinel 보존(채택)** — wrapper-friendly(`jq .reason_category`). markdown은 첫 줄 `BLOCKED: <us_id>`(R1) + Reason 라인 보존. **Write order contract** (Architect/Critic ITERATE 흡수): JSON sidecar 먼저 atomic_write → markdown sentinel 나중에 atomic_write. 불변 invariant: **markdown 존재 ⇒ JSON 존재 보장**. wrapper는 markdown sentinel을 watch하고, 발견 시 JSON을 read; markdown이 보이는데 JSON이 없으면 fopen retry(최대 5회 × 50ms). atomic_write는 single-file rename atomicity만 제공하지만, write order + reader retry로 cross-file race 차단.
45
- - **B. Markdown extension만(v4 plan안, 탈락)** — wrapper가 grep 4종 패턴 알아야. wrapper-friendly 부족.
46
- - **C. JSON 단일(탈락)** — 기존 markdown 파서 깸. backward-compat 위배.
47
- - 결정: A. write order contract로 race condition 해결.
48
- - **Viable Options 비교 — 카테고리 분류 (P1-D, Critic ITERATE 흡수)**:
49
- - **A. 6 domain (reason_category, primary) + 4 code (failure_category, secondary)(채택)**:
50
- - `reason_category` (primary, wrapper 분기 기준): `metric_failure / cross_us_dep / context_limit / infra_failure / repeat_axis / mission_abort`. 도메인 시그널, wrapper의 `jq .reason_category` 한 줄 분기.
51
- - `failure_category` (secondary, diagnostic only): `spec / implementation / integration / flaky | null`. verifier verdict의 codebase 결함 분류.
52
- - governance §1f에 명시: "wrapper MUST branch on `reason_category`. `failure_category` is diagnostic only — do NOT branch on it."
53
- - **B. 6 카테고리만(domain only, 탈락)** — verifier 정보 손실.
54
- - **C. 4 카테고리만(code only, 탈락)** — wrapper가 도메인 의미 부족.
55
- - **D. v4 5 카테고리 단일(탈락)** — abstract.
56
- - 결정: A. 두 필드 동시 expose + 우선순위 명시.
57
- - **Viable Options 비교 — Lane enforcement (P1-E)**:
58
- - **A. WARN-default + `--lane-strict` opt-in BLOCKED 승격(채택, downgrade 적용)** — default WARN: analytics + audit log + log_warn (silent 아님). strict: lane 위반 → BLOCKED. **Strict downgrade(Critic ITERATE 흡수)**: strict의 lane 위반 BLOCKED은 **`recoverable=true` + `suggested_action=retry_after_fix`** (terminal_alert 아님). 부정확한 mtime audit이 캠페인을 종신 종료할 권한 비대칭을 완화.
59
- - **B. 항상 strict (탈락)**.
60
- - **C. 항상 WARN (탈락)**.
61
- - 결정: A.
62
- - **Viable Options 비교 — PR 분할 (Architect/Critic ITERATE 흡수)**:
63
- - **A. 단일 PR — R1+R2+R3+R4(채택)** — 사용자 명시(solo project, cherry-pick 거부). 4 변경이 sentinel 형식으로 강결합.
64
- - **B. 2-PR 분할 — (R1+R2) / (R3+R4)** — codex review fatigue 분산. R1(sentinel 첫 줄)+R2(flywheel emit)는 작고 독립적, R3(JSON sidecar)+R4(lane)는 sentinel 강결합. review 비용 quadratic이라 분할이 더 효율 가능.
65
- - 결정: A 채택, 단 review iteration이 5+ 도달 시 B로 split fallback. ralplan max iteration(5) 초과 시 자동으로 split 결정 트리거.
66
-
67
- ## Self-Critique (Architect/Critic 9 patches 흡수)
68
-
69
- v5에서 Architect 7 + Critic 9 violations 모두 흡수:
70
- - §44/57: atomic_write 두 번 = atomicity 거짓 → write order contract + reader retry로 명시 race 해결.
71
- - §134/144: 라인 카운트 부정확(5/14) → 정확값(4/11)로 정정. 카테고리 매핑 표 재작성.
72
- - §38↔§53: silent-failure-0 ↔ WARN default 모순 → "Silent failure 0"의 의미를 "wrapper가 발견 못하는 상태 0"으로 명확화. WARN도 analytics + audit + log 3 채널 emit이라 silent 아님.
73
- - §51: 6+4 카테고리 우선순위 누락 → primary/secondary 명시.
74
- - §150-152: cross-US 키워드 휴리스틱 미정의 → 정확 토큰 리스트 명시.
75
- - §174: strict의 BLOCKED 권한 비대칭 → recoverable=true + retry_after_fix downgrade.
76
- - §218: 신규 docs sync 검증 누락 → 명시 추가.
77
- - §157-162: race-condition fixture 누락 → us015에 추가.
78
- - §28: 2-PR 분할 alternative 누락 → 명시 + review iteration 5+ 시 fallback.
79
-
80
- ---
81
-
82
- ## 해결 계획
83
-
84
- ### Fix R1: Risk D — zsh sentinel 첫 줄 `BLOCKED: <us_id>` 통일
85
-
86
- **현재 main 상태**: `lib_ralph_desk.zsh:709` write_blocked_sentinel이 `# Campaign Blocked` markdown 헤더 첫 줄 작성. Node 측은 `BLOCKED: <us_id>` 첫 줄. main이 두 형식 혼재.
87
- **현재 feature branch 상태**: a7f917a이 src/ working tree에 적용 — 첫 줄 통일됨. 본 PR로 main에 가져옴.
88
-
89
- **대상**: 이미 commit a7f917a에 적용 완료. 본 PR에서 그대로 통합.
90
- - `src/scripts/lib_ralph_desk.zsh` write_blocked_sentinel 첫 줄 `BLOCKED: <us_id>` (us_id = optional 2번째 인자, fallback `${CURRENT_US:-ALL}`).
91
- - write_complete_sentinel 동일 패턴.
92
- - AC19 4건 회귀 (us013) 이미 a7f917a에 포함.
93
-
94
- ### Fix R2: P0-A Multi-mission autonomy — `next_mission_candidate` emit
95
-
96
- **대상**:
97
- 1. `src/scripts/init_ralph_desk.zsh` flywheel prompt heredoc (~ line 634+) — JSON 형식 명세에 `next_mission_candidate` 옵션 필드 명시:
98
- ```
99
- Optionally include `next_mission_candidate` field in the JSON output:
100
- - null when no next mission is suggested
101
- - "<slug>" when flywheel recommends a specific next mission for the wrapper
102
- ```
103
- 2. `src/node/runner/campaign-main-loop.mjs` line 582 인근(flywheelSignal 파싱) — `state.next_mission_candidate = flywheelSignal.next_mission_candidate ?? null`. 모든 status.json write에 직렬화.
104
- 3. `src/governance.md` §7 (flywheel-signal.json 형식 표) — `next_mission_candidate` (string | null, optional) 추가.
105
- 4. `docs/multi-mission-orchestration.md` — emit 측 spec 1단락 추가.
106
-
107
- **검증**: `tests/test_us014_next_mission_candidate.sh` 신규 (4 AC).
108
-
109
- ### Fix R3: P1-D BLOCKED sentinel JSON taxonomy + Write order contract
110
-
111
- **채택**: JSON sidecar 신규 + markdown sentinel 보존. write order: JSON 먼저 → markdown 나중.
112
-
113
- **JSON sidecar schema** (governance §1f Failure Taxonomy 추가):
114
- ```json
115
- {
116
- "schema_version": "2.0",
117
- "slug": "<slug>",
118
- "us_id": "<us_id or ALL>",
119
- "blocked_at_iter": <int>,
120
- "blocked_at_utc": "<iso8601>",
121
- "reason_category": "metric_failure | cross_us_dep | context_limit | infra_failure | repeat_axis | mission_abort",
122
- "reason_detail": "<full reason text>",
123
- "failure_category": "spec | implementation | integration | flaky | null",
124
- "recoverable": true | false,
125
- "suggested_action": "next_mission_chain | restart | retry_after_fix | terminal_alert"
126
- }
127
- ```
128
-
129
- **Wrapper contract 명시 (governance §1f)**:
130
- - `reason_category` is **primary** — wrapper MUST branch on this field.
131
- - `failure_category` is **secondary, diagnostic only** — do NOT branch on it; logging/triage only.
132
- - Read order: wrapper watches markdown sentinel. When markdown appears, read JSON sidecar. If JSON not yet visible (race), fopen retry up to 5 × 50ms before failing. **Invariant: markdown 존재 ⇒ JSON 존재 (writer가 JSON 먼저 atomic_write 보장)**.
133
-
134
- **카테고리 매핑 (BLOCKED 진입 분기 → reason_category)**:
135
- - **Node side (`campaign-main-loop.mjs`, 4곳, Architect 정정값)**:
136
- - L610 flywheel inconclusive → `mission_abort` (recoverable=false, action=`terminal_alert`)
137
- - L638 flywheel retries-exhausted → `mission_abort` (recoverable=false, action=`terminal_alert`)
138
- - L754 verifier-blocked → cross-US 토큰 검사 → `cross_us_dep` 또는 `metric_failure` (recoverable=true, action=`retry_after_fix`)
139
- - L797 model-upgrade-exhausted → `repeat_axis` (recoverable=false, action=`next_mission_chain`)
140
- - **zsh side (`run_ralph_desk.zsh`, 11곳, Architect 정정값)**:
141
- - L1611 API unavailable → `infra_failure` (recoverable=true, action=`restart`)
142
- - L2307/2314 worker spawn fail → `infra_failure` (recoverable=true, action=`restart`)
143
- - L2357/2365 monitor fail → `infra_failure` (recoverable=true, action=`restart`)
144
- - L2452 consensus exhausted → `repeat_axis` (recoverable=false, action=`next_mission_chain`)
145
- - L2511 verifier dead → `infra_failure` (recoverable=true, action=`restart`)
146
- - L2645/2649 cb_threshold reached → `repeat_axis` (recoverable=false, action=`next_mission_chain`)
147
- - L2667 verdict blocked → cross-US 토큰 검사 → `cross_us_dep` 또는 `metric_failure`
148
- - L2679 worker signal blocked → 본문 토큰 검사 → `cross_us_dep` 또는 `metric_failure`
149
- - L2702 context stale → `context_limit` (recoverable=false, action=`next_mission_chain`)
150
-
151
- **Cross-US 토큰 리스트 (정확 명시, Critic ITERATE 흡수)** — 다음 토큰 중 하나라도 매치하면 `cross_us_dep`, 아니면 `metric_failure`:
152
- - `depends on US-`
153
- - `blocking US-`
154
- - `awaits US-`
155
- - `post-iter US-`
156
- - `requires US-` + 다른 US 번호 참조
157
- - `cross-US`
158
- - 한국어: `US-X 산출물`, `신규 US-X`, `post-iter`
159
-
160
- **대상 파일**:
161
- 1. `src/node/runner/campaign-main-loop.mjs` — 신규 helper `_classifyBlock(verdict|null, source, state)` + `_writeBlockedJson(paths, classification)`. 4 BLOCKED 분기에서 호출. Write order: JSON 먼저 → markdown 나중.
162
- 2. `src/scripts/lib_ralph_desk.zsh:709` (write_blocked_sentinel) — 신규 4번째 인자 `category` (default `metric_failure`). 함수 내부에서 JSON sidecar 작성(markdown 작성 직전, atomic_write 두 번을 정확한 순서로).
163
- 3. `src/scripts/run_ralph_desk.zsh` — 11곳 호출처 각각 적절한 category 인자 추가 (위 매핑).
164
- 4. `src/governance.md` §1f — Failure Taxonomy(6 + 4) + JSON schema + Wrapper contract + Read order/Invariant 명시.
165
- 5. `docs/protocol-reference.md` — “Blocked Sentinel JSON Schema + Write Order Contract” 섹션.
166
-
167
- **검증**: `tests/test_us015_sentinel_json_taxonomy.sh` 신규 (포함):
168
- - 6 reason_category fixture × 11+4 호출처에서 emit 가능. 단 정확 11+4 호출처 모두 매핑된 category가 정의되어 있는지 grep 정합 검사.
169
- - JSON sidecar schema 모든 필수 필드.
170
- - markdown sentinel(legacy) 보존 — backward-compat.
171
- - wrapper용 `jq .reason_category` 한 줄 분기.
172
- - recoverable/suggested_action 매핑 정합.
173
- - **Race-condition fixture (Critic ITERATE 흡수)**: 별도 zsh 서브셸에서 atomic_write을 순서대로 호출하고, intermediate state에서 markdown만 보이는 순간이 없는지 검증 (markdown은 JSON 작성 후에만 작성됨). reader retry는 5×50ms로 fixture에서 직접 시뮬레이션.
174
- - Cross-US 토큰 리스트 7종 모두 매치 → `cross_us_dep` 분류.
175
-
176
- ### Fix R4: P1-E Lane runtime enforcement (절충, downgrade 적용)
177
-
178
- **채택**: WARN-default + `--lane-strict` opt-in BLOCKED 승격. strict의 BLOCKED은 `recoverable=true` + `retry_after_fix`로 downgrade.
179
-
180
- **대상**:
181
- 1. `src/scripts/run_ralph_desk.zsh` — `--lane-strict` CLI 플래그. session-config + metadata.json에 `lane_mode: "warn"|"strict"` 직렬화.
182
- 2. `src/node/run.mjs` — `--lane-strict` 플래그 stub.
183
- 3. `src/node/runner/campaign-main-loop.mjs` post-iteration — PRD/test-spec/memory mtime 비교(start mtime 스냅샷 + end mtime). 변동 시:
184
- - default warn: analytics `event_type='lane_violation_warning'` + log_warn + audit log entry.
185
- - strict: 위 + sentinel BLOCKED with **`reason_category='infra_failure'` + `recoverable=true` + `suggested_action='retry_after_fix'`** (Critic downgrade).
186
- 4. `~/.claude/ralph-desk/logs/<slug>/lane-audit.json` audit log — 위반 1건당 entry. 빈 캠페인도 빈 array `[]`로 초기화.
187
- 5. `src/governance.md` §7¾ — “Lane Enforcement” 섹션:
188
- - WARN(default): informational, 자율 운영 무중단, audit log + analytics.
189
- - STRICT(opt-in): BLOCKED with recoverable=true + retry_after_fix. 부정확 audit가 종신 종료를 결정 안 함.
190
- 6. `src/scripts/init_ralph_desk.zsh` Worker prompt에 lane 명시 (governance §7¾ 참조).
191
-
192
- **비-목표**: chmod 강제 ACL, git_blame last_modifier (mtime + heuristic만).
193
-
194
- **검증**: `tests/test_us016_lane_enforcement.sh` 신규
195
- - `--lane-strict` 플래그 파싱.
196
- - session-config / metadata.json `lane_mode` 직렬화.
197
- - mtime fixture:
198
- - default WARN: lane_violation_warning analytics + audit, BLOCKED 안 됨.
199
- - strict: BLOCKED + JSON sidecar `recoverable=true` + `retry_after_fix`. terminal_alert 아닌 retry_after_fix 검증.
200
- - audit log 파일 빈 캠페인도 생성.
201
- - governance §7¾ 텍스트 정합.
202
-
203
- ---
204
-
205
- ## 변경 대상 파일 표
206
-
207
- ```
208
- [runtime sync 대상]
209
- src/scripts/init_ralph_desk.zsh # R2(flywheel prompt), R4(worker lane)
210
- src/scripts/run_ralph_desk.zsh # R3(11 호출처 카테고리), R4(--lane-strict + lane_mode)
211
- src/scripts/lib_ralph_desk.zsh # R1(이미 a7f917a), R3(JSON sidecar + write order)
212
- src/commands/rlp-desk.md # R4(--lane-strict 명시)
213
- src/governance.md # R3(§1f Failure Taxonomy + 6+4 + Wrapper contract + Read order), R4(§7¾ WARN+STRICT downgrade), R2(§7 flywheel signal)
214
- src/node/runner/campaign-main-loop.mjs # R1(Risk D 통합), R2(next_mission_candidate), R3(_classifyBlock + _writeBlockedJson + write order), R4(mtime audit + strict 분기)
215
- src/node/run.mjs # R4(--lane-strict stub)
216
-
217
- [문서 — 자동 sync (docs/blueprints/* wildcard 또는 명시 항목)]
218
- docs/protocol-reference.md # R3(Blocked Sentinel JSON Schema + Write Order), R2(next_mission_candidate)
219
- docs/multi-mission-orchestration.md # R2 emit 측 spec
220
-
221
- [테스트]
222
- tests/test_us013_prd_cross_us_lint.sh # R1 AC19 (이미 a7f917a)
223
- tests/test_us014_next_mission_candidate.sh # R2 신규
224
- tests/test_us015_sentinel_json_taxonomy.sh # R3 신규 (race-condition fixture 포함)
225
- tests/test_us016_lane_enforcement.sh # R4 신규
226
-
227
- [배포 / sync 검증 (Critic ITERATE 흡수)]
228
- install.sh # 신규 zsh 없음, 변경 0
229
- package.json#files # src/scripts/ 미포함 side issue (별도 PR)
230
- CLAUDE.md (Local File Sync 표) # 변경 0 (기존 6 runtime + 2 docs 표 그대로 사용; 신규 docs/protocol-reference.md/multi-mission-orchestration.md는 이미 표에 있음)
231
- ```
232
-
233
- ## 검증 (Self-Verification Gate + 종단 회귀 + 신규 검증 항목)
234
-
235
- CLAUDE.md "Self-Verification Gate (ABSOLUTE)" 3 시나리오 + 4 신규 회귀 + Critic-요구 추가 검증:
236
-
237
- 1. **LOW** — `zsh -n` 모든 수정 zsh + `node --check` 모든 mjs. (~10s)
238
- 2. **MEDIUM** — 신규 회귀:
239
- - **us014 (R2 P0-A)**: prompt 명시, leader 직렬화, null fallback, governance §7 형식 표. (~20s)
240
- - **us015 (R3 P1-D)**:
241
- - 6 reason_category fixture 매핑 정확.
242
- - 11+4 호출처 정합 (grep 검증, Architect/Critic 라인 카운트 정정 반영).
243
- - JSON sidecar schema 필수 필드.
244
- - markdown sentinel 보존.
245
- - **Race-condition fixture**: write order(JSON 먼저, markdown 나중) 검증, reader retry 5×50ms.
246
- - Cross-US 토큰 7종 매치 → `cross_us_dep`. (~60s)
247
- - **us016 (R4 P1-E)**: lane_mode 직렬화, default WARN(BLOCKED 안 됨), strict(recoverable=true + retry_after_fix), audit log 생성, governance §7¾. (~40s)
248
- 3. **CRITICAL** — 회귀 무손실: us001/us007/us008(known 1 fail)/us012/us013 모두 통과. (~90s)
249
-
250
- **추가 검증 (Critic ITERATE 흡수)**:
251
- - 신규 docs sync 검증: `docs/protocol-reference.md` + `docs/multi-mission-orchestration.md`이 `~/.claude/ralph-desk/docs/`에 sync된 후 `diff -q` clean. CLAUDE.md “Local File Sync (ABSOLUTE)” 표에 두 파일 포함 여부 직접 확인 (현재 표에 둘 다 있음).
252
- - `gh pr` 전 `codex review` 0 issues — 단일 PR이라 review iteration이 5+ 도달 시 2-PR split fallback 트리거.
253
-
254
- ## ralplan v6 컨센서스 상태
255
-
256
- 본 plan은 v6. ralplan v5→v6 iteration: Architect 7 violations + Critic 9 file:line patches 흡수 완료.
257
-
258
- ## ADR (간결)
259
-
260
- - **Decision**: 5 P 항목 중 P0-B/P0-C는 main에 이미 있음(DONE). 나머지 P0-A + P1-D + P1-E + Risk D를 단일 PR로 통합. JSON sidecar(P1-D) + write order contract(JSON 먼저 → markdown) + 6 domain + 4 code 두 카테고리 with primary/secondary 우선순위. WARN-default + strict opt-in with recoverable=true downgrade(P1-E). flywheel emit(P0-A) + zsh sentinel 통일(R1).
261
- - **Drivers**: wrapper 코드 단순화, mission chain 코드 수정 0, regex 우회 패턴 제거, atomicity race 차단.
262
- - **Alternatives considered**: markdown extension만(v4안 탈락), 5 카테고리 단일(탈락), JSON 단일(탈락), 항상 strict(탈락), 항상 WARN(탈락), terminal_alert(downgrade 적용으로 탈락), 2-PR split(review iteration 5+ 시 fallback alternative).
263
- - **Why chosen**: A 옵션들이 모두 backward-compat + wrapper-friendly + race 차단 + downgrade로 권한 비대칭 완화.
264
- - **Consequences**:
265
- - consumer wrapper가 `jq .reason_category` 한 줄 분기 → 단순화.
266
- - mission chain이 spec staging만으로 가능.
267
- - Lane strict mode가 강한 환경에서 retry_after_fix로 안전 종료 — 부정확 audit가 캠페인 종신 종료 안 함.
268
- - JSON sidecar + markdown sentinel 두 파일 race는 write order + reader retry로 차단.
269
- - 단일 PR이지만 review iteration 5+ 시 split fallback.
270
- - **Follow-ups**: `package.json#files`에 `src/scripts/` 명시(별도 PR), helper `rlp-desk auto-chain`(옵션 A, wrapper 책임 분리), git_blame 정확 actor 식별.