okstra 0.101.1 → 0.102.0

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.
@@ -0,0 +1,121 @@
1
+ # Manual User Test 섹션 설계 (implementation → final-verification 연계)
2
+
3
+ - 작성일: 2026-06-24
4
+ - 상태: 설계 합의 완료, 구현 계획 대기
5
+ - 범위: okstra 산출물(implementation / final-verification)에 "사람이 직접 손으로 확인하는 테스트 절차"를 담는 섹션 신설
6
+
7
+ ## 1. 배경 — 실재하는 공백과 단절
8
+
9
+ 현재 okstra 산출물에는 "사람이 환경을 띄운 뒤 직접 클릭·확인하는 절차"를 적을 자리가 없다.
10
+
11
+ - implementation 딜리버러블의 테스트 섹션은 [final-report.template.md:458](../../../templates/reports/final-report.template.md) §5.7.6 Validation Evidence 하나뿐이며, 이는 `Phase | Command | Exit code | Output tail | TDD evidence` 구조의 **자동 검증 증거 전용**이다. 딜리버러블 명세 [_implementation-deliverable.md:19-20](../../../prompts/profiles/_implementation-deliverable.md) 의 Validation evidence / TDD evidence 도 전부 실행 명령 기반이다.
12
+ - planning 의 stage 별 `Acceptance:` 도 "RED 스텝을 PASS 로 뒤집는 같은 테스트 명령"으로 정의된다([implementation-planning.md:73](../../../prompts/profiles/implementation-planning.md)). 사람 절차가 아니다.
13
+ - final-verification 에는 사람 검증에 가까운 입력란이 있으나([final-verification-input.template.md:58](../../../templates/reports/final-verification-input.template.md) `Manual verification notes:`, `:70` `## Acceptance Criteria`), 이는 **검증 단계 입력**일 뿐 implementation 산출물로 흘러드는 "구현 직후의 테스트 절차"가 아니다.
14
+ - 결정적으로 [okstra-container/SKILL.md](../../../skills/okstra-container/SKILL.md) 는 사람이 만져볼 환경을 docker 로 띄워주지만(`:9` "so a human can poke at the running build"), `prompts/`·`templates/` 어디에서도 참조되지 않는 고립 스킬이다. **환경은 띄워주는데, 띄운 다음 무엇을 어떻게 확인할지를 적어둘 자리가 리포트에 없는 단절** 상태다.
15
+
16
+ ## 2. 목표 / 비목표
17
+
18
+ ### 목표
19
+ - implementation 단계가 "Manual User Test" 절차 **초안**을 산출하고, final-verification 단계가 그 초안을 **실제로 수행해 결과를 확정**한다.
20
+ - 관찰 가능한 변화가 있는 task 에 한해 절차를 요구한다(내부 전용 변경은 면제).
21
+ - `okstra-container` 와 리포트를 조건부로 연결해 단절을 해소한다.
22
+
23
+ ### 비목표 (YAGNI)
24
+ - 자동 E2E/통합 테스트 프레임워크 도입 — 기존 자동 검증(§5.7.6 / verifier)은 그대로 둔다.
25
+ - 모든 task 에 일괄 강제 — 관찰 불가 변경까지 N/A 스텁을 양산하지 않는다.
26
+ - okstra-container 가 없는(=docker-compose 부재) 프로젝트에 컨테이너 사용 강요.
27
+
28
+ ## 3. 설계 결정 (확정)
29
+
30
+ | 결정 | 선택 | 근거 |
31
+ |---|---|---|
32
+ | 위치 | implementation(초안) ↔ final-verification(실행·확정) **양쪽 연계** | 절차 작성 시점(구현 직후)과 실행 시점(검증)이 다름. 단일 소유자 분리: 초안=implementation, 결과=final-verification |
33
+ | 범위 | 사람이 **관찰 가능한 변화**가 있을 때만 절차, 내부 전용이면 `사용자 테스트 불필요 — <사유>` 한 줄 | allowlist 방식으로 빈 섹션 노이즈 최소화 |
34
+ | container 연계 | **조건부** — `docker-compose.yml` 있으면 `okstra-container up`, 없으면 일반 실행 명령 | 단절 해소 + compose 없는 프로젝트 과임 방지 |
35
+ | 강제 | **validator 구조 강제** — 헤딩 존재 + (절차 또는 면제사유) 중 하나, 빈 스텁 거부 | "관찰 가능 여부"는 의미 판단이라 validator 자동 판정 불가 → 구조만 강제, 적절성은 교차검증 워커가 판단. CLAUDE.md "MUST 는 강제 지점 명시" 충족 |
36
+
37
+ ## 4. 섹션 포맷 명세
38
+
39
+ ### 4.1 implementation 초안 — `## 5.7.x Manual User Test (Draft)`
40
+ 관찰 가능한 변화가 있으면 항목별로:
41
+
42
+ ```
43
+ - 대상: <어떤 사용자-facing 동작/기능>
44
+ - 환경 기동:
45
+ - (docker-compose.yml 있음) `okstra-container up <task-id>` → published port 접속
46
+ - (없음) <프로젝트 실행 명령, 예: npm start / 해당 바이너리 실행>
47
+ - 확인 스텝:
48
+ 1. <사람이 하는 동작>
49
+ 2. ...
50
+ - 기대 결과: <관찰되어야 하는 것>
51
+ ```
52
+
53
+ 관찰 불가(내부 전용)면 항목 대신 한 줄:
54
+ ```
55
+ 사용자 테스트 불필요 — <사유, 예: 내부 리포지토리 메서드 리팩토링, 외부 관찰 동작 변화 없음>
56
+ ```
57
+
58
+ 절차 내용은 planning 의 `Acceptance:` 와 implementation 의 실제 diff 를 근거로 작성한다.
59
+
60
+ ### 4.2 final-verification 실행 결과 — `## 5.8.x Manual User Test Results`
61
+ implementation 초안의 각 항목을 수행하고:
62
+
63
+ ```
64
+ - 대상: <초안에서 인용>
65
+ - 수행: <실제로 한 동작 / 띄운 환경>
66
+ - 결과: pass | fail | blocked
67
+ - 관찰값: <실제로 본 것 — 초안의 기대 결과와 대조>
68
+ ```
69
+
70
+ 초안이 "사용자 테스트 불필요"였으면 그 사유의 타당성만 재확인(한 줄)하고 넘어간다.
71
+
72
+ ## 5. 데이터 흐름
73
+
74
+ ```
75
+ implementation-planning(Acceptance:)
76
+
77
+
78
+ implementation ──→ §5.7.x Manual User Test (Draft) [초안 단일 소유자]
79
+ │ │
80
+ │ (instruction-set 로 carry)
81
+ ▼ ▼
82
+ final-verification ──→ §5.8.x Manual User Test Results [결과 단일 소유자]
83
+
84
+ └── final-verification-input.template.md 의 Manual verification notes 입력란을 이 흐름과 통합
85
+ ```
86
+
87
+ ## 6. 영향 파일별 변경
88
+
89
+ | 파일 | 변경 |
90
+ |---|---|
91
+ | [prompts/profiles/_implementation-deliverable.md](../../../prompts/profiles/_implementation-deliverable.md) | 딜리버러블 항목에 "Manual user test draft" 추가 — §4.1 포맷, 범위 규칙(관찰 가능 시에만), 조건부 container 안내 지시 |
92
+ | [prompts/profiles/final-verification.md](../../../prompts/profiles/final-verification.md) | 초안을 수행·확정하라는 지시 추가 — §4.2 포맷 |
93
+ | [templates/reports/final-report.template.md](../../../templates/reports/final-report.template.md) | §5.7 에 `Manual User Test (Draft)` 하위섹션 신설(Routing Recommendation 직전 배치, 이후 번호 +1), §5.8 에 `Manual User Test Results` 하위섹션 신설(Routing 직전) |
94
+ | [templates/reports/final-verification-input.template.md](../../../templates/reports/final-verification-input.template.md) | `Manual verification notes:`(:58) 를 implementation 초안 인용 흐름과 통합 |
95
+ | [validators/validate-run.py](../../../validators/validate-run.py) | 기존 §5.7/§5.8 deliverable 섹션 scan 리스트(:1145-1163)에 신설 섹션명 추가 + 빈 스텁/면제사유 거부 체크 |
96
+ | [skills/okstra-container/SKILL.md](../../../skills/okstra-container/SKILL.md) | 리포트↔container 양방향 포인터 한 줄(이 절차 섹션에서 호출됨을 명시) |
97
+
98
+ > 구현 후 `npm run build` 로 `runtime/` 동기화 필요. 엔드유저 반영은 `okstra install` 재실행.
99
+
100
+ ## 7. enforcement (validator 체크 명세)
101
+
102
+ `validators/validate-run.py` 에 다음을 추가한다. 의미 판단(관찰 가능 여부)은 하지 않고 **구조만** 본다. 구현은 기존 메커니즘 위에 얹는다 — §5.7/§5.8 deliverable 섹션 제목 substring scan 리스트([validate-run.py:1145-1163](../../../validators/validate-run.py))에 신설 섹션명을 추가하면 헤딩 누락이 기존 "missing required §5.7" 에러([validate-run.py:1688](../../../validators/validate-run.py))로 잡힌다. 빈 스텁/면제사유 누락 거부는 deprecated stub 거부([validate-run.py:759](../../../validators/validate-run.py))와 동형의 체크를 추가한다.
103
+
104
+ - **implementation 리포트**: `## 5.7.x Manual User Test (Draft)` 헤딩이 존재해야 한다(MUST). 본문은 다음 중 하나여야 하며, 빈 스텁/플레이스홀더는 거부한다:
105
+ - 최소 1개의 절차 항목(대상 + 확인 스텝 + 기대 결과), 또는
106
+ - `사용자 테스트 불필요 — ` 로 시작하는 면제 한 줄(사유 비어 있으면 거부).
107
+ - **final-verification 리포트**: `## 5.8.x Manual User Test Results` 헤딩 존재(MUST). 본문은 결과 항목(결과=pass/fail/blocked) 또는 면제 재확인 한 줄.
108
+ - 절차의 **적절성·정확성**(스텝이 실제 변경을 커버하는지)은 validator 가 아니라 교차검증 워커/컨버전스가 판단한다 — validator 는 구조 게이트일 뿐임을 주석으로 명시.
109
+
110
+ 이로써 "Manual User Test 섹션은 implementation/final-verification 리포트에 필수" 라는 MUST 가 [validate-run.py](../../../validators/validate-run.py) 라는 구체적 강제 지점을 갖는다.
111
+
112
+ ## 8. 테스트 계획
113
+
114
+ - `tests/contract/` 또는 해당 validator 테스트: (a) 절차 항목 있는 리포트 통과, (b) 헤딩 누락 거부, (c) 빈 스텁 거부, (d) 면제 한 줄(사유 있음) 통과, (e) 면제 사유 비면 거부.
115
+ - `tests/report/` 또는 template 렌더 테스트: 신설 §5.7.x / §5.8.x 헤딩이 implementation / final-verification 렌더에 나타나고 다른 taskType 에는 안 나타나는지.
116
+ - `bash validators/validate-workflow.sh` 통과.
117
+ - 완료 게이트: `npm run check` exit 0.
118
+
119
+ ## 9. 미해결 사항
120
+
121
+ 없음. (섹션 정확 번호는 구현 시 §5.7/§5.8 재배치로 확정 — Routing Recommendation 을 항상 마지막으로 유지)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okstra",
3
- "version": "0.101.1",
3
+ "version": "0.102.0",
4
4
  "description": "Multi-agent cross-verification orchestrator runtime + Claude Code skills.",
