v0.2 walk-through UI: guided flow, data view, overview, modes, overrides, typed phrase; retry confirmation; visual pass
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: sonnet Assistant-Process: 295952@bnt-lap001 Assistant-Session: e93f64ad-516c-46eb-9666-aad8d300c477
This commit is contained in:
parent
f8ce4717a8
commit
f070999aa2
12 changed files with 1285 additions and 53 deletions
|
|
@ -343,3 +343,86 @@ def test_pushed_items_are_a_distinct_state_that_lets_the_run_continue_but_never_
|
|||
R1.finalize(r, "succeeded")
|
||||
R1.finalize(r, "failed", "pushed through to collect problems") # a clean-completion failure, finishable as failed
|
||||
assert r.data["outcome"] == "failed"
|
||||
|
||||
|
||||
# ---- retry policy, mode switch, views ------------------------------------------------------------------------------
|
||||
|
||||
def test_retry_of_a_non_idempotent_step_needs_the_partial_application_confirmation(tmp_path):
|
||||
r = new(tmp_path)
|
||||
for s in ("read-the-sign", "light-lamp", "read-the-sky"):
|
||||
do_step(r, s)
|
||||
pass_gate(r, "lamp-check")
|
||||
do_step(r, "warn")
|
||||
R.start(r, "write-logbook"); R.fail(r, "write-logbook", "disk full halfway") # non-idempotent: appends
|
||||
with pytest.raises(R.RunError, match="did not partly apply"):
|
||||
R.retry(r, "write-logbook")
|
||||
R.retry(r, "write-logbook", acknowledge_partial=True)
|
||||
assert r.data["steps"]["write-logbook"]["retries"][0]["acknowledged_partial"] is True
|
||||
R.start(r, "read-the-sign") if False else None
|
||||
r2 = new(tmp_path)
|
||||
R.start(r2, "read-the-sign"); R.fail(r2, "read-the-sign", "x") # idempotent: free retry
|
||||
R.retry(r2, "read-the-sign")
|
||||
assert "retries" not in r2.data["steps"]["read-the-sign"]
|
||||
|
||||
|
||||
def test_undeclared_idempotency_also_needs_the_confirmation(tmp_path):
|
||||
def m(d):
|
||||
step(d, "read-the-sign").pop("idempotent")
|
||||
r = new(tmp_path, m)
|
||||
R.start(r, "read-the-sign"); R.fail(r, "read-the-sign", "x")
|
||||
with pytest.raises(R.RunError, match="not declared idempotent"):
|
||||
R.retry(r, "read-the-sign")
|
||||
|
||||
|
||||
def step(d, sid):
|
||||
return next(s for st in d["stages"] for s in st.get("steps", []) if s["id"] == sid)
|
||||
|
||||
|
||||
def test_switch_mode_records_history_and_clears_a_stop(tmp_path):
|
||||
r = new(tmp_path, mode="auto-confirm")
|
||||
R._stop(r, "x", "why")
|
||||
assert r.data["auto_confirm"]["stopped"]
|
||||
R.switch_mode(r, "manual")
|
||||
assert r.data["mode"] == "manual" and r.data["mode_history"][0] == {**r.data["mode_history"][0], "from": "auto-confirm", "to": "manual"}
|
||||
with pytest.raises(R.RunError, match="already in manual"):
|
||||
R.switch_mode(r, "manual")
|
||||
R.switch_mode(r, "auto-confirm")
|
||||
assert "stopped" not in r.data["auto_confirm"] and len(r.data["mode_history"]) == 2
|
||||
|
||||
|
||||
def test_overview_counts_progress_pushed_and_datapoint_states(tmp_path):
|
||||
r = new(tmp_path)
|
||||
ov = R.overview(r)
|
||||
assert ov["progress"] == {"done": 0, "pushed": 0, "total": 8} and [s["id"] for s in ov["stages"]] == ["dusk", "night", "dawn"]
|
||||
assert ov["datapoints"]["set"] == 5 and ov["datapoints"]["nil"] == 1
|
||||
do_step(r, "light-lamp")
|
||||
ov = R.overview(r)
|
||||
assert ov["progress"]["done"] == 1 and ov["stages"][0]["steps_done"] == 1 and ov["datapoints"]["ok"] == 1
|
||||
assert ov["stages"][1]["unmet"] and not ov["stages"][0]["complete"]
|
||||
|
||||
|
||||
def test_datapoint_rows_group_and_filter_by_dimension(tmp_path):
|
||||
r = new(tmp_path)
|
||||
assert "actor" in R.dimension_keys(r) and "kind" in R.dimension_keys(r)
|
||||
rows = R.datapoint_rows(r)
|
||||
assert list(rows) == [""] and len(rows[""]) == 6
|
||||
by_actor = R.datapoint_rows(r, group="actor")
|
||||
assert set(by_actor) == {"keeper", "(none)"} and {x["id"] for x in by_actor["keeper"]} >= {"keeper_name", "lamp_fuel", "motto", "lamp_lit"}
|
||||
only = R.datapoint_rows(r, flt="kind:identity")
|
||||
assert {x["id"] for x in only[""]} == {"keeper_name", "motto"}
|
||||
lamp = next(x for x in rows[""] if x["id"] == "lamp_lit")
|
||||
assert lamp["editable"] is False and lamp["produced_by"] == "light-lamp"
|
||||
|
||||
|
||||
def test_artefact_rows_resolve_location_from_a_datapoint(tmp_path):
|
||||
r = new(tmp_path, values={"logdir": "/tmp/rtut-art-one"})
|
||||
a = R.artefact_rows(r)[0]
|
||||
assert a["id"] == "logbook-file" and a["location"] == "/tmp/rtut-art-one/logbook.txt" and a["produced_state"] == "todo"
|
||||
|
||||
|
||||
def test_captures_and_pasted_verification_work_for_v02_runs(tmp_path):
|
||||
r = new(tmp_path)
|
||||
R.start(r, "read-the-sky")
|
||||
R1.add_capture(r, "read-the-sky", "Weather tonight - fog.", "echo ...")
|
||||
res = R1.verify_pasted(r, "read-the-sky", "Weather tonight - fog.")
|
||||
assert res["passed"] and r.data["steps"]["read-the-sky"]["captures"][0]["output"].startswith("Weather")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue