okstra 0.165.0 → 0.165.2
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/package.json
CHANGED
package/runtime/BUILD.json
CHANGED
|
@@ -112,9 +112,26 @@ def _data_to_report_path(data_path: Path) -> Path:
|
|
|
112
112
|
return final_report_markdown_path(data_path)
|
|
113
113
|
|
|
114
114
|
|
|
115
|
+
def _project_id_of(task_key: str) -> str:
|
|
116
|
+
"""`project-id:task-group:task-id` 의 첫 세그먼트, 아니면 "".
|
|
117
|
+
|
|
118
|
+
A follow-up belongs to its parent's project by construction, so the new
|
|
119
|
+
task-key inherits that segment rather than re-deriving it from disk.
|
|
120
|
+
Omitting it is not a cosmetic difference: `okstra_project.state.
|
|
121
|
+
parse_task_key` raises on a two-segment key, and every catalog reader goes
|
|
122
|
+
through it, so one `<group>/<id>` entry took down `list_project_tasks` for
|
|
123
|
+
the whole project — the wizard could not render its first screen.
|
|
124
|
+
"""
|
|
125
|
+
parts = (task_key or "").split(":")
|
|
126
|
+
if len(parts) != 3 or not all(parts):
|
|
127
|
+
return ""
|
|
128
|
+
return parts[0]
|
|
129
|
+
|
|
130
|
+
|
|
115
131
|
def _spawn_one(
|
|
116
132
|
*,
|
|
117
133
|
project_root: Path,
|
|
134
|
+
project_id: str,
|
|
118
135
|
task_group: str,
|
|
119
136
|
parent_task_key: str,
|
|
120
137
|
parent_report_relative: str,
|
|
@@ -151,7 +168,7 @@ def _spawn_one(
|
|
|
151
168
|
origin = row["origin"].strip()
|
|
152
169
|
priority = (row.get("priority") or "P1").strip()
|
|
153
170
|
ticket_id = (row.get("ticketId") or "").strip()
|
|
154
|
-
new_task_key = f"{task_group}
|
|
171
|
+
new_task_key = f"{project_id}:{task_group}:{new_task_id}"
|
|
155
172
|
now = dt.datetime.now(dt.timezone.utc).isoformat()
|
|
156
173
|
|
|
157
174
|
spawned_meta: dict = {
|
|
@@ -257,6 +274,16 @@ def main(argv: list[str]) -> int:
|
|
|
257
274
|
print(f"data.json not found: {args.data_file}", file=sys.stderr)
|
|
258
275
|
return 1
|
|
259
276
|
|
|
277
|
+
project_id = _project_id_of(args.parent_task_key)
|
|
278
|
+
if not project_id:
|
|
279
|
+
print(
|
|
280
|
+
f"--parent-task-key must be project-id:task-group:task-id, got "
|
|
281
|
+
f"{args.parent_task_key!r} — a spawned task-key derived from it "
|
|
282
|
+
"would break every catalog read in this project.",
|
|
283
|
+
file=sys.stderr,
|
|
284
|
+
)
|
|
285
|
+
return 1
|
|
286
|
+
|
|
260
287
|
try:
|
|
261
288
|
data = json.loads(args.data_file.read_text(encoding="utf-8"))
|
|
262
289
|
except json.JSONDecodeError as exc:
|
|
@@ -297,6 +324,7 @@ def main(argv: list[str]) -> int:
|
|
|
297
324
|
continue
|
|
298
325
|
status, info = _spawn_one(
|
|
299
326
|
project_root=args.project_root,
|
|
327
|
+
project_id=project_id,
|
|
300
328
|
task_group=args.task_group,
|
|
301
329
|
parent_task_key=args.parent_task_key,
|
|
302
330
|
parent_report_relative=parent_report_relative,
|
|
@@ -4658,10 +4658,16 @@ def _sim_advance(state: WizardState, prompt: Prompt) -> None:
|
|
|
4658
4658
|
STEP_BY_ID[prompt.step].submit(state, _sim_answer(prompt))
|
|
4659
4659
|
if prompt.step not in state.answered:
|
|
4660
4660
|
state.answered.append(prompt.step)
|
|
4661
|
-
except WizardError:
|
|
4662
|
-
#
|
|
4663
|
-
#
|
|
4664
|
-
#
|
|
4661
|
+
except (WizardError, PrepareError):
|
|
4662
|
+
# 기본답이 거부되는 두 경우만 전진 처리한다 — 입력 검증이 막는 드문 text
|
|
4663
|
+
# 분기(WizardError), 그리고 답 자체는 유효하나 대상의 도메인 상태가 그
|
|
4664
|
+
# 경로를 막는 경우(PrepareError). 후자가 빠져 있어 승인 게이트가
|
|
4665
|
+
# `blocked-by-disagreement` 인 계획이 최신 태스크이면 위저드가 첫 화면조차
|
|
4666
|
+
# 내지 못하고 죽었다: 시뮬레이터가 기본 경로를 따라가다 그 계획을 고르고,
|
|
4667
|
+
# 진행률 라벨 하나 때문에 run 전체가 시작 불가가 됐다. 시뮬레이션은 분모
|
|
4668
|
+
# 추정이므로 막힌 경로를 만나면 그 화면을 지났다고 치고 계속 세면 된다.
|
|
4669
|
+
# KeyError/AttributeError 등 실제 버그는 여전히 삼키지 않고 그대로
|
|
4670
|
+
# 전파시켜, progress 라벨이 그럴듯하게 틀리는 대신 시끄럽게 실패한다.
|
|
4665
4671
|
members = prompt.questions if prompt.kind == "pick_group" else [prompt]
|
|
4666
4672
|
for p in members:
|
|
4667
4673
|
if p.step not in state.answered:
|
|
@@ -3983,6 +3983,29 @@ def _validate_gate_blocked_by(data: dict, failures: list[str]) -> None:
|
|
|
3983
3983
|
)
|
|
3984
3984
|
return
|
|
3985
3985
|
|
|
3986
|
+
if not actual_causes and _PLAN_GATE_RANK.get(declared_gate) == 0:
|
|
3987
|
+
# A blocking value with nothing left blocking it. The two checks around
|
|
3988
|
+
# this one both walk from a recorded cause outward, so a gate that
|
|
3989
|
+
# simply stopped being updated fell between them: a self-fix loop
|
|
3990
|
+
# resolved every majority-disagree item, `gateBlockedBy` emptied
|
|
3991
|
+
# correctly, and the gate token stayed at its round-1 value. The plan
|
|
3992
|
+
# was approvable and nothing said so — run-prep refused the approval,
|
|
3993
|
+
# and the refusal propagated far enough to take the run wizard down
|
|
3994
|
+
# with it, so no run could be started in that project at all. Rescoring
|
|
3995
|
+
# with `okstra plan-verify` and recording what it returns is the fix;
|
|
3996
|
+
# the round is not complete until that call agrees with the report.
|
|
3997
|
+
failures.append(
|
|
3998
|
+
"final-report data.json: implementationPlanning.planBodyVerification "
|
|
3999
|
+
f"`gateResult` is `{declared_gate}` but nothing blocks approval — "
|
|
4000
|
+
"no plan item is `majority-disagree`, no dispatch was a non-result, "
|
|
4001
|
+
"and no Requirement Coverage row blocks independently. A gate that "
|
|
4002
|
+
"withholds approval with no recorded cause is almost always a value "
|
|
4003
|
+
"left behind by an earlier round: rescore with `okstra plan-verify` "
|
|
4004
|
+
"and record its `gate.recomputed` "
|
|
4005
|
+
'(plan-body-verification.md §"Round protocol" step 5).'
|
|
4006
|
+
)
|
|
4007
|
+
return
|
|
4008
|
+
|
|
3986
4009
|
if declared_causes != actual_causes:
|
|
3987
4010
|
failures.append(
|
|
3988
4011
|
"final-report data.json: implementationPlanning.planBodyVerification "
|