5
5
  "license": "MIT",
6
6
  "author": "devonshin",
@@ -1,5 +1,5 @@
1
1
  {
2
- "package": "0.101.1",
3
- "builtAt": "2026-06-24T05:11:23.276Z",
2
+ "package": "0.102.0",
3
+ "builtAt": "2026-06-24T08:03:17.852Z",
4
4
  "repoRoot": "/home/runner/work/okstra/okstra"
5
5
  }
@@ -101,6 +101,7 @@ Rules (the schema enforces most of these — they are listed here so you know *w
101
101
  - Cite file paths and line numbers in every `evidence.primary[].source` / `consensus[].evidence` cell.
102
102
  - Preserve every analysis worker's ticket tagging — every row's `ticketId` field carries the ticket key or the task-fallback. For single-ticket runs, set `ticketCoverage` to `{"singleTicket": "<ticket>"}`. For runs that do not require ticket tagging (`release-handoff`, `final-verification`), set `ticketCoverage` to `{"omit": true}`.
103
103
  - For `implementation-planning`, populate `implementationPlanning.requirementCoverage` with one row per concrete requirement from the brief / packet, using IDs `R-001`, `R-002`, ... in source order. `coveredBy` MUST name the specific Option Candidate plus Stage/Step that satisfies the requirement. Use `status: "covered"` only when the report's plan actually covers it; otherwise use `gap` or `blocked C-NNN` and ensure the corresponding `Clarification Items` row blocks approval. Do not collapse this into `ticketCoverage`; ticket coverage is not requirement coverage.
104
+ - For `implementation-planning`, also populate `implementationPlanning.decisionDrafts` (one row per decision meeting all three decision-record criteria; `[]` otherwise) and `implementationPlanning.skippedAdrCandidates` (evaluated-but-dropped adr-candidates; `[]` otherwise). The schema excerpt enumerates the row shape; the renderer emits §5.4 `### Decision Drafts`. When `decisionDrafts` is non-empty, the plan's stages MUST carry a stepwise step that creates `.okstra/decisions/<NNNN>-<slug>.md` (validate-run gates this).
104
105
  - When the `Task Type` is `improvement-discovery`, populate `## 5.9 Improvement Candidates` with the 10-column schema enforced by `validators/validate_improvement_report.py`. Source the row IDs (`I-NNN`), lens whitelist, and Source workers patterns from `scripts/okstra_ctl/improvement_lenses.py` — do NOT introduce new lens names or worker prefixes. `improvement-discovery` is NOT in the data.json schema enum, so author its markdown directly (not via `okstra-render-final-report.py`). Immediately after writing the markdown, run (`Bash`): `okstra inject-report-index <markdown path> --report-language <en|ko>`. That adds the top-of-report Index plus `I-NNN` / `C-NNN` scroll anchors; the run validator fails the report when the Index anchor is absent.
105
106
 
106
107
  Write the data.json (and the audit sidecar `.md`) with your `Write` tool — that is the canonical authoring path, and okstra ships no hook that blocks `.md` writes (its only settings hook is the `SessionEnd` trace-cleanup; the coding-preflight hook emits reminders but never blocks). A Bash heredoc is acceptable ONLY when a specific `Write` call is genuinely rejected by the host environment, and it MUST produce byte-identical content — do not reach for it pre-emptively. Then invoke the renderer (`Bash`): `okstra render-final-report <data.json path>`. Confirm both files exist and respond with a short status line prefixed by your model identity, copied verbatim from the `**Model:** Report writer worker, <modelExecutionValue>` line in your dispatch prompt (per Worker Preamble → "Return message to the lead"):
@@ -281,7 +281,7 @@ Every field MUST anchor its claim with at least one evidence reference — a `pa
281
281
  3. **Evidence and Detailed Analysis** — primary evidence rows (file path, line, snippet); secondary evidence / alternate interpretations. If `reference-expectations.md` lists explicit expected values, record match/gap per row.
282
282
  4. **Missing Information and Risks** — uncertain / "I don't know" items. `implementation-planning` adds §5.5 (see heading contract below); `release-handoff` adds §5.6.
283
283
  5. **Clarification Items** — single unified `C-*` table; column schema (4 columns with the short fields stacked in one record-meta cell), ID convention, and rerun behaviour are owned by `_common-contract.md §Clarification request policy` (SSOT). The deprecated `5.5.9 Open Questions` / `1.1 추가 자료 요청` / `1.2 사용자 확인 질문` sub-sections are removed; the validator fails reports that reintroduce them.
284
- 6. **Recommended Next Steps** — prioritized actions. After Phase 7's follow-up spawner runs, append a row per newly created task-key (see "Phase 6 → Phase 7 execution sequence" above). **Approval-gate consistency:** when §1 carries any `Blocks: approval` row with `Status` ∈ {open, answered}, the Verdict Card `Next Step` and the first recommended step MUST point to the clarification rerun (`resume-clarification` of the SAME task-type) — never to "frontmatter `approved: true` 플립 → `implementation` 직행". Run-prep enforces this gate (`run.py _validate_approved_plan` fail-closes on those rows and on a blocking data.json `gateResult`), so a direct-implementation next-step is an instruction the reader cannot actually follow.
284
+ 6. **Recommended Next Steps** — prioritized actions. After Phase 7's follow-up spawner runs, append a row per newly created task-key (see "Phase 6 → Phase 7 execution sequence" above). **Approval-gate consistency:** when §1 carries any `Blocks: approval` row with `Status` ∈ {open, answered}, the Verdict Card `Next Step` and the first recommended step MUST point to the clarification rerun (`resume-clarification` of the SAME task-type) — never to "frontmatter `approved: true` 플립 → `implementation` 직행". Run-prep enforces this gate (`run.py _validate_approved_plan` fail-closes on those rows and on a blocking data.json `gateResult`), so a direct-implementation next-step is an instruction the reader cannot actually follow. **Cross-project pointer rule:** cross-project 의존(타 repo / 다른 top-level 배포 모듈 / published 패키지)은 `crossProjectDependencies`(§5.4 Cross-Project Dependencies)가 authoritative — `recommendedNextSteps` 에는 그 substance(선행 작업·검증 신호·handoff)를 복제하지 말고 그 섹션을 가리키는 포인터 한 줄만 둔다(이중 기록 금지).
285
285
  7. **Follow-up Tasks** — auto-spawn-eligible table. Each row drives `okstra-spawn-followups.py`; see template §7 for the row schema.
286
286
 
287
287
  **§5.10 Fix History (data-presence gated).** When the run-manifest carries a `fixCycleId`, fill the data.json `fixCycle` block (`cycle` / `targetReport` / `symptom` / `runs`). Read the values from the task root's `history/fix-cycles.jsonl`: `cycle` MUST equal `fixCycleId`, `targetReport` / `symptom` come from that cycle's `opened` row, and `runs` lists its attached `run` rows (`taskType` / `runSeq` / `runManifest`). The validator (`validators/validate-run.py` → `_validate_fix_cycle`) fails the run when the block is missing or `fixCycle.cycle` does not match `fixCycleId`. When the run-manifest has no `fixCycleId`, OMIT the `fixCycle` block entirely — the renderer omits §5.10.
@@ -30,6 +30,7 @@ are collected and convergence finished. Phase 1-5 do not need it.
30
30
  - **Pure code changes** (no persisted state, no infra mutation): a reachable revert SHA is sufficient. Record the exact `git revert <SHA>` command that would undo the change, and confirm `git rev-parse <SHA>` resolves.
31
31
  - **Feature-flag-gated changes**: confirm the off-switch path was exercised in this run's validation evidence (i.e. one of the validation commands ran with the flag off and succeeded). A plan that ships a flag without exercising the off-path does NOT satisfy this requirement.
32
32
  - **Schema migrations, config-format changes, or any change with persisted state**: a **dry-run of the rollback step is mandatory**, not preferred. Record the exact rollback command and its captured exit code / stdout. If the migration tool offers no dry-run mode (`--dry-run`, `--plan`, equivalent), the executor MUST refuse to claim rollback verification and instead end the run with a routing recommendation back to `implementation-planning` for a safer rollback strategy. Skipping this step on a stateful change is treated as a `contract-violated` outcome by `final-verification`.
33
+ - **Manual user test draft**: when this run produces a user-observable change (UI / API / CLI / artifact), write `target / environment / steps / expected result` per change into §5.7.9 (data field `implementation.manualUserTest`, `applicable=true` with `items`). Environment line: if the project has a `docker-compose.yml`, use `okstra-container up <task-id>` then connect to the published port; otherwise the project's run command (e.g. `npm start`). When there is no user-observable change, set `applicable=false` and give a one-line `exemptionReason` instead of items. Base the steps on the approved plan's `Acceptance:` and this run's actual diff — these are the steps a human (or `final-verification`) re-runs by hand, NOT the automated validation commands in `Validation evidence`.
33
34
  - **Routing recommendation for `final-verification`**: brief note on whether the changes are ready for final-verification phase or need a new error-analysis / planning loop first.
34
35
  - **Follow-up tasks (Section 4 of the final report)**: every item discovered during this run that was *not* delivered MUST appear in the final report's `## 4. Follow-up Tasks (후속 작업)` table with a concrete `Origin`, `New Task ID`, `Suggested task-type`, `Scope`, and `Reason / Why deferred`. Sources include: out-of-scope discoveries that the executor consciously chose not to fold into this run, verifier concerns the executor declined to fix in-place, scope-boundary items from the approved plan that turned out to need their own ticket, and any unresolved `## 1. Clarification Items` row carried over from the approved plan (`Status` ∈ `{open, answered}` at approval time). An empty section is acceptable but only when expressed as the single line `- 후속 작업 없음.` — silence is treated as a contract violation. Rows with `Auto-spawn? = yes` will be materialised by `scripts/okstra-spawn-followups.py` in Phase 7; rows with `Auto-spawn? = no` MUST also appear in `Section 3. Recommended Next Steps` so the user knows to act manually.
35
36
 
@@ -46,6 +47,7 @@ are collected and convergence finished. Phase 1-5 do not need it.
46
47
  ```
47
48
  Only newly-added lines (those starting with `+` and not part of the `+++` header) are inspected. If output is anything other than `clean`, the run MUST either remove the placeholders before finalising or record an explicit justification per occurrence in the final report.
48
49
  7. **Stage-foreign literal scrub** — when the report-writer modelled this stage's `data.json` on another stage's report (a common shortcut for structural consistency), stage-specific literals get copied verbatim and silently misattribute this run. Confirm every branch name, commit SHA, stageKey, and stage number in the report resolves to **this** run's stage `<N>` — its worktree branch is `<prefix>-<task-id>-s<N>`, its stageKey `<task-id>-stage-<N>`. Sweep the report for any `-s<M>` / `stage-<M>` / `Stage <M>` where `M ≠ N` and for SHAs not in this run's `Commit list`; each hit is a copy-from-other-stage defect to correct before finalising.
50
+ 8. **Manual user test coverage** — when this run changed user-observable behaviour, §5.7.9 must carry concrete steps (not the `applicable=false` exemption). A user-facing change shipped with an exemption line is a contract violation; an internal-only change with no observable surface is the only valid use of the exemption.
49
51
 
50
52
  ## Lead post-stage persistence (BLOCKING — runs after the Executor emits `### Stage Carry Evidence`)
51
53
 
@@ -24,13 +24,13 @@
24
24
  - documentation or rollout gaps
25
25
  - production-specific failure modes not caught by tests (env/config drift across stages, secrets & permission/auth changes, migration ordering & rollback executability, observability gaps)
26
26
  - Pre-verification entry gate (resolved & enforced by `okstra render-bundle` prep — the lead does NOT recompute it):
27
- - the verification target (scope / worktree / base / head / stages / source reports / diff stat) is injected as the `VERIFICATION_TARGET` block. The lead MUST treat it as authoritative and MUST NOT re-pick a target from the brief.
27
+ - the verification target (scope / worktree / base / stages / source reports / diff stat) is injected as the `VERIFICATION_TARGET` block. The lead MUST treat it as authoritative and MUST NOT re-pick a target from the brief.
28
28
  - **whole-task scope** (`--stage auto`, default): prep has already verified every Stage Map stage is `status:done` in `consumers.jsonl`, every done stage's `head_commit` is an ancestor of the task worktree HEAD (all stage branches merged), and the worktree is clean outside `.okstra/`. If any check failed the run never started (PrepareError); a started whole-task run is therefore a fully-merged, clean target.
29
29
  - **whole-task 은 read-only 가 아니라 mutating phase 다.** whole-task 모드는 진입 시 아직 task 브랜치에 머지되지 않은 done stage 들을 `--no-ff` 로 자동 머지해 통합 커밋을 생성하고, 이어서 정리 가능한 stage worktree·registry stage-key·stage 브랜치를 제거한다. 머지 충돌이 발생하면 충돌 파일을 보고하고 중단한다(사용자가 수동 해소 후 재시도). 미커밋 변경이 남은 stage worktree 는 보존한다. 따라서 위 entry gate 가 말하는 "fully-merged, clean target" 은 이 자동 통합 단계가 끝난 뒤의 상태이며, whole-task final-verification 은 통합 커밋을 만드는 mutating phase 로 취급해야 한다.
30
30
  - **single-stage scope** (`--stage N`): prep verified stage N is `status:done` and its isolated stage worktree exists and is clean. Other stages' state is irrelevant. A single-stage run is a partial verification: it MUST NOT recommend plain `release-handoff`, but MAY recommend `release-handoff(stage-group)` when the verdict is `accepted` — the stage becomes PR-eligible for a stage-group handoff.
31
- - the lead still captures `git rev-parse HEAD` / `git status --short` from the injected worktree to confirm the analysis ran against the injected head; a mismatch is a `tool-failure`, not a silent proceed.
31
+ - the lead still captures `git status --short` from the injected worktree to confirm the analysis ran against the delivered work-tree state; an unexpected divergence (dirty tree outside `.okstra/`, missing worktree) is a `tool-failure`, not a silent proceed.
32
32
  - Required deliverable shape (final report, in addition to the standard sections):
33
- - **Source Implementation Report(s)**: the `VERIFICATION_TARGET` snapshot verbatim — verification scope, worktree path, base/head SHAs, the list of stages under verification, and one row per stage citing its originating implementation final-report (`report_path` from `consumers.jsonl`; render `(report_path unrecorded)` when absent). The lead injects this same snapshot into every analyser prompt (`**Verification scope:** / **Worktree:** / **Verification base ref:** / **Verification head SHA:** / **Verification diff stat:**`); a worker that cannot confirm its analysis ran against that exact head MUST record a `tool-failure`.
33
+ - **Source Implementation Report(s)**: the `VERIFICATION_TARGET` snapshot verbatim — verification scope, worktree path, base ref, the list of stages under verification, and one row per stage citing its originating implementation final-report (`report_path` from `consumers.jsonl`; render `(report_path unrecorded)` when absent). The lead injects this same snapshot into every analyser prompt (`**Verification scope:** / **Worktree:** / **Verification base ref:** / **Verification diff stat:**`); a worker that cannot confirm its analysis ran against that worktree's delivered diff (against `base ref`) MUST record a `tool-failure`.
34
34
  - **Verdict vocabulary**: Section 7 (`Final Verdict`) MUST include a `Verdict Token` field whose value is exactly one of `accepted`, `conditional-accept`, or `blocked`. `conditional-accept` requires an explicit, exhaustive list of conditions; ambiguous verdicts ("looks good", "mostly ready") are not allowed. Each condition MUST be recorded as a row in the **Conditional Acceptance Conditions** deliverable (`id` `CA-NNN`, `condition`, `evidenceRequired`, `blocksReleaseHandoff`). The validator enforces verdict↔deliverable consistency: `accepted` ⇒ zero acceptance blockers, `blocked` ⇒ at least one, `conditional-accept` ⇒ at least one condition, and a `release-handoff` routing recommendation is allowed only when the verdict is `accepted`.
35
35
  - **Acceptance Blockers block** (under section 4): one row per blocker with `id`, `severity` (`critical` / `major` / `minor`), evidence (file path, log excerpt, or test output), and the recommended follow-up phase (`error-analysis` or `implementation-planning`). Empty block is acceptable and preferred — render the single line `- No acceptance blockers found.`
36
36
  - **Residual Risk block** (under section 4): risks that are not blockers but should be tracked, each with mitigation owner and a trigger that would escalate them to a blocker.
@@ -38,6 +38,7 @@
38
38
  - **Read-only command log**: any pre-existing test/validation command executed during this run MUST be listed with its exact command line and exit code. No mutating commands may appear here.
39
39
  - **Two-tier command lookup (shared with `implementation`):** when this phase performs its own independent re-validation, the command source is exactly the same two tiers `implementation` verifiers use — Tier 1 is the originating task brief / approved plan's `validation` set, Tier 2 is `<PROJECT_ROOT>/.okstra/project.json` under `qaCommands`. Auto-detecting tools from manifest files is forbidden; missing tiers are recorded as `qa-command not configured: <category>` rather than guessed. The `cmd` deny-list (`--fix`, `--write`, ` -w`, ` -u`, `--snapshot-update`, `INSTA_UPDATE=<not-no>`, `cargo update`, `npm install` without `ci`, etc.) is enforced identically. NOTE: runtime fail-fast validation (`okstra_ctl.qa_commands.validate_qa_commands`) only fires at `--task-type implementation` run-prep, so this phase MUST self-check each `qaCommands` entry against the deny-list before executing it — if a denied token is present, skip the command and record it as a `Read-only command log` line `qa-command rejected (denied token: <token>): <label>`.
40
40
  - **Tier 3 — stage conformance scripts (whole-task union):** because this phase verifies the **integrated, merged** state, it re-runs conformance against that state rather than per-stage. Read the task-level manifest `<task_root>/qa/conformance-manifest.json` (the directory is the `TASK_QA_PATH` token) and, in **whole-task scope**, run the `runCommand` of **every** `entries[]` item against the merged worktree, refreshing each `<task_root>/qa/result-<stageKey>.json` (`{ "stageKey", "overall": "PASS"|"FAIL"|"MISSING", "ranAt", "requirements" }`). In **single-stage scope**, run only the entry whose `stageKey` matches the verified stage. An entry carrying an `exemption` or user `waiver` is NOT executed — record the skip and reason; a `waiver` becomes a `conditional-accept` condition surfaced in the section 7 Verdict (conformance left unverified by user acknowledgement). Each `runCommand` runs in the worktree cwd with `qaEnv` env (replica DB DSN / app base URL / env file) — **replica / test environment only**, never shared / staging / prod, and the same source/lockfile mutation deny-list applies (a conformance script MAY mutate only its `qaEnv` replica datastore). Interpret each result from the exit code + stdout `QA-RESULT: PASS|FAIL` (last wins) and `REQ <id>: PASS|FAIL: <reason>` lines; no `QA-RESULT` marker → `MISSING`. Any entry whose result is not `PASS` (including `MISSING` or a never-run/missing sidecar) is an **Acceptance Blocker** (`major`+), so the verdict becomes `conditional-accept` / `blocked`. This is the same gate the `validate-run.py` Tier 3 check enforces on the result sidecars.
41
+ - **Manual user test results**: take each item from the source implementation report's §5.7.9 Manual User Test (Draft), execute the ones reproducible in this environment (e.g. `okstra-container up` then the documented steps), and record `result` (`pass` / `fail` / `blocked`) + observed value in §5.8.7 (data field `finalVerification.manualUserTest`). Steps that need human-only interaction this run cannot perform are recorded as `blocked` with the reason (handed to the user), never silently skipped. A failed manual test is an Acceptance Blocker. If the draft was an exemption (`applicable=false`), reaffirm the reason in one line (`applicable=false` + `exemptionReaffirm`).
41
42
  - **Routing recommendation**: the next safe phase — one of `release-handoff`, `done`, `error-analysis`, `implementation-planning` — tied to the verdict and blocker list. `release-handoff` is allowed ONLY when the Verdict Token is `accepted`. `release-handoff` is additionally allowed ONLY when the verification scope (the `Verification scope:` line of the injected `VERIFICATION_TARGET` block, recorded as the report's `verificationScope` field) is `whole-task`; a `single-stage` accepted run routes to `release-handoff(stage-group)` (or `implementation` / `done`); plain `release-handoff` remains whole-task-only. Enforcement: `validators/validate-run.py` rejects a `single-stage` report whose routing cites plain `release-handoff`.
42
43
  - **Verified-row recording** (single-stage scope only): when the Verdict Token is `accepted`, the lead MUST run `okstra handoff record-verified --plan-run-root <plan-run-root> --stage <N> --report-path <final-report.md path> --data-json <final-report data.json path>` and quote the command + exit code in the report. The helper re-validates taskType/scope/verdict from data.json, so a non-accepted or whole-task report is rejected at the tool layer.
43
44
  - Clarification request policy (phase-specific addendum — shared policy is in `_common-contract.md`):
@@ -54,7 +54,7 @@
54
54
  {{INCLUDE:_clarification-recommendation.md}}
55
55
  - **Evidence note required inside `Statement`**: every clarification row includes `Evidence checked: <path:line>` or `Evidence checked: none — <human-only reason>` in the `Statement` cell. `none` is allowed ONLY when the row's nature is "only a human can answer this" (reporter intent, business priority, organisational decision). A row with `none` that *could* have been answered by the codebase is a defect of this phase, restated from the pre-planning rule above.
56
56
  - Section heading contract (BLOCKING — validator scans for these literal English substrings):
57
- - The final report MUST include section headings containing each of the following exact strings: `Option Candidates`, `Trade-off`, `Recommended Option`, `Stage Map`, `Stage Exit Contract`, `Stage Validation`, `Dependency`, `Validation Checklist`, `Rollback`, `Requirement Coverage`. (Approval is no longer a body section — it is the YAML frontmatter `approved` field.)
57
+ - The final report MUST include section headings containing each of the following exact strings: `Option Candidates`, `Trade-off`, `Recommended Option`, `Stage Map`, `Stage Exit Contract`, `Stage Validation`, `Dependency`, `Cross-Project Dependencies`, `Decision Drafts`, `Validation Checklist`, `Rollback`, `Requirement Coverage`. (Approval is no longer a body section — it is the YAML frontmatter `approved` field.)
58
58
  - Korean translations are allowed in parentheses (e.g. `### Recommended Option (권장 옵션)`), but the English keyword must be present verbatim in the heading line.
59
59
  - The shape and ordering follow `final-report-template.md` sections 5.4 (`Implementation Plan Deliverables`) + 5.5 (`Stage Map`). `validators/validate-run.py` substring-matches the raw report text, so a Korean-only heading fails the gate — the cause of repeated observed failures.
60
60
  - Beyond substring matching, when the Plan Body Verification gate result is `passed` / `passed-with-dissent`, `validators/validate-run.py` runs the **structural** Stage Map validator (`validators/validate-implementation-plan-stages.py`) at the planning boundary — not deferred to the `implementation` entry gate. It enforces: the exact `## 5.5 Stage Map` heading, each `## 5.5.<i> Stage <i>:` section with its four required subsections, the per-stage effective step count (≤8), the `depends-on` DAG, and the per-stage vertical-slice contract (S10). S10 scans for the literal in-section strings `Slice value:`, `Acceptance:`, the three `Test case (success):` / `Test case (boundary):` / `Test case (failure):` lines (S10d), and the Stepwise `action`-cell prefixes `RED:` / `GREEN:` (or a `TDD exemption:` line, which waives both the test-case lines and the RED/GREEN check) — keep these tokens verbatim for the same reason as the heading keywords above.
@@ -88,16 +88,17 @@
88
88
  - **Vertical-slice-first partition rule (1st-class):** the grouping anchor is a **thin end-to-end vertical slice** — one stage delivers a single user-observable increment, crossing whatever layers are needed (data → service → API → UI) to make that one increment work. File/module proximity is demoted to the **intra-slice grouping rule**: within a slice, keep steps touching the same file/directory/module together so the diff, PR, and rollback unit stay cohesive. **Horizontal layer-splitting is forbidden** — never carve "the DB layer" into one stage and "the service layer" into the next; that produces stages that ship no standalone user value. A stage is split ONLY when (a) a real `depends-on` data/contract dependency exists, (b) effective steps would exceed 8, or (c) it is a distinct vertical slice (a different user-value increment). Maximising the number of parallel stages is NOT a reason to split — parallelism is an emergent property of independent stages, never a partitioning goal.
89
89
  - **Project-boundary partition rule (hard boundary):** a *project* boundary is either (a) a different repository / `PROJECT_ROOT`, or (b) a different top-level independently-deployable module within one repo. A stage maps to a single worktree on one repo/branch, so **no stage may contain edits belonging to more than one project** — this is a hard split that overrides the ≤8-step merging allowance; never co-locate two projects' changes in one stage to save a stage. Two cases:
90
90
  - **Same repo, different modules** — put each project/module's increment in its own stage. Their file sets are disjoint, so they default to `depends-on (none)` and run in parallel; add a `depends-on` link ONLY when a real cross-module contract / shared-schema / deploy-order dependency exists.
91
- - **Different repos** — a single okstra task **cannot** span repos: every stage worktree is a `git worktree add` in one repo's main checkout (one `<project-id>`), the run-index / manifests / registry are keyed to that one project, and the edit allowlist only covers that project's tree plus `~/.okstra/worktrees/**`. This is a structural limit, not a style preference. Therefore cross-repo work MUST be split into **a separate okstra run per repo** — never modelled as stages of one task. State, in the Stage Map `Cross-project parallelism:` line, whether those per-repo runs can proceed in parallel. To avoid re-deriving shared analysis, the second repo's run should consume the first run's relevant plan/decision artifacts as brief Source Material (see the cross-repo carry note below).
91
+ - **Different repos** — a single okstra task **cannot** span repos: every stage worktree is a `git worktree add` in one repo's main checkout (one `<project-id>`), the run-index / manifests / registry are keyed to that one project, and the edit allowlist only covers that project's tree plus `~/.okstra/worktrees/**`. This is a structural limit, not a style preference. Therefore cross-repo work MUST be split into **a separate okstra run per repo** — never modelled as stages of one task. State, in the Stage Map `Cross-project parallelism:` line, whether those per-repo runs can proceed in parallel. To avoid re-deriving shared analysis, the second repo's run should consume the first run's relevant plan/decision artifacts as brief Source Material (see the cross-project dependency rows below).
92
92
  - **Parallel-feasibility check (mandatory for every multi-project plan):** disjoint files (S9 below) is necessary but NOT sufficient for parallelism — a cross-project API/contract/schema/deploy-order dependency forces sequencing even when no file overlaps. For each pair of projects, explicitly determine and record (in the `Cross-project parallelism:` line) whether they are independent (run in parallel) or sequenced (and the exact dependency that forces the order).
93
93
  - **Parallel-safety invariant (BLOCKING):** any two stages that are both `depends-on (none)` MUST predict disjoint file sets in their `Stage Exit Contract`. Two parallel `implementation` runs would otherwise edit the same file concurrently. Work touching a shared file must either go in one stage or be ordered with `depends-on`. Enforced by `validators/validate-implementation-plan-stages.py` check S9.
94
- - **Cross-repo carry packet (multi-repo plans only the carry note referenced above):** when the plan identifies work in a repo *other* than this run's project, that work cannot be implemented here (Different-repos rule above). For each other repo, add a top-level report appendix section `## Cross-Repo Carry<repo>` with these three mandatory subsections, written so a reader who has never seen this run can act cold:
95
- - `### Preconditions already covered by this run` — what THIS run's repo delivers that `<repo>`'s work depends on (an endpoint, a published contract, a migration, a package version). For each, give the **observable verification signal** `<repo>`'s run must confirm before/while implementing (PR merged, endpoint live, version published) — NOT a `depends-on` stage link. A cross-repo dependency cannot be a `depends-on`: that gate resolves commits inside one repo's git graph only (`scripts/okstra_ctl/run.py` `_resolve_stage_base_commit`), so it is structurally incapable of pointing at another repo's commit.
96
- - `### Work for <repo> to implement` the self-contained B-portion: its requirements restated as fresh `R-NNN` from `<repo>`'s perspective, the proposed stages/steps, affected files, and any decision drafts. Carry ONLY what `<repo>` still has to build never carry this run's already-`done` stages as if they were `<repo>` stages.
97
- - `### How to start <repo>'s run` the exact handoff: in `<repo>` the user runs `okstra-brief` citing **this report's absolute path as Source Material** (the only sanctioned cross-`<PROJECT_ROOT>` read `okstra-brief` reads outside `<PROJECT_ROOT>/.okstra/**` only when the user explicitly cites the path), then `okstra-run` `<repo>`'s own planning implementation seeded by it.
98
- - **Recognition caveat (write it verbatim into the section):** `<repo>`'s run is structurally independent — its done-set comes only from its OWN `runs/.../consumers.jsonl`, its stage numbers are its own, and `depends-on` resolves against its own git graph. This run's completed stages are therefore NOT auto-recognised as `done` by `<repo>`'s gate (and must not be — they are this repo's work). The carry packet is narrative input that seeds `<repo>`'s planning; `<repo>` re-plans and implements only its own portion, treating this run's deliverables as the external preconditions above. This run MUST NOT write into `<repo>`'s tree or its `.okstra/`; it only emits the carry section in its own report.
94
+ - **Cross-project dependency rows (`crossProjectDependencies` 배열 — 옛 `## Cross-Repo Carry` 부록을 대체):** 타 프로젝트(다른 repo / 다른 top-level 독립 배포 모듈 / published 패키지) 대한 의존은 freeform `## Cross-Repo Carry` 부록이 아니라 **구조 필드 `crossProjectDependencies` `XP-NNN` 행**으로 기록한다. 프로젝트 의존마다 XP 행을 두고, 양방향 중 해당하는 `direction` 을 채운다(렌더는 `### Cross-Project Dependencies` §5.4; 단일 프로젝트 계획은 배열). 필드는 subsection 다음으로 대체한다`requiredWork`(상대가 만들어야 구체 작업) / `verificationSignal`(이 run 관측할 신호) / `linkedWork`(신호 충족 막히는 계획의 stage·step) / `howToStart`(상대 repo 에서의 정확한 handoff).
95
+ - `direction: upstream-precondition` — run 상대의 **선행 작업을 기다림(선행 필수)**: `requiredWork` = 상대가 먼저 구현해야 구체 작업, `verificationSignal` = run 진행 관측해야 신호(PR 머지 / 엔드포인트 live / 버전 publish), `linkedWork` = 신호 충족 막히는 계획의 stage·step, `howToStart` = 상대 repo 에서 `okstra-brief`(**이 보고서의 절대경로를 Source Material 인용** 유일하게 허용된 cross-`<PROJECT_ROOT>` read) → `okstra-run`.
96
+ - `direction: downstream-carry` run 이 상대가 쓸 것을 **만들어 줌(기존 carry)**: `requiredWork` = 상대가 후속 구현할 self-contained B-portion(상대 관점의 신규 `R-NNN`·제안 stage·영향 파일), `verificationSignal` = 상대가 진행 확인할(이 run 인도한) 신호, `linkedWork` = 신호를 인도하는 계획의 stage·step, `howToStart` upstream 동일. run 의 이미 `done` stage 상대 stage 양 넘기지 않는다 — 상대가 아직 만들어야 할 portion 만 담는다.
97
+ - **cross-repo 의존은 `depends-on` 으로 표현 불가:** 그 게이트는 한 repo git graph 내부 커밋만 해소하므로(`scripts/okstra_ctl/run.py` `_resolve_stage_base_commit`) 다른 repo 커밋을 가리킬 수 없다. cross-repo 작업은 stage 아니라 별도 okstra run + XP 행으로 분리한다(Different-repos 규칙). 상대 run 구조적으로 독립이라 run 완료 stage 를 `done` 으로 자동 인식하지 않으며(인식해서도 repo 작업), XP 행은 상대 planning 을 seed 하는 narrative 입력일 뿐이다. 이 run 은 상대 repo 트리·그 `.okstra/` 쓰지 않는다(이 보고서의 XP 행만 emit). 옛 "Recognition caveat" 문구는 렌더 노트(i18n `crossProjectRecognitionNote`)로 이전돼 `### Cross-Project Dependencies` 섹션에 자동 출력된다.
99
98
  - **Stage exit contract is the carry surface:** keep it as narrow as possible. Wider surface = more downstream coupling.
100
99
  - dependency / migration risk assessment (ordering constraints, data backfills, feature-flag prerequisites, repo-internal sequencing)
100
+ - **Cross-Project Dependencies (조건부 필수):** 계획이 다른 프로젝트 / repo / published 패키지의 작업에 의존하면 (a) `dependencyMigrationRisk` 에 `kind: cross-project` DM 행을, (b) `crossProjectDependencies` 에 매칭 `XP-NNN` 행을 둔다. upstream-precondition 행은 `requiredWork`·`verificationSignal`·`howToStart` 가 구체적이어야 한다 — `validators/validate-run.py` 가 DM `cross-project` ⇒ `direction: upstream-precondition` XP 행 ≥1 을, 스키마가 행 필드 비-빈을 강제한다. cross-project 의존은 soft Recommended Next Step 이 아니라 이 구조화된 선행 필수로 기록한다. 단일 프로젝트 계획은 빈 배열.
101
+ - **recommendedNextSteps 정책:** cross-project 선행/후속의 substance 는 `crossProjectDependencies` 에 두고, `§3 Recommended Next Steps` 에는 그 섹션(`§5.4 Cross-Project Dependencies`)을 가리키는 포인터만 둔다 — 이중 기록 금지.
101
102
  - validation checklist (pre / mid / post) — each item is an exact command or observable outcome
102
103
  - rollback strategy — exact revert path (commits, flags, migrations) and the signal that triggers rollback
103
104
  - **Requirement Coverage (mandatory, §5.5.8):** one row per concrete requirement from the task brief / packet. Assign stable IDs `R-001`, `R-002`, ... in source order. Columns: `ID | Source | Requirement | Covered by option / stage / step | Status`. `Source` cites the brief heading or file/line where the requirement came from. `Covered by` must name the specific Option Candidate and Stage/Step that satisfies it, not just "recommended option". `Status` is one of `covered`, `gap`, or `blocked C-NNN`. If any row is `gap` or `blocked C-NNN`, the Plan Body Verification gate MUST NOT be `passed` / `passed-with-dissent`; add a matching `Blocks=approval` row for the blocker and keep `approved: false`.
@@ -111,8 +112,8 @@
111
112
  1. **Hard to reverse** — would changing the decision later cost meaningfully more than deciding now?
112
113
  2. **Surprising without context** — would a future reader, seeing only the code, wonder "why was it built this way?"?
113
114
  3. **Real trade-off** — were there named alternatives, and was one picked for specific reasons?
114
- If **all three** hold, attach a decision draft as a report appendix section titled `Decision Drafts` (one decision per subsection). Each draft uses the `## Context / ## Decision / ## Consequences / ## Alternatives Considered` shape, names the alternatives that were rejected and why, and starts with `## Status: Proposed`. The next decision number is `(max existing in <PROJECT_ROOT>/.okstra/decisions/ + 1)` zero-padded to 4 digits. If any of the three criteria is missing, do NOT raise a decision draft — instead record `skipped adr-candidate: <topic> reason: <criterion that failed>` on one line under `Decision Drafts` so the next reader knows the candidate was evaluated and intentionally dropped.
115
- The drafts are NOT written by this phase. The approved plan's stepwise execution order MUST include the step `Create <PROJECT_ROOT>/.okstra/decisions/<NNNN>-<slug>.md from the decision draft in section X` so the `implementation` run commits the file inside okstra's subtree.
115
+ If **all three** hold, record a decision draft as a `decisionDrafts[]` row (rendered in §5.4 `### Decision Drafts`, one `#### <number>-<slug>` subsection each). Each row carries `number` (= `(max existing in <PROJECT_ROOT>/.okstra/decisions/ + 1)` zero-padded to ≥4 digits), `slug` (kebab-case), `status: Proposed`, and the `context` / `decision` / `consequences` / `alternativesConsidered` fields (`alternativesConsidered` names the rejected alternatives and why). If any of the three criteria is missing, do NOT raise a draft — instead record a `skippedAdrCandidates[]` row (`topic` + `reason: <criterion that failed>`, rendered as a `skipped adr-candidate: … — reason: …` line under the same section) so the next reader knows the candidate was evaluated and intentionally dropped.
116
+ The decision files are NOT written by this phase. The approved plan's stepwise execution order MUST include the step `Create <PROJECT_ROOT>/.okstra/decisions/<NNNN>-<slug>.md from the §5.4 Decision Drafts subsection <number>-<slug>` (materializing the structured fields into the file's `## Status / ## Context / ## Decision / ## Consequences / ## Alternatives Considered` shape) so the `implementation` run commits the file inside okstra's subtree. `validators/validate-run.py` enforces this: a non-empty `decisionDrafts` whose stages carry no stepwise step referencing `.okstra/decisions/` is `contract-violated`.
116
117
  - **Glossary proposals**: if a term or definition should become okstra institutional memory, add the step `Update <PROJECT_ROOT>/.okstra/glossary.md: <term> = <definition>` to the stepwise execution order. Use no other project-memory path.
