okstra 0.46.0 → 0.48.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,570 @@
1
+ # 적대적 Phase 5.5 검증 구현 계획
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** `requirements-discovery` / `error-analysis` 두 phase 의 Phase 5.5 convergence 재검증을, 검증자가 다른 워커의 주장을 적극적으로 반박하고 입증 책임을 주장 쪽에 두는 적대적 검증으로 전환한다.
6
+
7
+ **Architecture:** 새 에이전트·스테이지를 만들지 않는다. manifest `convergence` 블록에 `adversarial` 플래그를 phase-aware 로 주입하고(render.py), convergence skill 이 `adversarial=true` 분기에서 적대적 프롬프트·집계·범위-한정 재조사를 정의한다. verdict 영속 enum 은 유지하고 신규 `disagreeBasis` 필드로 적대성을 기록한다.
8
+
9
+ **Tech Stack:** Python 3 (okstra_ctl, pytest), Markdown skill/prompt 문서, JSON fixture. 빌드: `tools/build.mjs`(`npm run build`).
10
+
11
+ **설계 근거:** [`docs/superpowers/specs/2026-06-04-adversarial-verification-design.md`](../specs/2026-06-04-adversarial-verification-design.md)
12
+
13
+ ---
14
+
15
+ ## 파일 구조
16
+
17
+ | 파일 | 책임 | 작업 |
18
+ |---|---|---|
19
+ | [`scripts/okstra_ctl/render.py`](../../../scripts/okstra_ctl/render.py) | manifest `convergence` 블록에 `adversarial`/`verificationMode` phase-aware 주입 | Modify (`_build_convergence_block`, 899–926) |
20
+ | `tests/test_render_convergence_adversarial.py` | render 주입값 단위 테스트 | Create |
21
+ | [`tests/test_convergence_state_contract.py`](../../../tests/test_convergence_state_contract.py) | 상태 스키마 1.2 + `disagreeBasis` + `config.adversarial` 형태 강제 | Modify |
22
+ | `tests/fixtures/convergence/adversarial-contested.json` | 적대적 contested 케이스 fixture | Create |
23
+ | [`skills/okstra-convergence/SKILL.md`](../../../skills/okstra-convergence/SKILL.md) | 적대적 모드 동작 정의(프롬프트·집계·범위·스키마) | Modify |
24
+ | [`prompts/profiles/requirements-discovery.md`](../../../prompts/profiles/requirements-discovery.md) | Phase 5.5 적대적 선언 | Modify |
25
+ | [`prompts/profiles/error-analysis.md`](../../../prompts/profiles/error-analysis.md) | Phase 5.5 적대적 선언 | Modify |
26
+ | [`prompts/profiles/_common-contract.md`](../../../prompts/profiles/_common-contract.md) | Worker interaction model 의 Phase 5.5 설명 갱신 | Modify |
27
+ | [`CHANGES.md`](../../../CHANGES.md) | `사용자 영향:` 항목 | Modify |
28
+
29
+ 작업 순서: render(코드) → contract 테스트/fixture(데이터) → skill 문서(동작) → 프로필/계약 선언 → CHANGES + 빌드 + 전체 검증.
30
+
31
+ ---
32
+
33
+ ### Task 1: render.py — `adversarial`/`verificationMode` phase-aware 주입
34
+
35
+ **Files:**
36
+ - Create: `tests/test_render_convergence_adversarial.py`
37
+ - Modify: `scripts/okstra_ctl/render.py:899-926`
38
+
39
+ - [ ] **Step 1: 실패하는 테스트 작성**
40
+
41
+ Create `tests/test_render_convergence_adversarial.py`:
42
+
43
+ ```python
44
+ """_build_convergence_block — adversarial 모드 phase-aware 기본값 단위 테스트.
45
+
46
+ requirements-discovery / error-analysis 만 adversarial=true + full-reanalysis 를
47
+ 받고, 나머지 phase 는 협조적 lightweight 를 유지한다. maxRounds 의 기존 분기는
48
+ adversarial 도입과 무관하게 보존된다.
49
+ """
50
+ from __future__ import annotations
51
+
52
+ import sys
53
+ from pathlib import Path
54
+
55
+ import pytest
56
+
57
+ _REPO_ROOT = Path(__file__).resolve().parents[1]
58
+ sys.path.insert(0, str(_REPO_ROOT / "scripts"))
59
+
60
+ from okstra_ctl.render import _build_convergence_block # noqa: E402
61
+
62
+
63
+ @pytest.mark.parametrize("task_type", ["requirements-discovery", "error-analysis"])
64
+ def test_adversarial_phases_get_adversarial_full_reanalysis(task_type):
65
+ block = _build_convergence_block({"TASK_TYPE": task_type})
66
+ assert block["adversarial"] is True
67
+ assert block["verificationMode"] == "full-reanalysis"
68
+
69
+
70
+ @pytest.mark.parametrize(
71
+ "task_type",
72
+ [
73
+ "implementation-planning",
74
+ "implementation",
75
+ "final-verification",
76
+ "release-handoff",
77
+ ],
78
+ )
79
+ def test_non_adversarial_phases_stay_lightweight(task_type):
80
+ block = _build_convergence_block({"TASK_TYPE": task_type})
81
+ assert block["adversarial"] is False
82
+ assert block["verificationMode"] == "lightweight"
83
+
84
+
85
+ def test_maxrounds_unchanged_by_adversarial():
86
+ assert _build_convergence_block({"TASK_TYPE": "requirements-discovery"})["maxRounds"] == 1
87
+ assert _build_convergence_block({"TASK_TYPE": "error-analysis"})["maxRounds"] == 2
88
+ ```
89
+
90
+ - [ ] **Step 2: 테스트 실패 확인**
91
+
92
+ Run: `python3 -m pytest tests/test_render_convergence_adversarial.py -v`
93
+ Expected: FAIL — `KeyError: 'adversarial'` (블록에 키가 아직 없음).
94
+
95
+ - [ ] **Step 3: 최소 구현**
96
+
97
+ Modify `_build_convergence_block` body in `scripts/okstra_ctl/render.py` (현재 899–926). 본문 교체:
98
+
99
+ ```python
100
+ task_type = ctx.get("TASK_TYPE", "")
101
+ default_max_rounds = 1 if task_type == "requirements-discovery" else 2
102
+ adversarial_phases = {"requirements-discovery", "error-analysis"}
103
+ is_adversarial = task_type in adversarial_phases
104
+ raw_plan_verify = (ctx.get("OKSTRA_PLAN_VERIFICATION", "") or "").strip().lower()
105
+ plan_verify_enabled = raw_plan_verify != "false"
106
+ return {
107
+ "enabled": True,
108
+ "adversarial": is_adversarial,
109
+ "maxRounds": default_max_rounds,
110
+ "verificationMode": "full-reanalysis" if is_adversarial else "lightweight",
111
+ "planBodyVerification": {
112
+ "enabled": plan_verify_enabled,
113
+ "maxRounds": 1,
114
+ "gating": True,
115
+ },
116
+ }
117
+ ```
118
+
119
+ 그리고 docstring(900–912)의 defaults 목록에 한 줄 추가 — `- verificationMode "lightweight"` 다음 줄에:
120
+
121
+ ```python
122
+ - `adversarial` default True for `requirements-discovery` / `error-analysis`
123
+ (forces `verificationMode` to "full-reanalysis"), False otherwise
124
+ ```
125
+
126
+ - [ ] **Step 4: 테스트 통과 확인**
127
+
128
+ Run: `python3 -m pytest tests/test_render_convergence_adversarial.py -v`
129
+ Expected: PASS (3 tests / 6 parametrized cases).
130
+
131
+ - [ ] **Step 5: 커밋**
132
+
133
+ ```bash
134
+ git add scripts/okstra_ctl/render.py tests/test_render_convergence_adversarial.py
135
+ git commit -m "feat(okstra_ctl/render): inject adversarial convergence mode for discovery/error-analysis"
136
+ ```
137
+
138
+ ---
139
+
140
+ ### Task 2: convergence 상태 contract 테스트 + 적대적 fixture
141
+
142
+ 스키마를 1.2 로 올리고 `config.adversarial` 와 `votes.<worker>.disagreeBasis` 형태를 강제한다. 기존 1.1 fixture 3개는 그대로 통과해야 한다(신규 필드는 optional).
143
+
144
+ **Files:**
145
+ - Create: `tests/fixtures/convergence/adversarial-contested.json`
146
+ - Modify: `tests/test_convergence_state_contract.py`
147
+
148
+ - [ ] **Step 1: 적대적 fixture 작성 (실패 유발 데이터)**
149
+
150
+ Create `tests/fixtures/convergence/adversarial-contested.json`. requirements-discovery 스타일(effectiveMaxRounds=1, 단일 라운드=마지막 라운드)로, codex 의 `counter-evidence` 반박 1건이 F-001 을 `contested` 로 강등하는 케이스:
151
+
152
+ ```json
153
+ {
154
+ "schemaVersion": "1.2",
155
+ "taskKey": "fixture/adversarial-contested",
156
+ "config": {
157
+ "enabled": true,
158
+ "adversarial": true,
159
+ "maxRounds": 1,
160
+ "effectiveMaxRounds": 1,
161
+ "verificationMode": "full-reanalysis"
162
+ },
163
+ "findings": [
164
+ {
165
+ "findingId": "F-001",
166
+ "summary": "Login handler skips input validation",
167
+ "category": "bug",
168
+ "ticketIds": ["AD-100"],
169
+ "originWorker": "claude-worker",
170
+ "originEvidence": "src/auth/login.ts:42",
171
+ "classification": "contested",
172
+ "rounds": [
173
+ {
174
+ "round": 1,
175
+ "votes": {
176
+ "codex-worker": {
177
+ "verdict": "disagree",
178
+ "disagreeBasis": "counter-evidence",
179
+ "explanation": "src/auth/login.ts:48 already runs validateBody(); the claimed gap does not exist."
180
+ },
181
+ "gemini-worker": {
182
+ "verdict": "agree",
183
+ "disagreeBasis": null,
184
+ "explanation": "Re-read login.ts; could not break the claim."
185
+ }
186
+ }
187
+ }
188
+ ],
189
+ "consensusWorkers": ["claude-worker", "gemini-worker"],
190
+ "dissentingWorkers": ["codex-worker"]
191
+ }
192
+ ],
193
+ "roundHistory": [
194
+ {
195
+ "round": 1,
196
+ "inputQueueSize": 1,
197
+ "resolvedCount": 0,
198
+ "carriedForwardCount": 1,
199
+ "dispatches": [
200
+ {"worker": "codex-worker", "status": "completed", "durationMs": 173004},
201
+ {"worker": "gemini-worker", "status": "completed", "durationMs": 188210}
202
+ ],
203
+ "skippedWorkers": [
204
+ {"worker": "claude-worker", "reason": "no items to verify"}
205
+ ]
206
+ }
207
+ ],
208
+ "round2SkippedReason": "max-rounds-1",
209
+ "finalState": "max-rounds-reached",
210
+ "totalRounds": 1,
211
+ "finalClassificationCounts": {
212
+ "fullConsensus": 0,
213
+ "partialConsensus": 0,
214
+ "contested": 1,
215
+ "workerUnique": 0
216
+ }
217
+ }
218
+ ```
219
+
220
+ - [ ] **Step 2: 기존 테스트가 새 fixture 에서 깨지는지 확인 (red)**
221
+
222
+ Run: `python3 -m pytest tests/test_convergence_state_contract.py -k adversarial -v`
223
+ Expected: FAIL — `test_schema_version_is_1_1[adversarial-contested]` 가 `"1.2" == "1.1"` 단언에서 실패.
224
+
225
+ - [ ] **Step 3: contract 테스트를 1.2 수용 + 적대적 형태 검증으로 갱신**
226
+
227
+ Edit `tests/test_convergence_state_contract.py`:
228
+
229
+ (a) 모듈 docstring 첫 줄(1행)을 `(schema v1.1)` → `(schema v1.1 / v1.2)` 로 수정.
230
+
231
+ (b) `VALID_VERDICTS = {...}` 정의(32행) 바로 다음에 추가:
232
+
233
+ ```python
234
+ VALID_DISAGREE_BASIS = {"counter-evidence", "burden-not-met", None}
235
+ ```
236
+
237
+ (c) `test_schema_version_is_1_1`(40–41행) 전체를 교체:
238
+
239
+ ```python
240
+ def test_schema_version_is_supported(fixture):
241
+ assert fixture["schemaVersion"] in {"1.1", "1.2"}
242
+ ```
243
+
244
+ (d) 파일 끝에 신규 테스트 2개 추가:
245
+
246
+ ```python
247
+ def test_disagree_basis_is_enum_when_present(fixture):
248
+ for f in fixture["findings"]:
249
+ for r in f["rounds"]:
250
+ for vote in r["votes"].values():
251
+ if "disagreeBasis" in vote:
252
+ assert vote["disagreeBasis"] in VALID_DISAGREE_BASIS
253
+
254
+
255
+ def test_adversarial_disagree_carries_basis(fixture):
256
+ """In an adversarial run every disagree vote must cite a refutation basis."""
257
+ if not fixture["config"].get("adversarial"):
258
+ return
259
+ for f in fixture["findings"]:
260
+ for r in f["rounds"]:
261
+ for worker, vote in r["votes"].items():
262
+ if vote["verdict"] == "disagree":
263
+ assert vote.get("disagreeBasis") in {"counter-evidence", "burden-not-met"}, (
264
+ f"{f['findingId']} {worker}: adversarial disagree without disagreeBasis"
265
+ )
266
+ ```
267
+
268
+ - [ ] **Step 4: 전체 contract 테스트 통과 확인**
269
+
270
+ Run: `python3 -m pytest tests/test_convergence_state_contract.py -v`
271
+ Expected: PASS — 기존 3 fixture(1.1) + 신규 1 fixture(1.2) 전부 통과. `config.get("adversarial")` 가 1.1 fixture 에서 `None`(falsy) 이라 `test_adversarial_disagree_carries_basis` 는 그들에 대해 no-op.
272
+
273
+ - [ ] **Step 5: 커밋**
274
+
275
+ ```bash
276
+ git add tests/test_convergence_state_contract.py tests/fixtures/convergence/adversarial-contested.json
277
+ git commit -m "test(convergence): accept schema v1.2 with adversarial config + disagreeBasis"
278
+ ```
279
+
280
+ ---
281
+
282
+ ### Task 3: convergence SKILL.md — 적대적 모드 동작 정의
283
+
284
+ 이 Task 는 적대적 *행동*의 authoritative 선언이다(코드 강제 불가, lead/워커 prompt 지시). 다섯 군데를 편집한다. 각 Edit 는 기존 텍스트를 anchor 로 잡는다.
285
+
286
+ **Files:**
287
+ - Modify: `skills/okstra-convergence/SKILL.md`
288
+
289
+ - [ ] **Step 1: Configuration 표에 `adversarial` 행 추가**
290
+
291
+ Edit — `| `verificationMode` | `"lightweight"` | `"lightweight"` or `"full-reanalysis"` |` 행(48행) 다음에 새 행 삽입:
292
+
293
+ ```markdown
294
+ | `adversarial` | phase-aware: `true` for `requirements-discovery` / `error-analysis`, `false` otherwise | When `true`, Phase 5.5 runs in **adversarial mode** (see §"Adversarial Verification Mode"): verifiers actively try to refute each finding, the burden of proof sits on the claim, and `verificationMode` is forced to `"full-reanalysis"` scoped to the finding's cited evidence. Resolved by `scripts/okstra_ctl/render.py` `_build_convergence_block` and recorded in `config.adversarial` of the convergence state artifact. |
295
+ ```
296
+
297
+ - [ ] **Step 2: 신규 §"Adversarial Verification Mode" 절 추가**
298
+
299
+ Edit — §"Verification Mode" 의 "Full Re-analysis (opt-in)" 블록 끝(`Disadvantages: 2–3 times the cost, increased time` 줄, 193행) 다음에 새 절 삽입:
300
+
301
+ ```markdown
302
+
303
+ ## Adversarial Verification Mode
304
+
305
+ Active only when `config.adversarial == true` (default for `requirements-discovery` and `error-analysis`; see §"Configuration"). When `false`, every rule in this section is inert and the collaborative behaviour documented elsewhere in this skill applies unchanged.
306
+
307
+ In adversarial mode the verifier's job inverts: instead of confirming a peer's finding, the verifier **tries to break it**, and the burden of proof sits on the claim — a finding survives only if refutation attempts fail.
308
+
309
+ ### Scoped full-reanalysis (BLOCKING)
310
+
311
+ Adversarial mode forces `verificationMode = "full-reanalysis"`, but the re-analysis is **scoped to the evidence the finding under attack cites** (the file paths / line ranges / log lines in its `originEvidence`), plus the immediately surrounding context. The verifier MUST NOT re-read the whole task brief, instruction-set, or `final-report-template.md`. This keeps the documented "single largest avoidable cost in requirements-discovery and error-analysis" (see §"Reverify prompt: required-reading suppression") bounded while making the refutation real rather than a text-only argument.
312
+
313
+ ### Adversarial verdict semantics
314
+
315
+ The persisted `verdict` enum is unchanged (`agree | disagree | supplement | verification-error`). The prompt-facing labels are adversarial and map down on persistence:
316
+
317
+ | Prompt label | Persisted `verdict` | Meaning |
318
+ |---|---|---|
319
+ | SURVIVES | `agree` | Actively tried to refute and failed — the claim withstood the attack. |
320
+ | SURVIVES-WITH-CAVEAT | `supplement` | Holds, but a scope limit / extra condition / precondition was found. |
321
+ | REFUTED | `disagree` | The claim was broken (or failed to prove itself). MUST carry a `disagreeBasis`. |
322
+
323
+ Each `disagree` vote records a new field `disagreeBasis`:
324
+
325
+ | `disagreeBasis` | Meaning |
326
+ |---|---|
327
+ | `counter-evidence` | The verifier cited contradicting evidence (`file:line` / log line) in `explanation`. A **hard refute**. |
328
+ | `burden-not-met` | The verifier re-inspected the cited evidence and could neither confirm nor refute → the claim failed to prove itself ("when uncertain, lean to rejection"). |
329
+
330
+ A `disagree` with `disagreeBasis == null` is a contract violation in adversarial mode — every refutation must state which of the two grounds it rests on. Bare "I disagree" without re-inspection is not allowed.
331
+
332
+ ### Adversarial classification (replaces the §"Convergence Algorithm" per-round classifier when `adversarial == true`)
333
+
334
+ `verification-error` votes are excluded from numerator and denominator exactly as in the collaborative classifier. For each finding `F` in the queue at a round:
335
+
336
+ ```text
337
+ disagrees = [v for v in non-error votes if v.verdict == "disagree"]
338
+ hard_refutes = [v for v in disagrees if v.disagreeBasis == "counter-evidence"]
339
+ all_others_disagree = (every non-discoverer non-error vote is "disagree")
340
+
341
+ IF len(disagrees) == 0:
342
+ resolve F as "full-consensus" (or "partial-consensus" if any SUPPLEMENT/caveat)
343
+ ELIF all_others_disagree:
344
+ resolve F as "worker-unique" # only the discoverer still holds it
345
+ ELIF len(hard_refutes) >= 1:
346
+ # an evidence-backed refute exists and the roster is split → the claim is disputed
347
+ carry F forward; at the LAST executed round classify it "contested"
348
+ ELIF burden-not-met disagrees are a majority of non-error votes:
349
+ carry F forward; at the LAST executed round classify it "contested"
350
+ ELSE:
351
+ # a lone weak (burden-not-met) doubt against an otherwise-surviving claim
352
+ resolve F as "partial-consensus"
353
+ ```
354
+
355
+ `contested` remains a **final classification only** (per §"Scope and Terminology"): a disputed finding is carried forward through intermediate rounds and labelled `contested` only at the last executed round. For `requirements-discovery` (`effectiveMaxRounds = 1`) the single round IS the last round, so a split-with-hard-refute finding is labelled `contested` in that one round. The final-classifier block of §"Convergence Algorithm" is unchanged; this section only changes how each round's verdicts resolve into queue actions.
356
+
357
+ Design intent: one `counter-evidence` refute is enough to deny a claim consensus (it cannot rise above `contested` no matter how many others AGREE), while a single `burden-not-met` doubt does not by itself sink an otherwise-surviving claim — only a majority of burden-not-met doubts does.
358
+ ```
359
+
360
+ - [ ] **Step 3: 적대적 재검증 프롬프트 추가**
361
+
362
+ Edit — §"Lightweight Re-verification Prompt" 의 코드펜스가 끝나는 지점(283행 `**Verdict**: ...` 다음 ` ``` ` 줄) 다음에 새 하위 절 삽입:
363
+
364
+ ```markdown
365
+
366
+ ### Adversarial Re-verification Prompt
367
+
368
+ Used instead of the lightweight/full-reanalysis prompt when `config.adversarial == true`. The required anchor headers (§"Required reverify-prompt anchor headers") are identical. The `[Required reading]` clause is suppressed; only the cited-evidence paths of the items under attack are injected (see §"Adversarial Verification Mode" → Scoped full-reanalysis).
369
+
370
+ ```
371
+ You are <worker-role> performing ADVERSARIAL re-verification for <task-key> (round <N>).
372
+
373
+ ## Instructions
374
+
375
+ Your job is to BREAK each finding below, not to confirm it. For EACH finding,
376
+ open the cited evidence directly and actively search for evidence that the claim
377
+ is wrong, overstated, or unproven. Then respond with exactly one verdict:
378
+
379
+ - **REFUTED**: You broke the claim. State the basis:
380
+ - counter-evidence — you found contradicting evidence (give file:line or log line), OR
381
+ - burden-not-met — you re-inspected the cited evidence and could neither confirm
382
+ nor refute it (the claim has not proven itself).
383
+ - **SURVIVES**: You actively tried to refute it and failed — the claim withstood the attack.
384
+ - **SURVIVES-WITH-CAVEAT**: It holds, but a scope limit / extra condition / missing
385
+ precondition exists (state it).
386
+
387
+ The burden of proof is on the claim. If after inspecting the cited evidence you remain
388
+ uncertain, your verdict is REFUTED with basis = burden-not-met.
389
+
390
+ Inspect ONLY the evidence each finding cites and its immediate surroundings. Do NOT
391
+ re-read the task brief, instruction-set, or report template.
392
+
393
+ ## Findings to verify
394
+
395
+ ### F-001: <one-line summary>
396
+ **Origin**: <worker role>
397
+ **Cited evidence**: <file paths, line numbers, log lines from origin worker>
398
+
399
+ ### F-002: <one-line summary>
400
+ ...
401
+
402
+ ## Response format
403
+
404
+ ### F-001
405
+ **Verdict**: REFUTED | SURVIVES | SURVIVES-WITH-CAVEAT
406
+ **Basis** (only if REFUTED): counter-evidence | burden-not-met
407
+ **Explanation**: <2-3 sentences; for counter-evidence include the file:line you found>
408
+
409
+ ### F-002
410
+ ...
411
+ ```
412
+
413
+ When persisting votes, map SURVIVES→`agree`, SURVIVES-WITH-CAVEAT→`supplement`, REFUTED→`disagree`, and copy the stated Basis into `votes.<worker>.disagreeBasis` (null for non-REFUTED verdicts).
414
+ ```
415
+
416
+ - [ ] **Step 4: 스키마(State Artifact)에 `config.adversarial` + `disagreeBasis` + v1.2 반영**
417
+
418
+ Edit (a) — §"Convergence State Artifact" 예시 JSON 의 `config` 블록에 `adversarial` 추가. `"enabled": true,`(330행 부근) 다음 줄에:
419
+
420
+ ```json
421
+ "adversarial": false,
422
+ ```
423
+
424
+ Edit (b) — 같은 예시의 `votes` 항목에 `disagreeBasis` 를 한 곳 보여준다. `"codex-worker": { "verdict": "agree", "explanation": "<brief>" },` 를 다음으로 교체:
425
+
426
+ ```json
427
+ "codex-worker": { "verdict": "agree", "disagreeBasis": null, "explanation": "<brief>" },
428
+ ```
429
+
430
+ Edit (c) — Schema rules 목록(386–401행)의 `schemaVersion` 규칙 줄을 교체:
431
+
432
+ ```markdown
433
+ - `schemaVersion`: literal string `"1.2"` for adversarial-capable runs (`"1.1"` for collaborative-only runs remains valid). Readers MUST accept `"1.0"` / `"1.1"` / `"1.2"` and treat any missing field as `null`.
434
+ ```
435
+
436
+ Edit (d) — 같은 목록의 `config.effectiveMaxRounds` 규칙 줄 **앞에** 새 규칙 줄 추가:
437
+
438
+ ```markdown
439
+ - `config.adversarial`: boolean. `true` when this run used adversarial verification (default for `requirements-discovery` / `error-analysis`). When `true`, `config.verificationMode` is `"full-reanalysis"` (scoped) and every `disagree` vote carries a non-null `disagreeBasis`.
440
+ ```
441
+
442
+ Edit (e) — `findings[].rounds[].votes.<worker>.verdict` 규칙 줄 다음에 새 규칙 줄 추가:
443
+
444
+ ```markdown
445
+ - `findings[].rounds[].votes.<worker>.disagreeBasis`: enum `counter-evidence | burden-not-met | null`. Non-null only when `verdict == "disagree"` AND `config.adversarial == true`; `null` (or absent, treated as null) otherwise. See §"Adversarial Verification Mode".
446
+ ```
447
+
448
+ - [ ] **Step 5: 빌드 + 워크플로 검증으로 문서 정합 확인**
449
+
450
+ Run: `npm run build && bash validators/validate-workflow.sh`
451
+ Expected: 빌드 성공(`runtime/` 동기화), validator PASS.
452
+
453
+ - [ ] **Step 6: 커밋**
454
+
455
+ ```bash
456
+ git add skills/okstra-convergence/SKILL.md runtime/
457
+ git commit -m "feat(skills/okstra-convergence): define adversarial Phase 5.5 verification mode"
458
+ ```
459
+
460
+ ---
461
+
462
+ ### Task 4: 프로필 + 공통 계약에 적대적 Phase 5.5 선언
463
+
464
+ **Files:**
465
+ - Modify: `prompts/profiles/requirements-discovery.md`
466
+ - Modify: `prompts/profiles/error-analysis.md`
467
+ - Modify: `prompts/profiles/_common-contract.md`
468
+
469
+ - [ ] **Step 1: requirements-discovery 프로필에 선언 추가**
470
+
471
+ Edit `prompts/profiles/requirements-discovery.md` — `- Non-goals:` 줄(54행) **앞에** 새 항목 삽입:
472
+
473
+ ```markdown
474
+ - Cross-verification mode:
475
+ - Phase 5.5 convergence runs in **adversarial mode** for this phase (`convergence.adversarial=true`). Verifiers actively try to refute each worker's finding by directly re-inspecting the cited evidence; the burden of proof sits on the claim. See `skills/okstra-convergence/SKILL.md` §"Adversarial Verification Mode". A single evidence-backed refutation prevents a finding from reaching consensus.
476
+ ```
477
+
478
+ - [ ] **Step 2: error-analysis 프로필에 선언 추가**
479
+
480
+ Edit `prompts/profiles/error-analysis.md` — `- Non-goals:` 줄(33행) **앞에** 동일 항목 삽입:
481
+
482
+ ```markdown
483
+ - Cross-verification mode:
484
+ - Phase 5.5 convergence runs in **adversarial mode** for this phase (`convergence.adversarial=true`). Verifiers actively try to refute each root-cause / reproduction claim by directly re-inspecting the cited code, logs, or config; the burden of proof sits on the claim. See `skills/okstra-convergence/SKILL.md` §"Adversarial Verification Mode". A single evidence-backed refutation prevents a finding from reaching consensus.
485
+ ```
486
+
487
+ - [ ] **Step 3: 공통 계약의 Phase 5.5 설명 갱신**
488
+
489
+ Edit `prompts/profiles/_common-contract.md` — "Worker interaction model" 의 Phase 5.5 항목(17행)에서, 문장 끝 `See `skills/okstra-convergence/SKILL.md` for the round protocol, queue invariants, and final classification (`full-consensus` / `partial-consensus` / `contested` / `worker-unique`).` 다음에 한 문장 추가(같은 bullet 내):
490
+
491
+ ```markdown
492
+ For `requirements-discovery` and `error-analysis` this phase runs in **adversarial mode** (`convergence.adversarial=true`): verifiers try to refute each finding against its cited evidence and the burden of proof sits on the claim — see that skill's §"Adversarial Verification Mode".
493
+ ```
494
+
495
+ - [ ] **Step 4: 빌드 + 검증**
496
+
497
+ Run: `npm run build && bash validators/validate-workflow.sh`
498
+ Expected: 빌드 성공, validator PASS.
499
+
500
+ - [ ] **Step 5: 커밋**
501
+
502
+ ```bash
503
+ git add prompts/profiles/requirements-discovery.md prompts/profiles/error-analysis.md prompts/profiles/_common-contract.md runtime/
504
+ git commit -m "feat(prompts/profiles): declare adversarial Phase 5.5 for discovery/error-analysis"
505
+ ```
506
+
507
+ ---
508
+
509
+ ### Task 5: CHANGES.md + 전체 검증 + 최종 커밋
510
+
511
+ **Files:**
512
+ - Modify: `CHANGES.md`
513
+
514
+ - [ ] **Step 1: CHANGES.md 항목 추가**
515
+
516
+ Edit `CHANGES.md` — `## 2026-06-04` 헤더(5행) 바로 다음에 새 `###` 블록 삽입(기존 첫 항목 위):
517
+
518
+ ```markdown
519
+ ### feat(convergence): requirements-discovery / error-analysis 의 Phase 5.5 를 적대적 검증으로
520
+
521
+ - 기존 Phase 5.5 재검증은 협조적이었다 — 프롬프트가 `AGREE/DISAGREE/SUPPLEMENT` 를 묻고(동의가 저비용 기본값), 집계는 "다수 AGREE → consensus" 라 입증 책임이 반박자 쪽에 있어, 틀린 주장이라도 아무도 적극 반박하지 않으면 `full-consensus` 로 살아남았다. 라우팅(`requirements-discovery`)·근본원인(`error-analysis`) 처럼 틀린 주장이 다음 phase 전체를 오도하는 두 phase 에서 거짓 합의 비용이 가장 크다. 이제 이 두 phase 의 Phase 5.5 가 **적대적 모드**(`convergence.adversarial=true`)로 돈다 — 검증자는 인용된 증거를 직접 재조사해 주장을 깨뜨리려 시도하고(REFUTED/SURVIVES/SURVIVES-WITH-CAVEAT), 입증 책임은 주장 쪽에 있다(불확실하면 기각). 증거 기반 반박 1건이면 그 주장은 consensus 에 오르지 못한다. 재조사 범위는 finding 이 인용한 증거 파일+인접부로 한정해 비용 폭증을 막았고, maxRounds 는 현행 유지(req-discovery=1, error-analysis=2). 상태 아티팩트는 `config.adversarial` 와 반박 근거(`disagreeBasis ∈ counter-evidence|burden-not-met`)를 기록(schema v1.2). 다른 phase 의 convergence 는 협조적 그대로다.
522
+ - 사용자 영향: 다음 release + `npx -y okstra@latest install` 후 적용. 이제 두 phase 의 교차검증이 워커 주장을 적극 반박해, 근거 약한 합의가 걸러진다. 적대적 *행동* 자체는 lead/워커 prompt 지시(LLM 실행)이며 런타임 강제가 아니다 — 강제되는 것은 상태 아티팩트 형태(contract 테스트)뿐이다. `contested` 는 기각이 아니라 "다툼 있음" 분류이므로 finding 은 리포트에 남고 강등 사유(반대 증거 vs 입증 실패)가 기록된다.
523
+ ```
524
+
525
+ - [ ] **Step 2: 전체 테스트 + 검증 + 빌드 정합**
526
+
527
+ Run:
528
+ ```bash
529
+ npm run build
530
+ python3 -m pytest tests/ -q
531
+ bash validators/validate-workflow.sh
532
+ node bin/okstra --version
533
+ ```
534
+ Expected: 빌드 성공, pytest 전부 PASS, validator PASS, 버전 출력.
535
+
536
+ - [ ] **Step 3: 리뷰어 시점 self-review (Rule 5)**
537
+
538
+ `git diff main...HEAD` 전체를 처음 보는 리뷰어 관점으로 통독하고, 신규 식별자(`adversarial`, `disagreeBasis`, `Adversarial Verification Mode`, `counter-evidence`, `burden-not-met`)를 `grep -rn` 으로 일관성 확인:
539
+
540
+ ```bash
541
+ grep -rn "disagreeBasis\|adversarial\|Adversarial Verification Mode\|counter-evidence\|burden-not-met" skills/ prompts/ scripts/ tests/ CHANGES.md
542
+ ```
543
+ Expected: render.py(주입), skill(정의), 프로필/계약(선언), 테스트/fixture(강제), CHANGES(기록) 모두에서 같은 의미로 등장. 정의되지 않은 곳에서 토큰이 떠다니지 않을 것.
544
+
545
+ - [ ] **Step 4: 최종 커밋**
546
+
547
+ ```bash
548
+ git add CHANGES.md
549
+ git commit -m "docs(changes): log adversarial Phase 5.5 verification for discovery/error-analysis"
550
+ ```
551
+
552
+ ---
553
+
554
+ ## Self-Review (작성자 체크리스트)
555
+
556
+ **1. Spec coverage**
557
+ - §2.1 phase-조건부 모드 → Task 1(render) + Task 3 Step 1(Configuration 표).
558
+ - §2.2 적대적 프롬프트 → Task 3 Step 3.
559
+ - §2.3 verdict 매핑 + disagreeBasis → Task 3 Step 2/4, Task 2.
560
+ - §2.4 적대적 집계 → Task 3 Step 2.
561
+ - §2.5 범위-한정 full-reanalysis → Task 3 Step 2(Scoped full-reanalysis), Step 3(프롬프트 지시).
562
+ - §3.1 상태 스키마 1.2 → Task 2 + Task 3 Step 4.
563
+ - §3.2 render 주입 → Task 1.
564
+ - §4 변경 파일 6종 → Task 1–5 전부 커버.
565
+ - §5 enforcement 정직성 → Task 2(형태 강제), CHANGES(행동은 prompt 지시 명시).
566
+ - §7 수용 기준 1–5 → Task 5 Step 2 의 전체 검증으로 확인.
567
+
568
+ **2. Placeholder scan:** 모든 코드/JSON/markdown 블록은 실제 내용. TBD/TODO 없음.
569
+
570
+ **3. Type/식별자 일관성:** `adversarial`(bool), `disagreeBasis`(enum `counter-evidence|burden-not-met|null`), `verificationMode`("full-reanalysis"), 분류값(`full-consensus|partial-consensus|contested|worker-unique`) — Task 간 동일 철자 사용 확인.