okstra 0.102.1 → 0.102.3
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.
- package/README.kr.md +5 -6
- package/README.md +5 -6
- package/docs/kr/architecture/storage-model.md +1 -1
- package/docs/kr/architecture.md +8 -12
- package/docs/kr/cli.md +6 -6
- package/docs/kr/container.md +1 -1
- package/docs/kr/performance-improvement-plan-v2.md +2 -2
- package/docs/project-structure-overview.md +2 -2
- package/package.json +7 -2
- package/runtime/BUILD.json +2 -2
- package/runtime/bin/okstra-antigravity-exec.sh +1 -2
- package/runtime/bin/okstra-codex-exec.sh +1 -2
- package/runtime/bin/okstra-render-final-report.py +2 -9
- package/runtime/bin/okstra-spawn-followups.py +4 -6
- package/runtime/bin/okstra-trace-cleanup.sh +24 -4
- package/runtime/prompts/lead/okstra-lead-contract.md +2 -2
- package/runtime/prompts/lead/report-writer.md +1 -1
- package/runtime/prompts/profiles/_common-contract.md +9 -11
- package/runtime/python/okstra_ctl/codex_dispatch.py +4 -12
- package/runtime/python/okstra_ctl/consumers.py +35 -0
- package/runtime/python/okstra_ctl/container.py +4 -6
- package/runtime/python/okstra_ctl/dispatch_core.py +4 -12
- package/runtime/python/okstra_ctl/final_report_paths.py +24 -0
- package/runtime/python/okstra_ctl/handoff.py +2 -1
- package/runtime/python/okstra_ctl/implementation_stage.py +7 -11
- package/runtime/python/okstra_ctl/report_view_artifacts.py +14 -0
- package/runtime/python/okstra_ctl/report_views.py +7 -4
- package/runtime/python/okstra_ctl/run.py +3 -10
- package/runtime/python/okstra_ctl/stage_targets.py +18 -0
- package/runtime/python/okstra_ctl/wizard.py +24 -25
- package/runtime/python/okstra_ctl/wizard_stage_intent.py +54 -0
- package/runtime/python/okstra_token_usage/report.py +2 -5
- package/runtime/validators/validate-report-views.py +2 -1
- package/runtime/validators/validate-run.py +1 -11
- package/src/cli-registry.mjs +1 -1
- package/src/commands/lifecycle/install.mjs +10 -20
- package/src/commands/lifecycle/uninstall.mjs +7 -10
- package/docs/superpowers/plans/2026-05-12-ticket-id-in-reports.md +0 -638
- package/docs/superpowers/plans/2026-05-14-convergence-queue-pruning.md +0 -1568
- package/docs/superpowers/plans/2026-05-17-dual-format-final-report.md +0 -167
- package/docs/superpowers/plans/2026-05-20-final-report-language.md +0 -1501
- package/docs/superpowers/plans/2026-05-20-implementation-planning-multi-stage.md +0 -1267
- package/docs/superpowers/plans/2026-05-20-okstra-run-prompt-sot-b1.md +0 -1007
- package/docs/superpowers/plans/2026-05-20-wizard-messages-json-sot.md +0 -720
- package/docs/superpowers/plans/2026-05-20-wizard-prompt-json-sot-a1.md +0 -681
- package/docs/superpowers/plans/2026-05-21-improvement-discovery-task-type.md +0 -1691
- package/docs/superpowers/plans/2026-05-24-implementation-lead-context-slimming.md +0 -1700
- package/docs/superpowers/plans/2026-05-25-okstra-project-root-rename.md +0 -158
- package/docs/superpowers/plans/2026-05-26-wizard-3-option-picker.md +0 -860
- package/docs/superpowers/plans/2026-06-02-final-verification-protocol-hardening.md +0 -326
- package/docs/superpowers/plans/2026-06-02-okstra-run-branch-confirm-step.md +0 -337
- package/docs/superpowers/plans/2026-06-02-okstra-run-phase-pane-cleanup.md +0 -410
- package/docs/superpowers/plans/2026-06-02-requirements-discovery-fanout.md +0 -728
- package/docs/superpowers/plans/2026-06-04-adversarial-implementation-planning.md +0 -294
- package/docs/superpowers/plans/2026-06-04-adversarial-verification.md +0 -570
- package/docs/superpowers/plans/2026-06-04-coverage-critic.md +0 -516
- package/docs/superpowers/plans/2026-06-04-stage-cohesion-planner.md +0 -351
- package/docs/superpowers/plans/2026-06-04-stage-run-batching.md +0 -457
- package/docs/superpowers/plans/2026-06-05-acceptance-critic.md +0 -251
- package/docs/superpowers/plans/2026-06-05-compact-markdown-report-tables.md +0 -323
- package/docs/superpowers/plans/2026-06-05-wizard-batch-prompts.md +0 -559
- package/docs/superpowers/plans/2026-06-06-final-verification-whole-task-gate.md +0 -993
- package/docs/superpowers/plans/2026-06-06-stage-parallel-and-pending-fixes.md +0 -93
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p1.md +0 -447
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p2.md +0 -289
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p3.md +0 -774
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p4.md +0 -303
- package/docs/superpowers/plans/2026-06-06-stage-worktree-isolation-p5-multidep-base.md +0 -387
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase2.md +0 -275
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase3.md +0 -282
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4a.md +0 -147
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4b.md +0 -262
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4c.md +0 -184
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4d.md +0 -88
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase4e.md +0 -250
- package/docs/superpowers/plans/2026-06-07-stage-conformance-qa.md +0 -409
- package/docs/superpowers/plans/2026-06-09-implementation-run-artifact-stage-isolation.md +0 -320
- package/docs/superpowers/plans/2026-06-10-concurrent-run-team-guard.md +0 -456
- package/docs/superpowers/plans/2026-06-10-git-reconcile-stale-sha-recovery.md +0 -1408
- package/docs/superpowers/plans/2026-06-10-lead-worker-completion-polling-PROBE.md +0 -42
- package/docs/superpowers/plans/2026-06-10-lead-worker-completion-polling.md +0 -337
- package/docs/superpowers/plans/2026-06-10-p6-token-usage-incremental.md +0 -1029
- package/docs/superpowers/plans/2026-06-10-stage-group-handoff.md +0 -1572
- package/docs/superpowers/plans/2026-06-11-fix-cycle.md +0 -1290
- package/docs/superpowers/plans/2026-06-11-wizard-whole-task-final-verification.md +0 -526
- package/docs/superpowers/plans/2026-06-12-html-plan-approval.md +0 -1000
- package/docs/superpowers/plans/2026-06-13-repo-risk-hardening.md +0 -493
- package/docs/superpowers/plans/2026-06-14-host-runtime-auto-execution.md +0 -1775
- package/docs/superpowers/plans/2026-06-15-coding-preflight-pack-dispatch-path.md +0 -504
- package/docs/superpowers/plans/2026-06-15-internal-skill-migration-final-fixups.md +0 -342
- package/docs/superpowers/plans/2026-06-15-internal-skill-migration-fixups.md +0 -258
- package/docs/superpowers/plans/2026-06-15-internal-skill-migration-remaining-fixups.md +0 -387
- package/docs/superpowers/plans/2026-06-15-internal-skill-resource-migration.md +0 -749
- package/docs/superpowers/plans/2026-06-15-worker-prompt-anchor-final-fixups.md +0 -828
- package/docs/superpowers/plans/2026-06-15-worker-prompt-header-error-contract.md +0 -490
- package/docs/superpowers/plans/2026-06-17-okstra-error-report.md +0 -724
- package/docs/superpowers/plans/2026-06-18-subagent-pane-reclaim.md +0 -483
- package/docs/superpowers/plans/2026-06-19-agent-operating-standard.md +0 -476
- package/docs/superpowers/plans/2026-06-19-okstra-inspect-recap-facet.md +0 -647
- package/docs/superpowers/plans/2026-06-20-error-feedback-loop.md +0 -1019
- package/docs/superpowers/plans/2026-06-20-run-index-row-normalization.md +0 -583
- package/docs/superpowers/plans/2026-06-20-stage-auto-integrate-teardown.md +0 -819
- package/docs/superpowers/plans/2026-06-21-okstra-container-local-user-test.md +0 -714
- package/docs/superpowers/plans/2026-06-21-release-handoff-local-checkout.md +0 -348
- package/docs/superpowers/plans/2026-06-23-implementation-stage-multiselect-chain.md +0 -630
- package/docs/superpowers/plans/2026-06-23-implementation-step-commit-granularity.md +0 -274
- package/docs/superpowers/plans/2026-06-23-phase-batch-cleanup-enforcement.md +0 -409
- package/docs/superpowers/plans/2026-06-24-cross-project-precondition.md +0 -542
- package/docs/superpowers/plans/2026-06-24-decision-drafts-render-gap.md +0 -568
- package/docs/superpowers/plans/2026-06-24-manual-user-test-section.md +0 -203
- package/docs/superpowers/plans/2026-06-24-okstra-container-build-rename.md +0 -525
- package/docs/superpowers/specs/2026-05-12-ticket-id-in-reports-design.md +0 -131
- package/docs/superpowers/specs/2026-05-15-implementation-plan-verification-design.md +0 -254
- package/docs/superpowers/specs/2026-05-20-final-report-language-design.md +0 -383
- package/docs/superpowers/specs/2026-05-20-implementation-planning-multi-stage-design.md +0 -322
- package/docs/superpowers/specs/2026-05-20-okstra-run-prompt-sot-design.md +0 -299
- package/docs/superpowers/specs/2026-05-21-improvement-discovery-task-type-design.md +0 -335
- package/docs/superpowers/specs/2026-06-02-okstra-run-branch-confirm-step-design.md +0 -113
- package/docs/superpowers/specs/2026-06-02-okstra-run-phase-pane-cleanup-design.md +0 -173
- package/docs/superpowers/specs/2026-06-02-requirements-discovery-fanout-design.md +0 -154
- package/docs/superpowers/specs/2026-06-04-adversarial-implementation-planning-design.md +0 -90
- package/docs/superpowers/specs/2026-06-04-adversarial-verification-design.md +0 -176
- package/docs/superpowers/specs/2026-06-04-coverage-critic-design.md +0 -99
- package/docs/superpowers/specs/2026-06-04-stage-splitting-cost-aware-design.md +0 -98
- package/docs/superpowers/specs/2026-06-05-acceptance-critic-design.md +0 -90
- package/docs/superpowers/specs/2026-06-05-compact-markdown-report-tables-design.md +0 -87
- package/docs/superpowers/specs/2026-06-05-wizard-batch-prompts-design.md +0 -121
- package/docs/superpowers/specs/2026-06-06-final-verification-whole-task-gate-design.md +0 -126
- package/docs/superpowers/specs/2026-06-06-stage-worktree-isolation-design.md +0 -180
- package/docs/superpowers/specs/2026-06-06-vertical-slice-tdd-planning-design.md +0 -179
- package/docs/superpowers/specs/2026-06-07-stage-conformance-qa-design.md +0 -169
- package/docs/superpowers/specs/2026-06-09-executor-model-custom-id-cascade-design.md +0 -66
- package/docs/superpowers/specs/2026-06-09-implementation-run-artifact-stage-isolation-design.md +0 -87
- package/docs/superpowers/specs/2026-06-10-blocking-contract-posthoc-conformance-design.md +0 -168
- package/docs/superpowers/specs/2026-06-10-concurrent-run-team-guard-design.md +0 -107
- package/docs/superpowers/specs/2026-06-10-git-reconcile-stale-sha-recovery-design.md +0 -105
- package/docs/superpowers/specs/2026-06-10-lead-worker-completion-polling-design.md +0 -113
- package/docs/superpowers/specs/2026-06-10-stage-group-handoff-design.md +0 -156
- package/docs/superpowers/specs/2026-06-11-brief-entry-only-handoff-stage-entry-design.md +0 -158
- package/docs/superpowers/specs/2026-06-11-fix-cycle-design.md +0 -94
- package/docs/superpowers/specs/2026-06-11-wizard-whole-task-final-verification-design.md +0 -89
- package/docs/superpowers/specs/2026-06-12-codex-lead-adapter-design.md +0 -358
- package/docs/superpowers/specs/2026-06-12-html-plan-approval-design.md +0 -85
- package/docs/superpowers/specs/2026-06-12-stage-discipline-rule-design.md +0 -108
- package/docs/superpowers/specs/2026-06-13-forbidden-actions-ssot-design.md +0 -134
- package/docs/superpowers/specs/2026-06-13-neutral-tmux-lead-adapter-design.md +0 -284
- package/docs/superpowers/specs/2026-06-14-host-runtime-auto-execution-design.md +0 -404
- package/docs/superpowers/specs/2026-06-17-okstra-error-report-design.md +0 -123
- package/docs/superpowers/specs/2026-06-18-subagent-pane-reclaim-design.md +0 -150
- package/docs/superpowers/specs/2026-06-19-agent-operating-standard-design.md +0 -120
- package/docs/superpowers/specs/2026-06-19-okstra-inspect-recap-facet-design.md +0 -96
- package/docs/superpowers/specs/2026-06-20-error-feedback-loop-design.md +0 -171
- package/docs/superpowers/specs/2026-06-20-run-index-row-normalization-design.md +0 -112
- package/docs/superpowers/specs/2026-06-20-stage-auto-integrate-teardown-design.md +0 -145
- package/docs/superpowers/specs/2026-06-21-okstra-container-local-user-test-design.md +0 -125
- package/docs/superpowers/specs/2026-06-21-release-handoff-local-checkout-design.md +0 -113
- package/docs/superpowers/specs/2026-06-23-implementation-stage-multiselect-chain-design.md +0 -138
- package/docs/superpowers/specs/2026-06-23-implementation-step-commit-granularity-design.md +0 -120
- package/docs/superpowers/specs/2026-06-24-cross-project-precondition-design.md +0 -129
- package/docs/superpowers/specs/2026-06-24-decision-drafts-render-gap-design.md +0 -185
- package/docs/superpowers/specs/2026-06-24-manual-user-test-section-design.md +0 -121
- package/docs/superpowers/specs/2026-06-24-okstra-container-build-rename-design.md +0 -110
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
# Stage Conformance QA — Phase 4e (env + 우회 UX = 완성) Implementation Plan
|
|
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:** conformance QA 기능을 완성한다 — (a) `--qa-waiver <stageKey>:"<reason>"` 로 사용자 확인형 우회를 매니페스트 entry 의 `waiver` 에 기록, (b) `project.json.qaEnv.surfacePatterns` 를 diff-surface 교차검증에 반영, (c) `qaEnv`(replicaDbDsn/appBaseUrl/envFile/surfacePatterns) 를 setup 문서/wizard 에 명문화.
|
|
6
|
-
|
|
7
|
-
**Architecture:** 순수 헬퍼(`conformance.py`) + run.py prepare-time I/O + CLI 패스스루(bash) + validate-run surfacePatterns 배선 + 문서. SSOT: [`docs/superpowers/specs/2026-06-07-stage-conformance-qa-design.md`](../specs/2026-06-07-stage-conformance-qa-design.md) §7.2, §8, §12.5, §12.7.
|
|
8
|
-
|
|
9
|
-
**Tech Stack:** Python 3 (pytest), Bash, markdown.
|
|
10
|
-
|
|
11
|
-
**전제:** Phase 1~4d 완료. `detect_surfaces(file_paths, patterns)` 이미 override param 보유. `task_dir(project_root, task_group, task_id)` 존재([paths.py:39](../../../scripts/okstra_ctl/paths.py)).
|
|
12
|
-
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
## Task 1: waiver 헬퍼(순수)
|
|
16
|
-
|
|
17
|
-
**Files:** Modify `scripts/okstra_ctl/conformance.py`; Test `tests/test_okstra_ctl_conformance.py`
|
|
18
|
-
|
|
19
|
-
- [ ] **Step 1: 실패 테스트 추가**
|
|
20
|
-
|
|
21
|
-
```python
|
|
22
|
-
from okstra_ctl.conformance import apply_qa_waiver, parse_qa_waiver_arg # noqa: E402
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def test_parse_qa_waiver_arg_splits_on_first_colon():
|
|
26
|
-
assert parse_qa_waiver_arg('t-stage-1:replica down: skip') == ("t-stage-1", "replica down: skip")
|
|
27
|
-
assert parse_qa_waiver_arg("nope") is None
|
|
28
|
-
assert parse_qa_waiver_arg("") is None
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def test_apply_qa_waiver_sets_entry_waiver():
|
|
32
|
-
manifest = {"entries": [{"stageKey": "t-stage-1", "waiver": None}]}
|
|
33
|
-
assert apply_qa_waiver(manifest, "t-stage-1", "replica down", at="2026-06-07T00:00:00Z") is True
|
|
34
|
-
w = manifest["entries"][0]["waiver"]
|
|
35
|
-
assert w["acknowledgedBy"] == "user" and w["reason"] == "replica down"
|
|
36
|
-
assert w["scope"] == [] and w["at"] == "2026-06-07T00:00:00Z"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def test_apply_qa_waiver_unknown_stage_returns_false():
|
|
40
|
-
manifest = {"entries": [{"stageKey": "t-stage-1"}]}
|
|
41
|
-
assert apply_qa_waiver(manifest, "t-stage-9", "x", at="t") is False
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
- [ ] **Step 2: 실패 확인** — `python3 -m pytest tests/test_okstra_ctl_conformance.py -q` → FAIL(import 없음)
|
|
45
|
-
|
|
46
|
-
- [ ] **Step 3: 구현 추가** (`conformance.py` 끝)
|
|
47
|
-
|
|
48
|
-
```python
|
|
49
|
-
def parse_qa_waiver_arg(arg: object) -> tuple[str, str] | None:
|
|
50
|
-
"""`--qa-waiver` 값 `<stageKey>:<reason>` 를 (stageKey, reason) 로 분해.
|
|
51
|
-
형식이 아니거나 비면 None."""
|
|
52
|
-
if not isinstance(arg, str) or ":" not in arg:
|
|
53
|
-
return None
|
|
54
|
-
key, reason = arg.split(":", 1)
|
|
55
|
-
key, reason = key.strip(), reason.strip()
|
|
56
|
-
if not key or not reason:
|
|
57
|
-
return None
|
|
58
|
-
return key, reason
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def apply_qa_waiver(manifest: object, stage_key: str, reason: str, *, at: str,
|
|
62
|
-
acknowledged_by: str = "user") -> bool:
|
|
63
|
-
"""매니페스트에서 stage_key entry 의 `waiver` 를 채운다(in place). 찾으면 True.
|
|
64
|
-
사용자 확인형 우회(spec §7.2) — reason 은 사용자 지시 원문."""
|
|
65
|
-
entries = manifest.get("entries") if isinstance(manifest, dict) else None
|
|
66
|
-
if not isinstance(entries, list):
|
|
67
|
-
return False
|
|
68
|
-
for entry in entries:
|
|
69
|
-
if isinstance(entry, dict) and entry.get("stageKey") == stage_key:
|
|
70
|
-
entry["waiver"] = {"acknowledgedBy": acknowledged_by, "reason": reason,
|
|
71
|
-
"scope": [], "at": at}
|
|
72
|
-
return True
|
|
73
|
-
return False
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
- [ ] **Step 4: 통과** — `python3 -m pytest tests/test_okstra_ctl_conformance.py -q` → PASS
|
|
77
|
-
- [ ] **Step 5: 커밋** — `git commit -m "feat(okstra_ctl/conformance): qa waiver 파싱/적용 헬퍼"`
|
|
78
|
-
|
|
79
|
-
## Task 2: run.py — `--qa-waiver` prepare-time 적용
|
|
80
|
-
|
|
81
|
-
**Files:** Modify `scripts/okstra_ctl/run.py`; Test `tests/test_okstra_run_qa_waiver.py` (신규)
|
|
82
|
-
|
|
83
|
-
- [ ] **Step 1: argparse + PrepareInputs 필드 추가**
|
|
84
|
-
|
|
85
|
-
`scripts/okstra_ctl/run.py` 의 argparse 에 `--approved-plan` 근처(line 1862 부근)에:
|
|
86
|
-
|
|
87
|
-
```python
|
|
88
|
-
p.add_argument("--qa-waiver", default="", dest="qa_waiver",
|
|
89
|
-
help='Stage conformance 우회: "<stageKey>:<reason>" (사용자 확인형, 매니페스트 entry.waiver 기록)')
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
`PrepareInputs`(line 258, `approved_plan_path` 근처)에 `qa_waiver: str = ""` 추가. argv→PrepareInputs 매핑 지점에도 `qa_waiver=args.qa_waiver` 전달.
|
|
93
|
-
|
|
94
|
-
- [ ] **Step 2: implementation prepare gate 에 waiver 적용**
|
|
95
|
-
|
|
96
|
-
`run.py` 의 implementation 진입 게이트(qaCommands 검증 블록, `if inp.task_type == "implementation":` 부근 line 1112)에 이어, waiver 적용 헬퍼를 추가하고 호출:
|
|
97
|
-
|
|
98
|
-
```python
|
|
99
|
-
def _apply_qa_waiver_if_requested(inp: "PrepareInputs", project_root: Path) -> None:
|
|
100
|
-
"""`--qa-waiver` 가 있으면 task-level 매니페스트 entry 의 waiver 를 채운다."""
|
|
101
|
-
if not inp.qa_waiver:
|
|
102
|
-
return
|
|
103
|
-
from .conformance import apply_qa_waiver, parse_qa_waiver_arg
|
|
104
|
-
from .paths import task_dir
|
|
105
|
-
parsed = parse_qa_waiver_arg(inp.qa_waiver)
|
|
106
|
-
if parsed is None:
|
|
107
|
-
raise PrepareError(
|
|
108
|
-
f'--qa-waiver must be "<stageKey>:<reason>", got {inp.qa_waiver!r}'
|
|
109
|
-
)
|
|
110
|
-
stage_key, reason = parsed
|
|
111
|
-
manifest_path = task_dir(project_root, inp.task_group, inp.task_id) / "qa" / "conformance-manifest.json"
|
|
112
|
-
if not manifest_path.is_file():
|
|
113
|
-
raise PrepareError(f"--qa-waiver: conformance manifest not found at {manifest_path}")
|
|
114
|
-
manifest = json.loads(manifest_path.read_text())
|
|
115
|
-
when = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
116
|
-
if not apply_qa_waiver(manifest, stage_key, reason, at=when):
|
|
117
|
-
raise PrepareError(f"--qa-waiver: stageKey {stage_key!r} not in manifest {manifest_path}")
|
|
118
|
-
manifest_path.write_text(json.dumps(manifest, indent=2, ensure_ascii=False) + "\n")
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
게이트 블록에서 `if inp.task_type == "implementation":` 안, qaCommands 검증 다음에 `_apply_qa_waiver_if_requested(inp, project_root)` 호출. (`datetime`/`timezone` import 가 없으면 상단에 `from datetime import datetime, timezone` 추가.)
|
|
122
|
-
|
|
123
|
-
- [ ] **Step 3: 테스트(신규 파일)**
|
|
124
|
-
|
|
125
|
-
```python
|
|
126
|
-
# tests/test_okstra_run_qa_waiver.py
|
|
127
|
-
import json, sys
|
|
128
|
-
from pathlib import Path
|
|
129
|
-
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "scripts"))
|
|
130
|
-
from okstra_ctl.run import _apply_qa_waiver_if_requested, PrepareInputs, PrepareError
|
|
131
|
-
from okstra_ctl.paths import task_dir
|
|
132
|
-
import pytest
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
def _inp(**o):
|
|
136
|
-
base = dict(task_type="implementation", task_group="g", task_id="t", qa_waiver="")
|
|
137
|
-
base.update(o)
|
|
138
|
-
return PrepareInputs(**{k: v for k, v in base.items() if k in PrepareInputs.__dataclass_fields__})
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
def test_waiver_fills_manifest(tmp_path):
|
|
142
|
-
qa = task_dir(tmp_path, "g", "t") / "qa"; qa.mkdir(parents=True)
|
|
143
|
-
(qa / "conformance-manifest.json").write_text(json.dumps(
|
|
144
|
-
{"entries": [{"stageKey": "t-stage-1", "waiver": None}]}))
|
|
145
|
-
_apply_qa_waiver_if_requested(_inp(qa_waiver="t-stage-1:replica down"), tmp_path)
|
|
146
|
-
m = json.loads((qa / "conformance-manifest.json").read_text())
|
|
147
|
-
assert m["entries"][0]["waiver"]["reason"] == "replica down"
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
def test_waiver_unknown_stage_raises(tmp_path):
|
|
151
|
-
qa = task_dir(tmp_path, "g", "t") / "qa"; qa.mkdir(parents=True)
|
|
152
|
-
(qa / "conformance-manifest.json").write_text(json.dumps({"entries": []}))
|
|
153
|
-
with pytest.raises(PrepareError):
|
|
154
|
-
_apply_qa_waiver_if_requested(_inp(qa_waiver="t-stage-1:x"), tmp_path)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def test_no_waiver_noop(tmp_path):
|
|
158
|
-
_apply_qa_waiver_if_requested(_inp(qa_waiver=""), tmp_path) # no manifest, no error
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
(주의: `PrepareInputs` 필수 필드가 더 있으면 `_inp` 헬퍼를 실제 필드에 맞춰 보강 — 구현자가 dataclass 확인.)
|
|
162
|
-
|
|
163
|
-
- [ ] **Step 4: 통과 + 전체** — `python3 -m pytest tests/test_okstra_run_qa_waiver.py tests/ -q`
|
|
164
|
-
- [ ] **Step 5: 커밋** — `git commit -m "feat(okstra_ctl/run): --qa-waiver 로 매니페스트 entry waiver 기록"`
|
|
165
|
-
|
|
166
|
-
## Task 3: CLI 패스스루(bash)
|
|
167
|
-
|
|
168
|
-
**Files:** Modify `scripts/okstra.sh`, `scripts/lib/okstra/cli.sh`; Test `tests/test_okstra_sh_qa_waiver_passthrough.py`
|
|
169
|
-
|
|
170
|
-
- [ ] **Step 1: cli.sh 옵션 파싱** — `--stage)` 케이스(line 98) 근처에 추가:
|
|
171
|
-
|
|
172
|
-
```bash
|
|
173
|
-
--qa-waiver)
|
|
174
|
-
QA_WAIVER="$(require_option_value --qa-waiver "${2-}")"
|
|
175
|
-
shift 2
|
|
176
|
-
;;
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
help 문자열(line 192)의 옵션 목록에 `--qa-waiver` 추가.
|
|
180
|
-
|
|
181
|
-
- [ ] **Step 2: okstra.sh 전달** — line 125(`--stage`) 근처에:
|
|
182
|
-
|
|
183
|
-
```bash
|
|
184
|
-
[[ -n "${QA_WAIVER-}" ]] && PY_ARGS+=(--qa-waiver "$QA_WAIVER")
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
- [ ] **Step 3: 테스트** — `tests/test_okstra_sh_stage_passthrough.py` 를 모델로 `--qa-waiver foo:bar` 가 `python -m okstra_ctl.run` 인자까지 전달되는지 검증(스테이지 테스트의 인자-캡처 패턴 재사용).
|
|
188
|
-
|
|
189
|
-
- [ ] **Step 4: 통과 + 커밋** — `git commit -m "feat(cli): --qa-waiver 패스스루(okstra.sh/cli.sh)"`
|
|
190
|
-
|
|
191
|
-
## Task 4: surfacePatterns 배선(validate-run)
|
|
192
|
-
|
|
193
|
-
**Files:** Modify `validators/validate-run.py`; Test `tests/test_validate_run_conformance.py`
|
|
194
|
-
|
|
195
|
-
- [ ] **Step 1: `_validate_conformance` 에 surface_patterns 주입**
|
|
196
|
-
|
|
197
|
-
`_validate_conformance(report_path, failures)` 시그니처에 `surface_patterns: object = None` 추가. 내부 diff-surface 호출을 `detect_surfaces(changed_files, surface_patterns)` 로 바꾼다. 메인 dispatch 에서 호출 시 project.json 의 `qaEnv.surfacePatterns` 를 읽어 전달:
|
|
198
|
-
|
|
199
|
-
```python
|
|
200
|
-
if task_type in ("implementation", "final-verification"):
|
|
201
|
-
_sp = None
|
|
202
|
-
_pj = project_json_path(project_root)
|
|
203
|
-
if _pj.is_file():
|
|
204
|
-
try:
|
|
205
|
-
_sp = (json.loads(_pj.read_text()).get("qaEnv") or {}).get("surfacePatterns")
|
|
206
|
-
except (OSError, json.JSONDecodeError):
|
|
207
|
-
_sp = None
|
|
208
|
-
_validate_conformance(report_path, failures, surface_patterns=_sp)
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
`project_json_path` import 추가(`from okstra_project import project_json_path` 또는 기존 import 라인 확장 — 실제 export 확인).
|
|
212
|
-
|
|
213
|
-
- [ ] **Step 2: 테스트 추가** — `tests/test_validate_run_conformance.py` 에: 커스텀 패턴으로 `.xyz` 를 db 로 매핑해 미선언 시 BLOCKING 되는지(직접 `_validate_conformance(report, failures, surface_patterns={"db": ["*.xyz"]})` 호출).
|
|
214
|
-
|
|
215
|
-
- [ ] **Step 3: 통과 + 전체 + workflow** — `python3 -m pytest tests/ -q` + `bash validators/validate-workflow.sh`
|
|
216
|
-
- [ ] **Step 4: 커밋** — `git commit -m "feat(validators): qaEnv.surfacePatterns 를 diff-surface 교차검증에 반영"`
|
|
217
|
-
|
|
218
|
-
## Task 5: qaEnv 문서 + wizard picker(prescriptive)
|
|
219
|
-
|
|
220
|
-
**Files:** Modify `skills/okstra-setup/SKILL.md`, `skills/okstra-run/SKILL.md` (wizard); 그리고 surfacePatterns 기본 over-broad 주의 노트
|
|
221
|
-
|
|
222
|
-
- [ ] **Step 1: setup 문서** — `skills/okstra-setup/SKILL.md` 의 `qaCommands` 설명 근처에 `qaEnv` 블록 문서 추가:
|
|
223
|
-
|
|
224
|
-
```json
|
|
225
|
-
"qaEnv": {
|
|
226
|
-
"replicaDbDsn": "<replica/test DB DSN — never shared/staging/prod>",
|
|
227
|
-
"appBaseUrl": "http://localhost:3000",
|
|
228
|
-
"envFile": ".okstra/qa.env",
|
|
229
|
-
"surfacePatterns": { "db": ["*.sql", "*repository*"], "http": ["*controller*"] }
|
|
230
|
-
}
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
설명: verifier Tier3 conformance 스크립트가 replica DB·endpoint 에 닿는 통로(replica 전용). `surfacePatterns` 는 diff-surface 교차검증의 프로젝트별 override(기본 `*router*`/`*storage*` 패턴은 프론트엔드 파일을 넓게 잡으므로 프론트 중심 repo 는 override 권장 — Phase 4b 리뷰 노트).
|
|
234
|
-
|
|
235
|
-
- [ ] **Step 2: wizard picker** — `skills/okstra-run/SKILL.md` 의 implementation 입력 흐름에, conformance BLOCKING 이 예상되고 사용자가 우회를 원할 때 `--qa-waiver <stageKey>:"<reason>"` 를 안내하는 추천형 picker 한 단계 추가(추천 + "직접 입력", 마지막 옵션 "직접 입력"). lead/worker 자체 면제 금지 — 사용자 입력만.
|
|
236
|
-
|
|
237
|
-
- [ ] **Step 3: build + 검증** — `npm run build` + `bash validators/validate-workflow.sh` + `python3 -m pytest tests/ -q`
|
|
238
|
-
- [ ] **Step 4: 커밋** — `git commit -m "docs(skills): qaEnv(replica/surfacePatterns) 문서 + qa-waiver wizard 안내"`
|
|
239
|
-
|
|
240
|
-
---
|
|
241
|
-
|
|
242
|
-
## Phase 4e Self-Review 체크
|
|
243
|
-
|
|
244
|
-
- [ ] `--qa-waiver` 가 매니페스트 entry.waiver 를 채우고(원문 reason), 미지 stageKey/형식오류는 PrepareError?
|
|
245
|
-
- [ ] CLI 패스스루(okstra.sh→run)가 동작?
|
|
246
|
-
- [ ] qaEnv.surfacePatterns 가 detect_surfaces 에 반영?
|
|
247
|
-
- [ ] setup 문서/wizard 에 qaEnv·waiver 명문화?
|
|
248
|
-
- [ ] 전체 pytest + workflow validator + build 통과?
|
|
249
|
-
|
|
250
|
-
Phase 4e 완료 = conformance QA 기능 전체 완성. 완료 후 `CHANGES.md` 에 `사용자 영향:` 엔트리 추가 + 전체 회귀 최종 확인.
|
|
@@ -1,409 +0,0 @@
|
|
|
1
|
-
# Stage Conformance QA (Tier 3) Implementation Plan
|
|
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:** implementation/final-verification 의 각 stage 가 상위 요구사항에 부합하는지 실DB·엔드포인트·외부 API 수준에서 검증하는 Tier 3 conformance 게이트를 추가하고, 미검증을 silent pass 가 아닌 BLOCKING 으로 만들되 명시적 우회(선언형 면제 / 사용자 확인형 waiver)를 감사한다.
|
|
6
|
-
|
|
7
|
-
**Architecture:** 모든 산출물이 의존하는 결정론적 코어(매니페스트 검증 + `QA-RESULT` 파서)를 먼저 만들고(Phase 1), 그 위에 강제(Phase 2) → 생성/실행 wiring(Phase 3) → 우회 UX/env(Phase 4)를 얹는다. 설계 SSOT: [`docs/superpowers/specs/2026-06-07-stage-conformance-qa-design.md`](../specs/2026-06-07-stage-conformance-qa-design.md).
|
|
8
|
-
|
|
9
|
-
**Tech Stack:** Python 3 (stdlib + pytest), markdown prompt profiles, Node CLI (`bin/`, `scripts/lib/okstra/`), project.json.
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## 단계 분해 (Phase Roadmap)
|
|
14
|
-
|
|
15
|
-
이 기능은 분리 가능한 4개 슬라이스다. **본 문서는 Phase 1 을 bite-sized TDD 로 완전히 기술**하고, Phase 2~4 는 각자 독립 계획으로 확장한다(Phase 1 이 데이터 계약을 락인하므로 그 뒤가 안정적). 각 phase 는 그 자체로 테스트 가능하다.
|
|
16
|
-
|
|
17
|
-
| Phase | 슬라이스 | 핵심 산출물 | 의존 |
|
|
18
|
-
|---|---|---|---|
|
|
19
|
-
| 1 | 계약 & 검증 코어 | `scripts/okstra_ctl/conformance.py` + 단위테스트 | 없음 |
|
|
20
|
-
| 2 | 강제(load-bearing) | `validators/validate-run.py` Tier3 BLOCKING + `run.py` 진입 게이트 | 1 |
|
|
21
|
-
| 3 | 생성·실행 wiring | `implementation-planning.md`/`_implementation-verifier.md`/`final-verification.md` prompt | 1,2 |
|
|
22
|
-
| 4 | 우회 UX & env | `--qa-waiver` CLI + wizard + `project.json.qaEnv` | 1,2 |
|
|
23
|
-
|
|
24
|
-
Phase 2~4 요약(각자 별도 계획으로 작성):
|
|
25
|
-
|
|
26
|
-
- **Phase 2** — `validators/validate-run.py` 에 conformance 게이트 추가: implementation/final-verification diff 가 `requires`(db/io/http/external) 표면을 건드리는데 매니페스트 entry 가 없거나 결과가 FAIL/MISSING 이고 면제·waiver 도 없으면 BLOCKING. 선언형 면제가 실제 surface 와 불일치하면 BLOCKING. `run.py` 의 implementation 진입 게이트(현 [`run.py:1110`](../../../scripts/okstra_ctl/run.py:1110) qaCommands 패턴 모방)에서 `validate_conformance_manifest` 호출. surface 추정은 implementation report 의 Diff Summary(파일 경로/확장자/SQL 키워드) 기반 휴리스틱 + 명시 `requires` 우선.
|
|
27
|
-
- **Phase 3** — `prompts/profiles/implementation-planning.md` 에 stage 별 `### 5.5.x Stage Conformance Tests` 산출 + `.okstra/.../qa/` 스크립트·매니페스트 기록 지시(`Acceptance:` 의 실행형). `_implementation-verifier.md` 의 two-tier lookup 에 **Tier 3** 추가(스크립트 실행 → `parse_qa_result` 결과를 Read-only command log 에 기록). `final-verification.md` 는 전 stage 합집합 실행.
|
|
28
|
-
- **Phase 4** — `scripts/okstra.sh`/`scripts/lib/okstra/cli.sh` 에 `--qa-waiver <stageKey>:"<reason>"` 패스스루(→ `run.py` 가 매니페스트 entry 의 `waiver` 채움), `okstra-run` wizard 에 사용자 확인 picker, `project.json.qaEnv`(replicaDbDsn/appBaseUrl/envFile) 문서화(resolver 는 비-identity 필드를 이미 보존하므로 코드 변경 불필요 — [`resolver.py:120`](../../../scripts/okstra_project/resolver.py:120) 확인).
|
|
29
|
-
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
## Phase 1 — 계약 & 검증 코어
|
|
33
|
-
|
|
34
|
-
새 모듈 [`scripts/okstra_ctl/qa_commands.py`](../../../scripts/okstra_ctl/qa_commands.py) 와 동형의 sibling `conformance.py` 를 만든다. 런타임 검증/파싱 전용(순수 함수, 부수효과 없음).
|
|
35
|
-
|
|
36
|
-
### Task 1: 매니페스트 구조 검증 (`validate_conformance_manifest`)
|
|
37
|
-
|
|
38
|
-
**Files:**
|
|
39
|
-
- Create: `scripts/okstra_ctl/conformance.py`
|
|
40
|
-
- Test: `tests/test_okstra_ctl_conformance.py`
|
|
41
|
-
|
|
42
|
-
- [ ] **Step 1: 실패 테스트 작성**
|
|
43
|
-
|
|
44
|
-
```python
|
|
45
|
-
# tests/test_okstra_ctl_conformance.py
|
|
46
|
-
from __future__ import annotations
|
|
47
|
-
|
|
48
|
-
import sys
|
|
49
|
-
from pathlib import Path
|
|
50
|
-
|
|
51
|
-
_REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
52
|
-
sys.path.insert(0, str(_REPO_ROOT / "scripts"))
|
|
53
|
-
|
|
54
|
-
from okstra_ctl.conformance import ( # noqa: E402
|
|
55
|
-
CAPABILITY_WHITELIST,
|
|
56
|
-
validate_conformance_manifest,
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def _entry(**over):
|
|
61
|
-
base = {
|
|
62
|
-
"stageKey": "dev-9184-stage-3",
|
|
63
|
-
"script": "qa/stage-3.ts",
|
|
64
|
-
"runCommand": "npx ts-node .okstra/qa/stage-3.ts",
|
|
65
|
-
"requirementIds": ["R-001"],
|
|
66
|
-
"requires": ["db", "http"],
|
|
67
|
-
"passContract": "exit 0 = PASS",
|
|
68
|
-
"exemption": None,
|
|
69
|
-
"waiver": None,
|
|
70
|
-
}
|
|
71
|
-
base.update(over)
|
|
72
|
-
return base
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def test_valid_manifest_has_no_errors():
|
|
76
|
-
manifest = {"entries": [_entry()]}
|
|
77
|
-
assert validate_conformance_manifest(manifest) == []
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def test_absent_manifest_is_legal():
|
|
81
|
-
assert validate_conformance_manifest(None) == []
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def test_non_object_manifest_rejected():
|
|
85
|
-
assert validate_conformance_manifest([]) != []
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def test_entries_must_be_array():
|
|
89
|
-
assert any("entries" in e for e in validate_conformance_manifest({"entries": {}}))
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def test_missing_required_string_fields_reported():
|
|
93
|
-
errs = validate_conformance_manifest({"entries": [_entry(stageKey="")]})
|
|
94
|
-
assert any("stageKey" in e for e in errs)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
def test_requirement_ids_must_be_non_empty_list():
|
|
98
|
-
errs = validate_conformance_manifest({"entries": [_entry(requirementIds=[])]})
|
|
99
|
-
assert any("requirementIds" in e for e in errs)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def test_unknown_capability_rejected():
|
|
103
|
-
errs = validate_conformance_manifest({"entries": [_entry(requires=["db", "gpu"])]})
|
|
104
|
-
assert any("gpu" in e for e in errs)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def test_capability_whitelist_value():
|
|
108
|
-
assert CAPABILITY_WHITELIST == ("db", "io", "http", "external")
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
- [ ] **Step 2: 실패 확인**
|
|
112
|
-
|
|
113
|
-
Run: `python3 -m pytest tests/test_okstra_ctl_conformance.py -q`
|
|
114
|
-
Expected: FAIL — `ModuleNotFoundError: No module named 'okstra_ctl.conformance'`
|
|
115
|
-
|
|
116
|
-
- [ ] **Step 3: 최소 구현**
|
|
117
|
-
|
|
118
|
-
```python
|
|
119
|
-
# scripts/okstra_ctl/conformance.py
|
|
120
|
-
"""Stage conformance(Tier 3) 매니페스트 검증 + `QA-RESULT` 파서.
|
|
121
|
-
|
|
122
|
-
implementation/final-verification 의 verifier 는 stage 별 conformance 스크립트를
|
|
123
|
-
실행해 상위 요구사항 부합을 검증한다. 본 모듈은 그 검증/파싱의 결정론적 코어다.
|
|
124
|
-
|
|
125
|
-
1. `conformance-manifest.json` 구조 검증 (`validate_conformance_manifest`).
|
|
126
|
-
2. 스크립트 stdout 의 `QA-RESULT` 마커 파싱 (`parse_qa_result`).
|
|
127
|
-
|
|
128
|
-
스크립트 실행/게이트 강제는 verifier prompt 와 validators/validate-run.py 가 담당한다.
|
|
129
|
-
"""
|
|
130
|
-
from __future__ import annotations
|
|
131
|
-
|
|
132
|
-
import re
|
|
133
|
-
from dataclasses import dataclass
|
|
134
|
-
|
|
135
|
-
# diff 가 건드린 표면과 대조할 capability 태그 화이트리스트.
|
|
136
|
-
CAPABILITY_WHITELIST: tuple[str, ...] = ("db", "io", "http", "external")
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
class ConformanceError(ValueError):
|
|
140
|
-
"""conformance 매니페스트가 계약을 어긴 경우."""
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
def _check_nonempty_str(value: object, path: str, errors: list[str]) -> bool:
|
|
144
|
-
if not isinstance(value, str) or not value.strip():
|
|
145
|
-
errors.append(f"{path} must be a non-empty string")
|
|
146
|
-
return False
|
|
147
|
-
return True
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
def _check_capabilities(value: object, path: str, errors: list[str]) -> None:
|
|
151
|
-
if not isinstance(value, list):
|
|
152
|
-
errors.append(f"{path} must be an array")
|
|
153
|
-
return
|
|
154
|
-
for cap in value:
|
|
155
|
-
if cap not in CAPABILITY_WHITELIST:
|
|
156
|
-
errors.append(
|
|
157
|
-
f"{path}: unknown capability {cap!r} "
|
|
158
|
-
f"(allowed: {', '.join(CAPABILITY_WHITELIST)})"
|
|
159
|
-
)
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
def _check_entry(entry: object, idx: int, errors: list[str]) -> None:
|
|
163
|
-
path = f"entries[{idx}]"
|
|
164
|
-
if not isinstance(entry, dict):
|
|
165
|
-
errors.append(f"{path} must be an object")
|
|
166
|
-
return
|
|
167
|
-
_check_nonempty_str(entry.get("stageKey"), f"{path}.stageKey", errors)
|
|
168
|
-
_check_nonempty_str(entry.get("script"), f"{path}.script", errors)
|
|
169
|
-
_check_nonempty_str(entry.get("runCommand"), f"{path}.runCommand", errors)
|
|
170
|
-
_check_nonempty_str(entry.get("passContract"), f"{path}.passContract", errors)
|
|
171
|
-
req_ids = entry.get("requirementIds")
|
|
172
|
-
if (
|
|
173
|
-
not isinstance(req_ids, list)
|
|
174
|
-
or not req_ids
|
|
175
|
-
or not all(isinstance(r, str) and r.strip() for r in req_ids)
|
|
176
|
-
):
|
|
177
|
-
errors.append(f"{path}.requirementIds must be a non-empty array of strings")
|
|
178
|
-
_check_capabilities(entry.get("requires", []), f"{path}.requires", errors)
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
def validate_conformance_manifest(manifest: object) -> list[str]:
|
|
182
|
-
"""conformance-manifest 전체 검증. 위반 메시지 리스트 반환(비면 안전).
|
|
183
|
-
|
|
184
|
-
매니페스트 부재(None)는 합법 — 스크립트 없는 task 가 있을 수 있고, 게이트
|
|
185
|
-
강제(diff surface 대조)는 validators/validate-run.py 가 판정한다.
|
|
186
|
-
"""
|
|
187
|
-
if manifest is None:
|
|
188
|
-
return []
|
|
189
|
-
if not isinstance(manifest, dict):
|
|
190
|
-
return [f"conformance manifest must be an object, got {type(manifest).__name__}"]
|
|
191
|
-
entries = manifest.get("entries")
|
|
192
|
-
if not isinstance(entries, list):
|
|
193
|
-
return ["conformance manifest .entries must be an array"]
|
|
194
|
-
errors: list[str] = []
|
|
195
|
-
for idx, entry in enumerate(entries):
|
|
196
|
-
_check_entry(entry, idx, errors)
|
|
197
|
-
return errors
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
- [ ] **Step 4: 통과 확인**
|
|
201
|
-
|
|
202
|
-
Run: `python3 -m pytest tests/test_okstra_ctl_conformance.py -q`
|
|
203
|
-
Expected: PASS (8 passed)
|
|
204
|
-
|
|
205
|
-
- [ ] **Step 5: 커밋**
|
|
206
|
-
|
|
207
|
-
```bash
|
|
208
|
-
git add scripts/okstra_ctl/conformance.py tests/test_okstra_ctl_conformance.py
|
|
209
|
-
git commit -m "feat(okstra_ctl/conformance): conformance 매니페스트 구조 검증"
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
### Task 2: 면제/waiver 형태 검증 + stageKey 중복
|
|
213
|
-
|
|
214
|
-
**Files:**
|
|
215
|
-
- Modify: `scripts/okstra_ctl/conformance.py`
|
|
216
|
-
- Test: `tests/test_okstra_ctl_conformance.py`
|
|
217
|
-
|
|
218
|
-
- [ ] **Step 1: 실패 테스트 추가**
|
|
219
|
-
|
|
220
|
-
```python
|
|
221
|
-
def test_exemption_requires_reason_and_declared_at():
|
|
222
|
-
bad = _entry(exemption={"reason": ""})
|
|
223
|
-
errs = validate_conformance_manifest({"entries": [bad]})
|
|
224
|
-
assert any("exemption.reason" in e for e in errs)
|
|
225
|
-
assert any("exemption.declaredAt" in e for e in errs)
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
def test_valid_exemption_passes():
|
|
229
|
-
ok = _entry(exemption={"reason": "doc-only stage", "declaredAt": "2026-06-07"})
|
|
230
|
-
assert validate_conformance_manifest({"entries": [ok]}) == []
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
def test_waiver_requires_fields_and_scope_whitelist():
|
|
234
|
-
bad = _entry(waiver={"acknowledgedBy": "user", "reason": "env down",
|
|
235
|
-
"at": "2026-06-07", "scope": ["db", "gpu"]})
|
|
236
|
-
errs = validate_conformance_manifest({"entries": [bad]})
|
|
237
|
-
assert any("waiver.scope" in e and "gpu" in e for e in errs)
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
def test_valid_waiver_passes():
|
|
241
|
-
ok = _entry(waiver={"acknowledgedBy": "user", "reason": "replica down — skip db",
|
|
242
|
-
"at": "2026-06-07T10:00:00Z", "scope": ["db"]})
|
|
243
|
-
assert validate_conformance_manifest({"entries": [ok]}) == []
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
def test_duplicate_stage_key_reported():
|
|
247
|
-
errs = validate_conformance_manifest({"entries": [_entry(), _entry()]})
|
|
248
|
-
assert any("duplicate" in e for e in errs)
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
- [ ] **Step 2: 실패 확인**
|
|
252
|
-
|
|
253
|
-
Run: `python3 -m pytest tests/test_okstra_ctl_conformance.py -q`
|
|
254
|
-
Expected: FAIL — 면제/waiver 미검증, 중복 미검출
|
|
255
|
-
|
|
256
|
-
- [ ] **Step 3: 구현 추가**
|
|
257
|
-
|
|
258
|
-
`conformance.py` 의 `_check_entry` 끝(`_check_capabilities(... "requires" ...)` 다음 줄)에 추가:
|
|
259
|
-
|
|
260
|
-
```python
|
|
261
|
-
_check_exemption(entry.get("exemption"), f"{path}.exemption", errors)
|
|
262
|
-
_check_waiver(entry.get("waiver"), f"{path}.waiver", errors)
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
`_check_capabilities` 정의 다음에 두 헬퍼 추가:
|
|
266
|
-
|
|
267
|
-
```python
|
|
268
|
-
def _check_exemption(value: object, path: str, errors: list[str]) -> None:
|
|
269
|
-
if value is None:
|
|
270
|
-
return
|
|
271
|
-
if not isinstance(value, dict):
|
|
272
|
-
errors.append(f"{path} must be an object or null")
|
|
273
|
-
return
|
|
274
|
-
_check_nonempty_str(value.get("reason"), f"{path}.reason", errors)
|
|
275
|
-
_check_nonempty_str(value.get("declaredAt"), f"{path}.declaredAt", errors)
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
def _check_waiver(value: object, path: str, errors: list[str]) -> None:
|
|
279
|
-
if value is None:
|
|
280
|
-
return
|
|
281
|
-
if not isinstance(value, dict):
|
|
282
|
-
errors.append(f"{path} must be an object or null")
|
|
283
|
-
return
|
|
284
|
-
_check_nonempty_str(value.get("acknowledgedBy"), f"{path}.acknowledgedBy", errors)
|
|
285
|
-
_check_nonempty_str(value.get("reason"), f"{path}.reason", errors)
|
|
286
|
-
_check_nonempty_str(value.get("at"), f"{path}.at", errors)
|
|
287
|
-
_check_capabilities(value.get("scope", []), f"{path}.scope", errors)
|
|
288
|
-
```
|
|
289
|
-
|
|
290
|
-
`validate_conformance_manifest` 의 루프를 중복 검출로 교체:
|
|
291
|
-
|
|
292
|
-
```python
|
|
293
|
-
errors: list[str] = []
|
|
294
|
-
seen: set[str] = set()
|
|
295
|
-
for idx, entry in enumerate(entries):
|
|
296
|
-
_check_entry(entry, idx, errors)
|
|
297
|
-
key = entry.get("stageKey") if isinstance(entry, dict) else None
|
|
298
|
-
if isinstance(key, str) and key:
|
|
299
|
-
if key in seen:
|
|
300
|
-
errors.append(f"entries[{idx}].stageKey duplicate: {key!r}")
|
|
301
|
-
seen.add(key)
|
|
302
|
-
return errors
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
- [ ] **Step 4: 통과 확인**
|
|
306
|
-
|
|
307
|
-
Run: `python3 -m pytest tests/test_okstra_ctl_conformance.py -q`
|
|
308
|
-
Expected: PASS (13 passed)
|
|
309
|
-
|
|
310
|
-
- [ ] **Step 5: 커밋**
|
|
311
|
-
|
|
312
|
-
```bash
|
|
313
|
-
git add scripts/okstra_ctl/conformance.py tests/test_okstra_ctl_conformance.py
|
|
314
|
-
git commit -m "feat(okstra_ctl/conformance): 면제/waiver 형태 + stageKey 중복 검증"
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
### Task 3: `QA-RESULT` stdout 파서 (`parse_qa_result`)
|
|
318
|
-
|
|
319
|
-
**Files:**
|
|
320
|
-
- Modify: `scripts/okstra_ctl/conformance.py`
|
|
321
|
-
- Test: `tests/test_okstra_ctl_conformance.py`
|
|
322
|
-
|
|
323
|
-
- [ ] **Step 1: 실패 테스트 추가**
|
|
324
|
-
|
|
325
|
-
```python
|
|
326
|
-
from okstra_ctl.conformance import parse_qa_result # noqa: E402 (파일 상단 import 에 합쳐도 됨)
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
def test_parse_pass_with_requirements():
|
|
330
|
-
out = "running...\nREQ R-001: PASS: row count 3 > 0\nQA-RESULT: PASS\n"
|
|
331
|
-
res = parse_qa_result(out)
|
|
332
|
-
assert res.overall == "PASS"
|
|
333
|
-
assert res.requirements["R-001"]["status"] == "PASS"
|
|
334
|
-
assert "row count" in res.requirements["R-001"]["reason"]
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
def test_parse_fail():
|
|
338
|
-
res = parse_qa_result("REQ R-001: FAIL: parity mismatch\nQA-RESULT: FAIL\n")
|
|
339
|
-
assert res.overall == "FAIL"
|
|
340
|
-
assert res.requirements["R-001"]["status"] == "FAIL"
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
def test_missing_marker_is_missing():
|
|
344
|
-
assert parse_qa_result("no marker here").overall == "MISSING"
|
|
345
|
-
assert parse_qa_result("").overall == "MISSING"
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
def test_last_marker_wins():
|
|
349
|
-
assert parse_qa_result("QA-RESULT: PASS\nQA-RESULT: FAIL\n").overall == "FAIL"
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
- [ ] **Step 2: 실패 확인**
|
|
353
|
-
|
|
354
|
-
Run: `python3 -m pytest tests/test_okstra_ctl_conformance.py -q`
|
|
355
|
-
Expected: FAIL — `cannot import name 'parse_qa_result'`
|
|
356
|
-
|
|
357
|
-
- [ ] **Step 3: 구현 추가**
|
|
358
|
-
|
|
359
|
-
`conformance.py` 끝에 추가:
|
|
360
|
-
|
|
361
|
-
```python
|
|
362
|
-
_QA_RESULT_RE = re.compile(r"^QA-RESULT:\s*(PASS|FAIL)\s*$", re.MULTILINE)
|
|
363
|
-
_REQ_LINE_RE = re.compile(r"^REQ\s+(\S+):\s*(PASS|FAIL):\s*(.*)$", re.MULTILINE)
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
@dataclass
|
|
367
|
-
class QaResult:
|
|
368
|
-
overall: str # "PASS" | "FAIL" | "MISSING"
|
|
369
|
-
requirements: dict # id -> {"status": "PASS"|"FAIL", "reason": str}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
def parse_qa_result(stdout: str) -> QaResult:
|
|
373
|
-
"""스크립트 stdout 에서 `QA-RESULT` 마커 + `REQ` 줄 파싱.
|
|
374
|
-
|
|
375
|
-
마커가 없으면 overall='MISSING' — 스크립트가 계약을 안 지킨 것이므로 게이트는
|
|
376
|
-
FAIL 로 취급한다. 마커가 여럿이면 마지막 것을 채택한다.
|
|
377
|
-
"""
|
|
378
|
-
text = stdout or ""
|
|
379
|
-
markers = _QA_RESULT_RE.findall(text)
|
|
380
|
-
overall = markers[-1] if markers else "MISSING"
|
|
381
|
-
requirements: dict = {}
|
|
382
|
-
for rid, status, reason in _REQ_LINE_RE.findall(text):
|
|
383
|
-
requirements[rid] = {"status": status, "reason": reason.strip()}
|
|
384
|
-
return QaResult(overall=overall, requirements=requirements)
|
|
385
|
-
```
|
|
386
|
-
|
|
387
|
-
- [ ] **Step 4: 통과 확인**
|
|
388
|
-
|
|
389
|
-
Run: `python3 -m pytest tests/test_okstra_ctl_conformance.py -q`
|
|
390
|
-
Expected: PASS (17 passed)
|
|
391
|
-
|
|
392
|
-
- [ ] **Step 5: 전체 스위트 회귀 확인 + 커밋**
|
|
393
|
-
|
|
394
|
-
```bash
|
|
395
|
-
python3 -m pytest tests/ -q
|
|
396
|
-
git add scripts/okstra_ctl/conformance.py tests/test_okstra_ctl_conformance.py
|
|
397
|
-
git commit -m "feat(okstra_ctl/conformance): QA-RESULT stdout 파서"
|
|
398
|
-
```
|
|
399
|
-
|
|
400
|
-
---
|
|
401
|
-
|
|
402
|
-
## Phase 1 Self-Review 체크
|
|
403
|
-
|
|
404
|
-
- [ ] spec §3(데이터 모델) 의 모든 entry 필드(stageKey/script/runCommand/requirementIds/requires/passContract/exemption/waiver)가 `validate_conformance_manifest` 로 커버되는가?
|
|
405
|
-
- [ ] spec §4(스크립트 계약)의 `QA-RESULT`/`REQ` 마커가 `parse_qa_result` 로 커버되는가?
|
|
406
|
-
- [ ] capability 화이트리스트가 spec §3 의 `["db","io","http","external"]` 와 일치하는가?
|
|
407
|
-
- [ ] Phase 2 가 의존하는 공개 API(`validate_conformance_manifest`, `parse_qa_result`, `CAPABILITY_WHITELIST`, `QaResult`)가 안정적으로 노출되는가?
|
|
408
|
-
|
|
409
|
-
Phase 1 완료 후 Phase 2 계획(`docs/superpowers/plans/2026-06-07-stage-conformance-qa-phase2.md`)을 작성한다.
|