117
118
  - No-placeholder rule (plan failures — reject any option or step that contains these):
118
119
  - "TBD", "TODO", "implement later", "fill in details", "add appropriate error handling", "handle edge cases", "write tests for the above" without actual test code
@@ -128,3 +129,5 @@
128
129
  6. **Review-rule preflight check** — if a project review rule pack exists, map each relevant rule to the recommended option. Reject the draft if it knowingly creates a violation that the later PR reviewer would flag, unless the plan records a specific rationale and follow-up. In particular, scan for repeated helper stacks across planned files, tests that assert delegation to the same calculator/helper they exercise, public names that hide side effects, domain rules placed in repositories/adapters, and APIs made dead by this change.
129
130
  7. **Plan-body verification reconciliation (BLOCKING for implementation-planning).** Inspect the `### 5.5.9 Plan Body Verification` verdict table. For every plan-item row classified as `majority-disagree → C-<N>`, the corresponding `C-<N>` row MUST exist in `## 1. Clarification Items` with `Kind` chosen per the standard policy and `Blocks=approval`. Do NOT create a parallel `Open Questions` block under the implementation plan body — the unified table is the single home. Conversely, the `Classification` column's `C-<N>` reference and the `## 1. Clarification Items` `ID` column MUST match 1:1; an orphan on either side is a contract violation. For `partial-consensus` and `worker-unique` plan-items, the dissenting opinion lives in §5.5.9 `Dissent log` and is NOT promoted to §5.
