|
@@ -2599,6 +2599,14 @@ def bundle_dispatch(ctx):
|
|
|
# up (the fake saddle still carries gpu from the routing checks).
|
|
# up (the fake saddle still carries gpu from the routing checks).
|
|
|
check(queue.get("waiting_for_saddle") is not True,
|
|
check(queue.get("waiting_for_saddle") is not True,
|
|
|
"capable saddle available: no wait flag on the new task")
|
|
"capable saddle available: no wait flag on the new task")
|
|
|
|
|
+ # Drain barrier: the cap task must be CLAIMED before the flaky
|
|
|
|
|
+ # arc arms fail_next (under parallel-shard load the dispatch tick
|
|
|
|
|
+ # can lag task creation; an unclaimed predecessor would consume
|
|
|
|
|
+ # the one-shot failure flag instead of the flaky task).
|
|
|
|
|
+ check(wait_for(lambda: task_state(auth, cap_create).get("status")
|
|
|
|
|
+ in ("working", "done", "failed") or False,
|
|
|
|
|
+ timeout=60, what="cap task claimed") is not None,
|
|
|
|
|
+ "the capable task is claimed before the flaky arc starts")
|
|
|
|
|
|
|
|
print("== retry + resume ==")
|
|
print("== retry + resume ==")
|
|
|
# A daemon-side failure fails the task with its explanation.
|
|
# A daemon-side failure fails the task with its explanation.
|
|
@@ -2610,7 +2618,7 @@ def bundle_dispatch(ctx):
|
|
|
})
|
|
})
|
|
|
flaky = task_id_tmp
|
|
flaky = task_id_tmp
|
|
|
check(wait_for(lambda: task_state(auth, flaky).get("status") == "failed" or False,
|
|
check(wait_for(lambda: task_state(auth, flaky).get("status") == "failed" or False,
|
|
|
- timeout=60, what="flaky task fails") is not None,
|
|
|
|
|
|
|
+ timeout=90, what="flaky task fails") is not None,
|
|
|
"daemon failure fails the task")
|
|
"daemon failure fails the task")
|
|
|
detail = task_state(auth, flaky)
|
|
detail = task_state(auth, flaky)
|
|
|
check("bash -n rejected" in (detail.get("summary") or ""),
|
|
check("bash -n rejected" in (detail.get("summary") or ""),
|
|
@@ -2627,15 +2635,16 @@ def bundle_dispatch(ctx):
|
|
|
check(any("bash -n rejected" in c["body"] and c["author"] == "agent"
|
|
check(any("bash -n rejected" in c["body"] and c["author"] == "agent"
|
|
|
for c in detail["comments"]),
|
|
for c in detail["comments"]),
|
|
|
"failure explanation kept as an agent comment (author 'agent')")
|
|
"failure explanation kept as an agent comment (author 'agent')")
|
|
|
- check(any(c["body"] == "Task retried by user" for c in detail["comments"]),
|
|
|
|
|
- "retry lands its audit comment")
|
|
|
|
|
|
|
+ check(any(c["body"] == "Task retried by user" and c["author"] == "system"
|
|
|
|
|
+ and c.get("is_system") for c in detail["comments"]),
|
|
|
|
|
+ "retry lands its audit comment as a system row (never an agent row)")
|
|
|
check(len(detail["comments"]) >= comments_before + 1,
|
|
check(len(detail["comments"]) >= comments_before + 1,
|
|
|
"retry keeps the comment history")
|
|
"retry keeps the comment history")
|
|
|
pushes_before = len([p for p in fake.start_payloads
|
|
pushes_before = len([p for p in fake.start_payloads
|
|
|
if int(p.get("task", {}).get("id") or 0) == flaky])
|
|
if int(p.get("task", {}).get("id") or 0) == flaky])
|
|
|
check(wait_for(lambda: len([p for p in fake.start_payloads
|
|
check(wait_for(lambda: len([p for p in fake.start_payloads
|
|
|
if int(p.get("task", {}).get("id") or 0) == flaky])
|
|
if int(p.get("task", {}).get("id") or 0) == flaky])
|
|
|
- > pushes_before or False, timeout=60,
|
|
|
|
|
|
|
+ > pushes_before or False, timeout=90,
|
|
|
what="flaky task re-dispatched") is not None,
|
|
what="flaky task re-dispatched") is not None,
|
|
|
"retried task dispatches again")
|
|
"retried task dispatches again")
|
|
|
# The resumed arc surfaces on the next /status read — in the
|
|
# The resumed arc surfaces on the next /status read — in the
|
|
@@ -2644,11 +2653,13 @@ def bundle_dispatch(ctx):
|
|
|
check(wait_task_done(auth, flaky, timeout=60) is not None,
|
|
check(wait_task_done(auth, flaky, timeout=60) is not None,
|
|
|
"retried task completes on the restored workspace")
|
|
"retried task completes on the restored workspace")
|
|
|
detail = task_state(auth, flaky)
|
|
detail = task_state(auth, flaky)
|
|
|
- check(any("restored the session context" in p["summary"]
|
|
|
|
|
- for p in detail.get("progress", [])),
|
|
|
|
|
|
|
+ check(any("restored the session context" in c["body"]
|
|
|
|
|
+ and c["author"] == "system" and c.get("is_system")
|
|
|
|
|
+ for c in detail.get("comments", [])),
|
|
|
"daemon resume reported on the first status after the restore")
|
|
"daemon resume reported on the first status after the restore")
|
|
|
- check(len(detail["progress"]) > progress_before,
|
|
|
|
|
- "resume note appended to the progress timeline")
|
|
|
|
|
|
|
+ check(all("restored the session context" not in p["summary"]
|
|
|
|
|
+ for p in detail.get("progress", [])),
|
|
|
|
|
+ "the resume note stays OUT of the progress feed (F8: status is not progress)")
|
|
|
|
|
|
|
|
# Manual resume: a task stuck working on an offline saddle.
|
|
# Manual resume: a task stuck working on an offline saddle.
|
|
|
task_id_tmp = create_queued(auth, {
|
|
task_id_tmp = create_queued(auth, {
|
|
@@ -2685,8 +2696,9 @@ def bundle_dispatch(ctx):
|
|
|
check(detail["status"] == "queued",
|
|
check(detail["status"] == "queued",
|
|
|
"manual resume requeues the stuck task")
|
|
"manual resume requeues the stuck task")
|
|
|
check(any(c["body"] == "Saddle became unreachable; task requeued"
|
|
check(any(c["body"] == "Saddle became unreachable; task requeued"
|
|
|
|
|
+ and c["author"] == "system" and c.get("is_system")
|
|
|
for c in detail["comments"]),
|
|
for c in detail["comments"]),
|
|
|
- "manual resume lands the interruption comment")
|
|
|
|
|
|
|
+ "manual resume lands the interruption comment as a system row")
|
|
|
check(all(s["status"] == "offline"
|
|
check(all(s["status"] == "offline"
|
|
|
for s in slot_public(auth, "/saddles", "saddles")["saddles"]),
|
|
for s in slot_public(auth, "/saddles", "saddles")["saddles"]),
|
|
|
"saddle stays offline after the manual requeue")
|
|
"saddle stays offline after the manual requeue")
|
|
@@ -4587,7 +4599,7 @@ def bundle_answers(ctx):
|
|
|
timeout=30, what="accept resume allocation") is not None,
|
|
timeout=30, what="accept resume allocation") is not None,
|
|
|
"accept resumes the parked daemon with the approval notice")
|
|
"accept resumes the parked daemon with the approval notice")
|
|
|
planner.push_results("done", summary="plan accepted and implemented")
|
|
planner.push_results("done", summary="plan accepted and implemented")
|
|
|
- check(wait_task_done(auth, accept_task, timeout=30) is not None,
|
|
|
|
|
|
|
+ check(wait_task_done(auth, accept_task, timeout=60) is not None,
|
|
|
"the accepted plan's task completes")
|
|
"the accepted plan's task completes")
|
|
|
planner.set_state("idle")
|
|
planner.set_state("idle")
|
|
|
|
|
|
|
@@ -4756,9 +4768,10 @@ def bundle_answers(ctx):
|
|
|
timeout=60, what="daemon death -> offline") is not None,
|
|
timeout=60, what="daemon death -> offline") is not None,
|
|
|
"vanishing daemon drives the saddle offline (failed staleness probe)")
|
|
"vanishing daemon drives the saddle offline (failed staleness probe)")
|
|
|
check(wait_for(lambda: any("unreachable" in c["body"]
|
|
check(wait_for(lambda: any("unreachable" in c["body"]
|
|
|
|
|
+ and c.get("is_system")
|
|
|
for c in task_state(auth, vanish).get("comments", []))
|
|
for c in task_state(auth, vanish).get("comments", []))
|
|
|
or False, timeout=30, what="unreachable comment") is not None,
|
|
or False, timeout=30, what="unreachable comment") is not None,
|
|
|
- "the affected task gets an unreachable agent comment")
|
|
|
|
|
|
|
+ "the affected task gets an unreachable system row (never an agent comment)")
|
|
|
check(task_state(auth, vanish)["status"] == "queued",
|
|
check(task_state(auth, vanish)["status"] == "queued",
|
|
|
"the task requeues after the daemon death")
|
|
"the task requeues after the daemon death")
|
|
|
card = saddle_card(auth, "fake-saddle")
|
|
card = saddle_card(auth, "fake-saddle")
|
|
@@ -5240,6 +5253,12 @@ def bundle_goals(ctx):
|
|
|
check(state.get("start") is not None and state.get("cancel") is not None
|
|
check(state.get("start") is not None and state.get("cancel") is not None
|
|
|
and state.get("pause") is None,
|
|
and state.get("pause") is None,
|
|
|
"draft offers start + cancel only")
|
|
"draft offers start + cancel only")
|
|
|
|
|
+ check(state["run_task_id"] == 0 and state["comments"] == [],
|
|
|
|
|
+ "a draft goal has no thread yet (the run task owns it)")
|
|
|
|
|
+ draft_page = http_get_page(auth, "/goals/%d" % goal1)
|
|
|
|
|
+ check('stm-if="goal.run_task_id > 0" stm-action="goal.comment"' in draft_page
|
|
|
|
|
+ and "comments open with its run" in draft_page,
|
|
|
|
|
+ "the draft goal page gates the composer on the run (muted note until then)")
|
|
|
# Branch-name uniqueness: same name, same involved project → refuse.
|
|
# Branch-name uniqueness: same name, same involved project → refuse.
|
|
|
duplicate_directives = act(auth, slot_public(auth, "/goals", "goals")["create"], {
|
|
duplicate_directives = act(auth, slot_public(auth, "/goals", "goals")["create"], {
|
|
|
"project_id": "1", "title": "Clash", "brief": "b", "branch_name": "goal/flange"})
|
|
"project_id": "1", "title": "Clash", "brief": "b", "branch_name": "goal/flange"})
|
|
@@ -6239,16 +6258,16 @@ def bundle_replay(ctx):
|
|
|
conn = db()
|
|
conn = db()
|
|
|
task_comment_bodies = [r[0] for r in conn.execute(
|
|
task_comment_bodies = [r[0] for r in conn.execute(
|
|
|
"select body from task_comments where task_id = ?", (comment_run,))]
|
|
"select body from task_comments where task_id = ?", (comment_run,))]
|
|
|
- goal_comment_bodies = [r[0] for r in conn.execute(
|
|
|
|
|
- "select body from goal_comments where goal_id = ?", (comment_goal,))]
|
|
|
|
|
event_texts = [r[0] for r in conn.execute(
|
|
event_texts = [r[0] for r in conn.execute(
|
|
|
"select text from goal_events where goal_id = ? and kind = 'comment'",
|
|
"select text from goal_events where goal_id = ? and kind = 'comment'",
|
|
|
(comment_goal,))]
|
|
(comment_goal,))]
|
|
|
|
|
+ tables = {r[0] for r in conn.execute(
|
|
|
|
|
+ "select name from sqlite_master where type = 'table'").fetchall()}
|
|
|
conn.close()
|
|
conn.close()
|
|
|
check(task_comment_bodies == [comment_body],
|
|
check(task_comment_bodies == [comment_body],
|
|
|
- "the task comment stores the body, not the command echo")
|
|
|
|
|
- check(goal_comment_bodies == [comment_body],
|
|
|
|
|
- "the goal comment stores the body, not the command echo")
|
|
|
|
|
|
|
+ "the run task's comment stores the body, not the command echo")
|
|
|
|
|
+ check("goal_comments" not in tables,
|
|
|
|
|
+ "the goal_comments table is gone (the run task's rows are the thread)")
|
|
|
check(len(event_texts) == 1 and event_texts[0].startswith("The flange weld holds.")
|
|
check(len(event_texts) == 1 and event_texts[0].startswith("The flange weld holds.")
|
|
|
and "saddle comment" not in event_texts[0] and "EOF" not in event_texts[0],
|
|
and "saddle comment" not in event_texts[0] and "EOF" not in event_texts[0],
|
|
|
"the goal_events comment projection stores the body text")
|
|
"the goal_events comment projection stores the body text")
|
|
@@ -6711,6 +6730,25 @@ def bundle_goalui(ctx):
|
|
|
check(".goal-event.footnote" in css and ".footnote {" in css,
|
|
check(".goal-event.footnote" in css and ".footnote {" in css,
|
|
|
"the stylesheet ships the footnote row weight beside the utility")
|
|
"the stylesheet ships the footnote row weight beside the utility")
|
|
|
|
|
|
|
|
|
|
+ print("== C2: system rows render distinctly on the ONE thread ==")
|
|
|
|
|
+ run_comments = task_state(auth, run_task)["comments"]
|
|
|
|
|
+ system_rows = [c for c in run_comments if c.get("is_system")]
|
|
|
|
|
+ check(system_rows and all(c["author"] == "system" for c in system_rows)
|
|
|
|
|
+ and not any(c.get("from_agent") for c in system_rows),
|
|
|
|
|
+ "the harness bookkeeping rows are authored 'system' (never agent)")
|
|
|
|
|
+ goal_thread = goal_state(goal)["comments"]
|
|
|
|
|
+ check(any(c.get("is_system") for c in goal_thread),
|
|
|
|
|
+ "the goal page reads the same thread (the system rows included)")
|
|
|
|
|
+ css = open(os.path.join(REPO, "src/pages/app.css")).read()
|
|
|
|
|
+ check(".comment.system" in css and ".system-label" in css,
|
|
|
|
|
+ "the stylesheet ships the distinct system-event styling")
|
|
|
|
|
+ page = http_get_page(auth, "/goals/%d" % goal)
|
|
|
|
|
+ check("comment.is_system" in page and "system-label" in page,
|
|
|
|
|
+ "the goal page renders system rows with the labelled muted style")
|
|
|
|
|
+ task_page = http_get_page(auth, "/tasks/%d" % worker)
|
|
|
|
|
+ check("comment.is_system" in task_page and "system-label" in task_page,
|
|
|
|
|
+ "the task page renders system rows with the same style")
|
|
|
|
|
+
|
|
|
print("== C1: one review block per verdict path (F6) ==")
|
|
print("== C1: one review block per verdict path (F6) ==")
|
|
|
# The daemon's honest round pushes the ENTRY and the STATE
|
|
# The daemon's honest round pushes the ENTRY and the STATE
|
|
|
# together — the done pair must raise exactly ONE completion
|
|
# together — the done pair must raise exactly ONE completion
|