|
@@ -1344,20 +1344,43 @@ def bundle_pages(ctx):
|
|
|
"the task page ships no verdict constants (server-owned wording)")
|
|
"the task page ships no verdict constants (server-owned wording)")
|
|
|
check('name="verdict" value="accept"' in task_page_html,
|
|
check('name="verdict" value="accept"' in task_page_html,
|
|
|
"the accept forms post the verdict marker")
|
|
"the accept forms post the verdict marker")
|
|
|
|
|
+ # Plan 16 S5 (U1): verdict-shaped blocks keep their explicit
|
|
|
|
|
+ # buttons + free-text path; a question with NULL options renders
|
|
|
|
|
+ # the question form (radio "other" + free reply), never a
|
|
|
|
|
+ # free-text-only verdict.
|
|
|
|
|
+ check("Request changes" in task_page_html and 'name="feedback"' in task_page_html,
|
|
|
|
|
+ "the review verdict keeps Request changes with free-text feedback (U1)")
|
|
|
|
|
+ check('value="__other__"' in task_page_html and 'name="other"' in task_page_html,
|
|
|
|
|
+ "question blocks render the option/other question form (U1)")
|
|
|
|
|
+ status, body = saddle_api("POST", "/api/saddle/%d/update" % fake_id, fake_token,
|
|
|
|
|
+ {"type": "block", "session_id": session_id, "data": {
|
|
|
|
|
+ "question": "Free-text thought?", "type": "question"}})
|
|
|
|
|
+ check(status == 200 and body.get("block_id"),
|
|
|
|
|
+ "a NULL-options question block raises")
|
|
|
|
|
+ bare_question = [b for b in task_state(auth, plan_task)["blocks"]
|
|
|
|
|
+ if b["question"] == "Free-text thought?"][0]
|
|
|
|
|
+ check(bare_question.get("options") == []
|
|
|
|
|
+ and bare_question.get("block_type") == "question",
|
|
|
|
|
+ "a NULL-options question stays a question (no options, not a verdict)")
|
|
|
|
|
|
|
|
print("== B2: question block body above options ==")
|
|
print("== B2: question block body above options ==")
|
|
|
# Contract side (A2 will restore bodies end-to-end): a question
|
|
# Contract side (A2 will restore bodies end-to-end): a question
|
|
|
# block raised with a heredoc body carries it; the body preview
|
|
# block raised with a heredoc body carries it; the body preview
|
|
|
# rides above the options (template order) and survives answering
|
|
# rides above the options (template order) and survives answering
|
|
|
- # alongside the selected answer.
|
|
|
|
|
|
|
+ # alongside the selected answer. (Blocks list newest-first; the
|
|
|
|
|
+ # U1 fixture above raised a second, options-less question.)
|
|
|
detail = task_state(auth, plan_task)
|
|
detail = task_state(auth, plan_task)
|
|
|
- question = [b for b in detail["blocks"] if b["block_type"] == "question"][0]
|
|
|
|
|
|
|
+ question = [b for b in detail["blocks"]
|
|
|
|
|
+ if b["block_type"] == "question"
|
|
|
|
|
+ and b["body"] == "# Storage choice"][0]
|
|
|
check(question.get("body") == "# Storage choice"
|
|
check(question.get("body") == "# Storage choice"
|
|
|
and question.get("show_body") is True,
|
|
and question.get("show_body") is True,
|
|
|
"question block keeps its body markdown with the preview on")
|
|
"question block keeps its body markdown with the preview on")
|
|
|
act(auth, question["answer_action"], {"option": "SQLite"})
|
|
act(auth, question["answer_action"], {"option": "SQLite"})
|
|
|
detail = task_state(auth, plan_task)
|
|
detail = task_state(auth, plan_task)
|
|
|
- question = [b for b in detail["blocks"] if b["block_type"] == "question"][0]
|
|
|
|
|
|
|
+ question = [b for b in detail["blocks"]
|
|
|
|
|
+ if b["block_type"] == "question"
|
|
|
|
|
+ and b["body"] == "# Storage choice"][0]
|
|
|
check(question.get("answered") is True and question.get("answer") == "SQLite"
|
|
check(question.get("answered") is True and question.get("answer") == "SQLite"
|
|
|
and question.get("body") == "# Storage choice"
|
|
and question.get("body") == "# Storage choice"
|
|
|
and question.get("show_body") is True,
|
|
and question.get("show_body") is True,
|
|
@@ -2681,10 +2704,9 @@ 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 c["body"]
|
|
|
|
|
- and c["author"] == "system" and c.get("is_system")
|
|
|
|
|
|
|
+ check(all("restored the session context" not in c["body"]
|
|
|
for c in detail.get("comments", [])),
|
|
for c in detail.get("comments", [])),
|
|
|
- "daemon resume reported on the first status after the restore")
|
|
|
|
|
|
|
+ "the session-restore stays OUT of the user-facing thread (U2: raw terminal only)")
|
|
|
check(all("restored the session context" not in p["summary"]
|
|
check(all("restored the session context" not in p["summary"]
|
|
|
for p in detail.get("progress", [])),
|
|
for p in detail.get("progress", [])),
|
|
|
"the resume note stays OUT of the progress feed (F8: status is not progress)")
|
|
"the resume note stays OUT of the progress feed (F8: status is not progress)")
|
|
@@ -5401,6 +5423,10 @@ def bundle_goals(ctx):
|
|
|
state = goal_state(goal1)
|
|
state = goal_state(goal1)
|
|
|
check(task_state(auth, run_task)["status"] == "blocked",
|
|
check(task_state(auth, run_task)["status"] == "blocked",
|
|
|
"the goal-run task parks on the plan review")
|
|
"the goal-run task parks on the plan review")
|
|
|
|
|
+ # U3's plan-review counterpart: the run-task card never reads as a
|
|
|
|
|
+ # bare "blocked" while a review-shaped block parks it.
|
|
|
|
|
+ check(task_state(auth, run_task)["display"] == "awaiting your review",
|
|
|
|
|
+ "the run-task card reads 'awaiting your review' during the plan review (U3)")
|
|
|
pending = state.get("plan_pending_block")
|
|
pending = state.get("plan_pending_block")
|
|
|
check(pending and "# Plan v1" in (pending.get("body") or ""),
|
|
check(pending and "# Plan v1" in (pending.get("body") or ""),
|
|
|
"the pending plan review carries the submitted markdown")
|
|
"the pending plan review carries the submitted markdown")
|
|
@@ -5830,6 +5856,12 @@ def bundle_goals(ctx):
|
|
|
"the pending report lives ONLY in its block (goal row clean, S1)")
|
|
"the pending report lives ONLY in its block (goal row clean, S1)")
|
|
|
check(task_state(auth, run_task)["status"] == "blocked",
|
|
check(task_state(auth, run_task)["status"] == "blocked",
|
|
|
"the goal-run task parks on the completion review")
|
|
"the goal-run task parks on the completion review")
|
|
|
|
|
+ # Plan 16 S5 (U3): label reconciliation — the goal awaiting its
|
|
|
|
|
+ # completion review reads "awaiting your review" and the run-task
|
|
|
|
|
+ # status card agrees (never a bare "blocked" for a review park).
|
|
|
|
|
+ check(goal_state(goal1)["display"] == "awaiting your review"
|
|
|
|
|
+ and task_state(auth, run_task)["display"] == "awaiting your review",
|
|
|
|
|
+ "the goal and the run-task card agree: 'awaiting your review' (U3)")
|
|
|
check(state["compare_url"].endswith("/compare/main...goal/flange"),
|
|
check(state["compare_url"].endswith("/compare/main...goal/flange"),
|
|
|
"the completion review links the compare view")
|
|
"the completion review links the compare view")
|
|
|
# Reject first: feedback returns the goal to active. Plan 16 S2:
|
|
# Reject first: feedback returns the goal to active. Plan 16 S2:
|
|
@@ -7105,6 +7137,46 @@ def bundle_goalui(ctx):
|
|
|
check("comment.is_system" in task_page and "system-label" in task_page,
|
|
check("comment.is_system" in task_page and "system-label" in task_page,
|
|
|
"the task page renders system rows with the same style")
|
|
"the task page renders system rows with the same style")
|
|
|
|
|
|
|
|
|
|
+ print("== S5 U2: mechanical rows filtered from user-facing timelines ==")
|
|
|
|
|
+ # The daemon's mechanical system entries (the park notice, the
|
|
|
|
|
+ # restore footnote) ride the wire typed 'system': the raw terminal
|
|
|
|
|
+ # row STORES (session_commands), but no comment row, narrative
|
|
|
|
|
+ # event or dashboard timeline row is authored for them.
|
|
|
|
|
+ open_run = open_session(run_task)
|
|
|
|
|
+ mechanical = [
|
|
|
|
|
+ {"id": 9501, "command": "saddle suspend",
|
|
|
|
|
+ "output": "goal agent parked until an owned task completes or"
|
|
|
|
|
+ " fails; resume via /start resume_notice",
|
|
|
|
|
+ "exit_code": 0, "type": "system", "timestamp": "2026-09-12T00:00:00Z"},
|
|
|
|
|
+ {"id": 9502, "command": "harness resume",
|
|
|
|
|
+ "output": "[harness] session context restored — task resumed",
|
|
|
|
|
+ "exit_code": 0, "type": "system", "timestamp": "2026-09-12T00:00:01Z"},
|
|
|
|
|
+ ]
|
|
|
|
|
+ status, _ = saddle_api("POST", "/api/saddle/results", fake_token,
|
|
|
|
|
+ {"session_id": open_run, "state": "suspended",
|
|
|
|
|
+ "entries": mechanical})
|
|
|
|
|
+ check(status == 200, "the mechanical system entries push cleanly")
|
|
|
|
|
+ conn = db()
|
|
|
|
|
+ raw_rows = conn.execute(
|
|
|
|
|
+ "select count(*) from session_commands where session_id = ?"
|
|
|
|
|
+ " and type = 'system'", (open_run,)).fetchone()[0]
|
|
|
|
|
+ conn.close()
|
|
|
|
|
+ check(raw_rows >= 2,
|
|
|
|
|
+ "the raw terminal keeps the mechanical rows (typed entries store)")
|
|
|
|
|
+ check(all("parked until" not in c["body"]
|
|
|
|
|
+ and "session context restored" not in c["body"]
|
|
|
|
|
+ for c in task_state(auth, run_task)["comments"]),
|
|
|
|
|
+ "no mechanical comment row lands on the thread (U2)")
|
|
|
|
|
+ check(all("parked until" not in e["text"]
|
|
|
|
|
+ and "session context restored" not in e["text"]
|
|
|
|
|
+ for e in goal_state(goal)["events"]),
|
|
|
|
|
+ "no mechanical narrative event lands on the goal page (U2)")
|
|
|
|
|
+ dash_events = slot_public(auth, "/", "dashboard")["activity"]
|
|
|
|
|
+ check(all("parked until" not in e["text"]
|
|
|
|
|
+ and "session context restored" not in e["text"]
|
|
|
|
|
+ for e in dash_events),
|
|
|
|
|
+ "no mechanical row lands on the dashboard timeline (U2)")
|
|
|
|
|
+
|
|
|
print("== C4: head line, answered-blocks expander, footnote comments ==")
|
|
print("== C4: head line, answered-blocks expander, footnote comments ==")
|
|
|
state = goal_state(goal)
|
|
state = goal_state(goal)
|
|
|
check(state["current_activity"] == "parked awaiting its tasks' terminal transitions",
|
|
check(state["current_activity"] == "parked awaiting its tasks' terminal transitions",
|
|
@@ -7147,6 +7219,13 @@ def bundle_goalui(ctx):
|
|
|
conn.close()
|
|
conn.close()
|
|
|
check(completion_block_count() == 1,
|
|
check(completion_block_count() == 1,
|
|
|
"the done state raises exactly ONE completion block")
|
|
"the done state raises exactly ONE completion block")
|
|
|
|
|
+ # Plan 16 S5 (U1): the verdict-shaped block renders the rich review
|
|
|
|
|
+ # form — explicit accept/reject affordances with the free-text
|
|
|
|
|
+ # feedback path, never a free-text-only verdict.
|
|
|
|
|
+ goal_page = http_get_page(auth, "/goals/%d" % goal)
|
|
|
|
|
+ check("Accept goal" in goal_page and "Reject with feedback" in goal_page
|
|
|
|
|
+ and 'name="feedback"' in goal_page,
|
|
|
|
|
+ "the completion review presents explicit accept/reject + free-text feedback (U1)")
|
|
|
# The burst shape: the done ENTRY and the done STATE in one push
|
|
# The burst shape: the done ENTRY and the done STATE in one push
|
|
|
# (fresh entry ids), then the whole pair redelivered — the
|
|
# (fresh entry ids), then the whole pair redelivered — the
|
|
|
# pending-report idempotency leaves the one row alone.
|
|
# pending-report idempotency leaves the one row alone.
|