130
131
  8. **Stage Map self-check** — for every stage, count the effective rows of its `Stepwise Execution Order` table by hand; reject the draft if any stage exceeds 8. Confirm each stage declares a non-empty `Slice value:` and `Acceptance:` line, the three `Test case (success|boundary|failure):` lines (or carries a `TDD exemption:` line), and that its first step `action` starts with `RED:` with a later `GREEN:` — this is what validator S10 enforces, including S10d on the test-case lines. Read each stage's three test-case lines as a reviewer: reject any that restates the happy path in all three slots, leaves `boundary` blank, or writes `N/A` where a real edge input exists. Walk the `depends-on` graph and confirm it is a DAG (no cycle, no self-reference). For each `depends-on` link, confirm it encodes a real data/contract dependency — do NOT add links to serialise unrelated work, and do NOT split a stage merely to create more parallel stages. **Parallel-safety:** for every pair of `depends-on (none)` stages, confirm their `Stage Exit Contract` predicted file sets are disjoint; if they share a file, merge them or add a `depends-on` link (validator S9 rejects overlap). **Project-boundary:** confirm no stage mixes edits from two projects (different repo/`PROJECT_ROOT` or different top-level deployable module); if any stage does, split it per project. For multi-project plans, confirm each stage's `title` carries its `[<project>]` tag and the `Cross-project parallelism:` line under the table records the parallel-vs-sequenced determination (with the forcing dependency) for every project pair; for cross-repo work, confirm it is split into separate per-repo runs (required — one run structurally cannot touch another repo) rather than crammed into one task's stages.
132
+ 9. **Cross-project dependency check** — 타 repo / 다른 top-level 배포 모듈 / published 패키지에 대한 의존을 빠뜨리지 않았는지 확인한다. `dependencyMigrationRisk` 에 `kind: cross-project` 행이 있으면 매칭되는 `direction: upstream-precondition` `XP-NNN` 행이 `crossProjectDependencies` 에 있고, 그 `requiredWork` 가 추상 표현("상대 작업 완료")이 아니라 상대가 실제로 만들어야 할 구체 작업인지 reviewer 로서 다시 읽는다(validator S 가 존재만 보므로 구체성은 self-review 가 책임짐). cross-repo 작업을 한 task 의 stage 로 욱여넣지 않고 별도 run + XP 행으로 분리했는지, cross-project substance 가 `§3 Recommended Next Steps` 에 중복되지 않고 `§5.4 Cross-Project Dependencies` 에만 있는지 확인한다.
133
+ 10. **Decision-draft materialization check** — `decisionDrafts` 가 비어있지 않으면, 매칭 materialization step(`.okstra/decisions/<NNNN>-<slug>.md` 생성)이 어느 stage 의 stepwise 에 존재하는지, draft 개수와 materialization step 이 1:1 로 대응하는지 reviewer 로서 확인한다. validator 는 step 의 *존재*만 보므로 `<NNNN>-<slug>` 정확성·개수 대응은 self-review 가 책임진다.
@@ -1570,7 +1570,6 @@ def _reserve_final_verification_target(
1570
1570
  f"- **Verification scope:** `{target.scope}`\n"
1571
1571
  f"- **Worktree:** `{target.worktree_path}`\n"
1572
1572
  f"- **Verification base ref:** `{target.base}`\n"
1573
- f"- **Verification head SHA:** `{target.head}`\n"
1574
1573
  f"- **Stages under verification:** {target.stages}\n"
1575
1574
  f"- **Source implementation reports:**\n{reports}\n"
1576
1575
  f"- **Verification diff stat:**\n```\n{diff_stat}\n```"
@@ -360,7 +360,10 @@
360
360
  "validationChecklist",
361
361
  "rollbackStrategy",
362
362
  "requirementCoverage",
363
- "planBodyVerification"
363
+ "planBodyVerification",
364
+ "crossProjectDependencies",
365
+ "decisionDrafts",
366
+ "skippedAdrCandidates"
364
367
  ],
365
368
  "additionalProperties": false,
366
369
  "properties": {
@@ -410,7 +413,19 @@
410
413
  "minItems": 1,
411
414
  "items": { "$ref": "#/$defs/ImplementationRequirementCoverageRow" }
412
415
  },
413
- "planBodyVerification": { "$ref": "#/$defs/PlanBodyVerification" }
416
+ "planBodyVerification": { "$ref": "#/$defs/PlanBodyVerification" },
417
+ "crossProjectDependencies": {
418
+ "type": "array",
419
+ "items": { "$ref": "#/$defs/CrossProjectDependency" }
420
+ },
421
+ "decisionDrafts": {
422
+ "type": "array",
423
+ "items": { "$ref": "#/$defs/DecisionDraft" }
424
+ },
425
+ "skippedAdrCandidates": {
426
+ "type": "array",
427
+ "items": { "$ref": "#/$defs/SkippedAdrCandidate" }
428
+ }
414
429
  }
415
430
  },
416
431
 
@@ -526,6 +541,7 @@
526
541
  "validationEvidence",
527
542
  "verifierResults",
528
543
  "rollbackVerification",
544
+ "manualUserTest",
529
545
  "routingRecommendation"
530
546
  ],
531
547
  "additionalProperties": false,
@@ -580,6 +596,29 @@
580
596
  "minItems": 1,
581
597
  "items": { "$ref": "#/$defs/RollbackVerificationRow" }
582
598
  },
599
+ "manualUserTest": {
600
+ "type": "object",
601
+ "additionalProperties": false,
602
+ "required": ["applicable"],
603
+ "properties": {
604
+ "applicable": { "type": "boolean" },
605
+ "exemptionReason": { "type": "string" },
606
+ "items": {
607
+ "type": "array",
608
+ "items": {
609
+ "type": "object",
610
+ "additionalProperties": false,
611
+ "required": ["target", "environmentSetup", "steps", "expectedResult"],
612
+ "properties": {
613
+ "target": { "type": "string" },
614
+ "environmentSetup": { "type": "string" },
615
+ "steps": { "type": "array", "items": { "type": "string" } },
616
+ "expectedResult": { "type": "string" }
617
+ }
618
+ }
619
+ }
620
+ }
621
+ },
583
622
  "routingRecommendation": { "type": "string", "minLength": 1 }
584
623
  }
585
624
  },
@@ -593,6 +632,7 @@
593
632
  "residualRisk",
594
633
  "validationEvidence",
595
634
  "readonlyCommandLog",
635
+ "manualUserTest",
596
636
  "routingRecommendation"
597
637
  ],
598
638
  "additionalProperties": false,
@@ -640,6 +680,29 @@
640
680
  "type": "array",
641
681
  "items": { "$ref": "#/$defs/ReadonlyCommandRow" }
642
682
  },
683
+ "manualUserTest": {
684
+ "type": "object",
685
+ "additionalProperties": false,
686
+ "required": ["applicable"],
687
+ "properties": {
688
+ "applicable": { "type": "boolean" },
689
+ "exemptionReaffirm": { "type": "string" },
690
+ "results": {
691
+ "type": "array",
692
+ "items": {
693
+ "type": "object",
694
+ "additionalProperties": false,
695
+ "required": ["target", "performed", "result", "observed"],
696
+ "properties": {
697
+ "target": { "type": "string" },
698
+ "performed": { "type": "string" },
699
+ "result": { "type": "string" },
700
+ "observed": { "type": "string" }
701
+ }
702
+ }
703
+ }
704
+ }
705
+ },
643
706
  "routingRecommendation": { "type": "string", "minLength": 1 }
644
707
  }
645
708
  },
@@ -1318,7 +1381,7 @@
1318
1381
  "properties": {
1319
1382
  "id": { "type": "string", "pattern": "^DM-\\d{3,}$" },
1320
1383
  "kind": {
1321
- "enum": ["order", "backfill", "flag-precondition", "repo-sequencing", "other"]
1384
+ "enum": ["order", "backfill", "flag-precondition", "repo-sequencing", "cross-project", "other"]
1322
1385
  },
1323
1386
  "item": { "type": "string", "minLength": 1 },
1324
1387
  "impact": { "type": "string", "minLength": 1 },
@@ -1326,6 +1389,46 @@
1326
1389
  }
1327
1390
  },
1328
1391
 
1392
+ "CrossProjectDependency": {
1393
+ "type": "object",
1394
+ "required": ["id", "direction", "project", "requiredWork", "verificationSignal", "linkedWork", "howToStart"],
1395
+ "additionalProperties": false,
1396
+ "properties": {
1397
+ "id": { "type": "string", "pattern": "^XP-\\d{3,}$" },
1398
+ "direction": { "enum": ["upstream-precondition", "downstream-carry"] },
1399
+ "project": { "type": "string", "minLength": 1 },
1400
+ "requiredWork": { "type": "string", "minLength": 1 },
1401
+ "verificationSignal": { "type": "string", "minLength": 1 },
1402
+ "linkedWork": { "type": "string", "minLength": 1 },
1403
+ "howToStart": { "type": "string", "minLength": 1 }
1404
+ }
1405
+ },
1406
+
1407
+ "DecisionDraft": {
1408
+ "type": "object",
1409
+ "required": ["number", "slug", "status", "context", "decision", "consequences", "alternativesConsidered"],
1410
+ "additionalProperties": false,
1411
+ "properties": {
1412
+ "number": { "type": "string", "pattern": "^\\d{4,}$" },
1413
+ "slug": { "type": "string", "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" },
1414
+ "status": { "enum": ["Proposed"] },
1415
+ "context": { "type": "string", "minLength": 1 },
1416
+ "decision": { "type": "string", "minLength": 1 },
1417
+ "consequences": { "type": "string", "minLength": 1 },
1418
+ "alternativesConsidered": { "type": "string", "minLength": 1 }
1419
+ }
1420
+ },
1421
+
1422
+ "SkippedAdrCandidate": {
1423
+ "type": "object",
1424
+ "required": ["topic", "reason"],
1425
+ "additionalProperties": false,
1426
+ "properties": {
1427
+ "topic": { "type": "string", "minLength": 1 },
1428
+ "reason": { "type": "string", "minLength": 1 }
1429
+ }
1430
+ },
1431
+
1329
1432
  "ValidationCheckRow": {
1330
1433
  "type": "object",
1331
1434
  "required": ["id", "phase", "ticketId", "check", "commandOrObservation", "expectedOutcome"],
@@ -78,7 +78,7 @@ okstra container up --project-root <projectRoot> --task-key <task-key>
78
78
 
79
79
  Parse the stdout JSON and report the provisioned services and the watcher pane. If a service failed its healthcheck, the call surfaces the failing services and the `docker compose ... logs` line to inspect — relay that line; do not invent your own.
80
80
 
81
- After a successful `up`, tell the user how to reach the running build (the compose file's published ports) and that `okstra container status <task-key>` / `okstra container down <task-key>` manage it from here.
81
+ After a successful `up`, tell the user how to reach the running build (the compose file's published ports) and that `okstra container status <task-key>` / `okstra container down <task-key>` manage it from here. For *what to verify* once it is up, point the user to the implementation report's §5.7.9 Manual User Test (Draft) — its steps and expected results are the manual test script for this build.
82
82
 
83
83
  ---
84
84
 
@@ -217,6 +217,22 @@ implementation-option: {{ frontmatter.implementationOption | yaml_scalar }}
217
217
  {% endfor %}
218
218
  {%- endif %}
219
219
 
220
+ ### Cross-Project Dependencies{% if t("sectionAside.crossProjectDependencies") != "Cross-Project Dependencies" %} ({{ t("sectionAside.crossProjectDependencies") }}){% endif %}
221
+
222
+ {% if implementationPlanning.crossProjectDependencies | length == 0 -%}
223
+ {{ t("emptyState.crossProjectDependencies") }}
224
+ {%- else %}
225
+ {% for xp in implementationPlanning.crossProjectDependencies %}
226
+ **{{ xp.id }} — [{{ xp.direction }}] {{ xp.project }}**
227
+
228
+ - {{ t("implementationPlanning.crossProjectFields.requiredWork") }}: {{ xp.requiredWork }}
229
+ - {{ t("implementationPlanning.crossProjectFields.verificationSignal") }}: {{ xp.verificationSignal }}
230
+ - {{ t("implementationPlanning.crossProjectFields.linkedWork") }}: {{ xp.linkedWork }}
231
+ - {{ t("implementationPlanning.crossProjectFields.howToStart") }}: {{ xp.howToStart }}
232
+ {% endfor %}
233
+ {{ t("implementationPlanning.crossProjectRecognitionNote") }}
234
+ {%- endif %}
235
+
220
236
  ### Validation Checklist{% if t("sectionAside.validationChecklist") != "Validation Checklist" %} ({{ t("sectionAside.validationChecklist") }}){% endif %}
221
237
 
222
238
  | ID | Phase | Ticket ID | Check | Command / Observation | Expected outcome |
@@ -241,6 +257,25 @@ implementation-option: {{ frontmatter.implementationOption | yaml_scalar }}
241
257
  | {{ row.id | mdcell }} | `{{ row.source | mdcell }}` | {{ row.requirement | mdcell }} | {{ row.coveredBy | mdcell }} | `{{ row.status | mdcell }}` |
242
258
  {% endfor %}
243
259
 
260
+ ### Decision Drafts{% if t("sectionAside.decisionDrafts") != "Decision Drafts" %} ({{ t("sectionAside.decisionDrafts") }}){% endif %}
261
+
262
+ {% if (implementationPlanning.decisionDrafts | length == 0) and (implementationPlanning.skippedAdrCandidates | length == 0) -%}
263
+ {{ t("emptyState.decisionDrafts") }}
264
+ {%- else %}
265
+ {% for d in implementationPlanning.decisionDrafts %}
266
+ #### {{ d.number }}-{{ d.slug }}
267
+
268
+ - **{{ t("implementationPlanning.decisionDraftFields.status") }}:** {{ d.status }}
269
+ - **{{ t("implementationPlanning.decisionDraftFields.context") }}:** {{ d.context }}
270
+ - **{{ t("implementationPlanning.decisionDraftFields.decision") }}:** {{ d.decision }}
271
+ - **{{ t("implementationPlanning.decisionDraftFields.consequences") }}:** {{ d.consequences }}
272
+ - **{{ t("implementationPlanning.decisionDraftFields.alternativesConsidered") }}:** {{ d.alternativesConsidered }}
273
+ {% endfor %}
274
+ {% for s in implementationPlanning.skippedAdrCandidates %}
275
+ - skipped adr-candidate: {{ s.topic }} — reason: {{ s.reason }}
276
+ {% endfor %}
277
+ {%- endif %}
278
+
244
279
  ## 5.5 Stage Map
245
280
 
246
281
  | stage | title | depends-on | step-count | exit-contract-summary |
@@ -482,7 +517,21 @@ implementation-option: {{ frontmatter.implementationOption | yaml_scalar }}
482
517
  | {{ row.category | mdcell }} | `{{ row.rollbackCommand | mdcell }}` | {{ row.verification | mdcell }} | `{{ row.result | mdcell }}` |
483
518
  {% endfor %}
484
519
 
485
- ### 5.7.9 Routing Recommendation
520
+ ### 5.7.9 Manual User Test (Draft)
521
+
522
+ {% if implementation.manualUserTest.applicable -%}
523
+ {% for item in implementation.manualUserTest.items %}
524
+ - **{{ item.target }}**
525
+ - {{ t("manualUserTest.environmentSetup") }}: {{ item.environmentSetup }}
526
+ - {{ t("manualUserTest.steps") }}:
527
+ {% for step in item.steps %} {{ loop.index }}. {{ step }}
528
+ {% endfor %} - {{ t("manualUserTest.expectedResult") }}: {{ item.expectedResult }}
529
+ {% endfor %}
530
+ {%- else -%}
531
+ - {{ t("manualUserTest.notApplicable") }}: {{ implementation.manualUserTest.exemptionReason }}
532
+ {%- endif %}
533
+
534
+ ### 5.7.10 Routing Recommendation
486
535
 
487
536
  {{ implementation.routingRecommendation }}
488
537
 
@@ -565,7 +614,19 @@ implementation-option: {{ frontmatter.implementationOption | yaml_scalar }}
565
614
  {% endfor %}
566
615
  {%- endif %}
567
616
 
568
- ### 5.8.7 Routing Recommendation
617
+ ### 5.8.7 Manual User Test Results
618
+
619
+ {% if finalVerification.manualUserTest.applicable -%}
620
+ | {{ t("manualUserTest.performed") }} | {{ t("manualUserTest.result") }} | {{ t("manualUserTest.observed") }} |
621
+ |---|---|---|
622
+ {% for row in finalVerification.manualUserTest.results -%}
623
+ | {{ row.target | mdcell }} — {{ row.performed | mdcell }} | `{{ row.result | mdcell }}` | {{ row.observed | mdcell }} |
624
+ {% endfor %}
625
+ {%- else -%}
626
+ - {{ t("manualUserTest.notApplicable") }}: {{ finalVerification.manualUserTest.exemptionReaffirm }}
627
+ {%- endif %}
628
+
629
+ ### 5.8.8 Routing Recommendation
569
630
 
570
631
  {{ finalVerification.routingRecommendation }}
571
632