goals.py 73 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289
  1. """The goals bundle (moved verbatim from the pre-split web_stack_test.py;
  2. see test/harness.py for the shared machinery and the runner)."""
  3. from harness import * # noqa: F401,F403 — the shared wire helpers
  4. # =============================================================================
  5. # Bundle: goals — the plan-12 goal lifecycle: creation with branch-name
  6. # uniqueness, the start → planning → plan review → active flow with
  7. # revisions, the goal agent's task fan-out through the task CLI
  8. # callbacks (ownership + creator-only enqueue), the event-driven
  9. # coalesced resume, pause/resume queueing, the completion review with
  10. # branch push state, the cancel cascade, and the human tasks'
  11. # auto-enqueue-after predecessor.
  12. # =============================================================================
  13. def run(ctx):
  14. auth, fake, fake_id, fake_token = ctx.auth, ctx.fake, ctx.fake_id, ctx.fake_token
  15. app = ctx.app
  16. def create_goal(fields):
  17. act(auth, slot_public(auth, "/goals", "goals")["create"], fields)
  18. rows = [g for g in slot_public(auth, "/goals", "goals")["goals"]
  19. if g["title"] == fields["title"]]
  20. return int(rows[0]["id"])
  21. def plant_live_worker(goal_id, title="Live supervision worker",
  22. status="blocked"):
  23. """Goal 27 refinements — the parking guard: a goal agent may
  24. park only while its goal owns a task whose terminal transition
  25. could still fire (the park's wake source). Scenes that script
  26. the agent to park with every owned task already terminal plant
  27. one BLOCKED live worker first — wakeable (not terminal), never
  28. dispatched (nothing runs behind the scene's back), and swept
  29. by the goal's own cancel/fail cascade at teardown."""
  30. conn = db()
  31. conn.execute("insert into tasks (project_id, goal_id, kind, title,"
  32. " status, priority, mode, creator, created_at)"
  33. " values (1, ?, 'task', ?, ?, 5, 'implement', 'goal',"
  34. " strftime('%s','now'))", (goal_id, title, status))
  35. planted = conn.execute("select max(id) from tasks").fetchone()[0]
  36. conn.commit()
  37. conn.close()
  38. return int(planted)
  39. print("== plan 12: goal creation, validation, lifecycle actions ==")
  40. goal1 = create_goal({"project_id": "1", "title": "Ship the flange",
  41. "brief": "Make the flange module production-grade.",
  42. "branch_name": "goal/flange", "priority": "3"})
  43. state = goal_state(goal1)
  44. check(state["status"] == "draft" and state["display"] == "draft"
  45. and state["branch_name"] == "goal/flange"
  46. and state["base_branch"] == "main",
  47. "a goal lands in draft with its branch pair")
  48. check(state.get("start") is not None and state.get("cancel") is not None
  49. and state.get("pause") is None,
  50. "draft offers start + cancel only")
  51. check(state["run_task_id"] == 0 and state["comments"] == [],
  52. "a draft goal has no thread yet (the run task owns it)")
  53. draft_page = http_get_page(auth, "/goals/%d" % goal1)
  54. check('stm-if="goal.run_task_id > 0" stm-action="goal.comment"' in draft_page
  55. and "comments open with its run" in draft_page,
  56. "the draft goal page gates the composer on the run (muted note until then)")
  57. # Branch-name uniqueness: same name, same involved project → refuse.
  58. duplicate_directives = act(auth, slot_public(auth, "/goals", "goals")["create"], {
  59. "project_id": "1", "title": "Clash", "brief": "b", "branch_name": "goal/flange"})
  60. check(any(d.get("type") == "notify" and d.get("kind") == "error"
  61. and "goal/flange" in (d.get("message") or "")
  62. for d in duplicate_directives),
  63. "a live goal's branch name cannot be reused in an involved project (%s)"
  64. % [d.get("message") for d in duplicate_directives if d.get("type") == "notify"])
  65. conn = db()
  66. conn.execute("insert into projects (name, git_url, default_branch, created_at)"
  67. " values ('other-repo', 'https://example.com/other.git', 'main',"
  68. " strftime('%s','now'))")
  69. conn.commit()
  70. conn.close()
  71. goal_elsewhere = create_goal({"project_id": "2", "title": "Elsewhere",
  72. "brief": "b", "branch_name": "goal/flange"})
  73. check(goal_elsewhere > 0,
  74. "the same branch name in a disjoint project is fine (one per repo)")
  75. # Brief validation.
  76. brief_directives = act(auth, slot_public(auth, "/goals", "goals")["create"], {
  77. "project_id": "1", "title": "No brief", "brief": " ", "branch_name": "goal/x1"})
  78. check(any(d.get("type") == "notify" and d.get("kind") == "error"
  79. and "brief" in (d.get("message") or "").lower()
  80. for d in brief_directives),
  81. "an empty brief rejects")
  82. # Start: planning + the goal-run internal task.
  83. act(auth, state["start"], {})
  84. state = goal_state(goal1)
  85. check(state["status"] == "planning" and state.get("start") is None,
  86. "start moves the goal to planning")
  87. conn = db()
  88. run_row = conn.execute(
  89. "select id, kind, mode, creator, goal_id, priority, status from tasks"
  90. " where goal_id = ? and kind = 'goal_run'", (goal1,)).fetchone()
  91. conn.close()
  92. check(run_row is not None and run_row[1] == "goal_run"
  93. and run_row[2] == "orchestrate" and run_row[3] == "goal"
  94. and run_row[4] == goal1 and run_row[5] == 3
  95. and run_row[6] in ("queued", "assigned", "working"),
  96. "start creates the goal-run task (orchestrate, priority 3, creator goal,"
  97. " queued)")
  98. run_task = int(run_row[0])
  99. check(goal_state(goal1)["run_task_id"] == run_task,
  100. "the goal state carries the run task id")
  101. check(any(e["kind"] == "started" for e in goal_state(goal1)["events"]),
  102. "the narrative records the start")
  103. print("== feedback: goal-agent runs are hidden from every task listing ==")
  104. # The internal run task never lists anywhere tasks are browsed —
  105. # only the goal page exposes it (worker tasks stay listed). The
  106. # plan script is armed BEFORE the first dispatch so this section's
  107. # waits never consume the round the plan-review flow below needs.
  108. fake.goal_script = [("plan", "# Plan v1\n\n1. Add tests\n2. Ship")]
  109. check(wait_for(lambda: task_state(auth, run_task)["status"]
  110. in ("assigned", "working") or False,
  111. timeout=60, what="run task dispatches") is not None,
  112. "the run task reaches its saddle")
  113. queue_rows = slot_public(auth, "/tasks", "tasks")["tasks"]
  114. check(all(int(t["id"]) != run_task for t in queue_rows),
  115. "the tasks queue never lists the goal-agent run task")
  116. check(all(t["kind"] != "goal_run" for t in queue_rows),
  117. "no goal_run rows leak into the queue at all")
  118. project_state = slot_public(auth, "/projects/1", "project")
  119. check(all(int(t["id"]) != run_task for t in project_state["tasks"]),
  120. "the project page's task list excludes the run task")
  121. dashboard = slot_public(auth, "/", "dashboard")
  122. check(all(int(a["id"]) != run_task for a in dashboard["active_tasks"]),
  123. "the dashboard's active tasks exclude the run task")
  124. check(all(int(i["task_id"]) != run_task for i in dashboard["activity"]),
  125. "the activity timeline never references the run task")
  126. check(wait_for(lambda: int(next(
  127. (s for s in slot_public(auth, "/", "dashboard")["saddles"]
  128. if s["name"] == "fake-saddle"), {})
  129. .get("current_goal_id") or 0) == goal1 or False,
  130. timeout=30, what="saddle card links the goal") is not None,
  131. "a saddle hosting the goal agent reads as supervising the goal")
  132. fake_dash_card = next(s for s in slot_public(auth, "/", "dashboard")["saddles"]
  133. if s["name"] == "fake-saddle")
  134. check(fake_dash_card["current_goal_title"] == "Ship the flange",
  135. "the supervision link carries the goal title")
  136. print("== plan 12: the goal agent plans, plan review, revisions ==")
  137. # (The plan script was armed before the run's first dispatch — see
  138. # the listing-hiding section above — so the first round submits it.)
  139. check(wait_for(lambda: goal_state(goal1)["status"] == "plan_review" or False,
  140. timeout=120, what="goal agent submits its plan") is not None,
  141. "the goal agent's plan push enters plan review")
  142. state = goal_state(goal1)
  143. check(task_state(auth, run_task)["status"] == "blocked",
  144. "the goal-run task parks on the plan review")
  145. # U3's plan-review counterpart: the run-task card never reads as a
  146. # bare "blocked" while a review-shaped block parks it.
  147. check(task_state(auth, run_task)["display"] == "awaiting your review",
  148. "the run-task card reads 'awaiting your review' during the plan review (U3)")
  149. pending = state.get("plan_pending_block")
  150. check(pending and "# Plan v1" in (pending.get("body") or ""),
  151. "the pending plan review carries the submitted markdown")
  152. check(any(b["block_type"] == "plan" and b["is_plan_review"]
  153. for b in state["blocks"]),
  154. "the goal blocks list carries the pending plan block")
  155. print("== feedback: goal visual identity ==")
  156. # A goal never reads as a task at a glance: the nav carries the
  157. # plain Goals entry (5eaab31 dropped the nav-goal accent), the
  158. # list page is iconed, and the dashboard pile's goal cards carry
  159. # their own accent styling.
  160. home_html = http_get_page(auth, "/")
  161. check('<a class="nav-goal" href="/goals">' in home_html,
  162. "the nav carries the Goals entry beside Tasks")
  163. check(any(p["item_type"] == "goal" for p in
  164. slot_public(auth, "/", "dashboard")["pinned"]),
  165. "the goal's pending block pins onto the dashboard pile")
  166. # Goal 41 T10: the dashboard's feed is the work stream now; the
  167. # pinned goal cards keep their distinct styling through the
  168. # dynamic class expressions (item_type 'goal' -> goal-card +
  169. # badge-kind-goal at render time).
  170. check("'card goal-card'" in home_html
  171. and "'badge badge-kind-' + blockview.item_type" in home_html,
  172. "dashboard goal cards carry the distinct goal styling")
  173. goals_html = http_get_page(auth, "/goals")
  174. check("&#127919;" in goals_html and "goal-icon" in goals_html,
  175. "the goals list page carries the goal icon")
  176. goal_page_html = http_get_page(auth, "/goals/%d" % goal1)
  177. check('class="card goal-head' in goal_page_html and "goal-page" in goal_page_html,
  178. "the goal page carries the goal-accented identity chrome")
  179. # Reject with feedback: the agent revises.
  180. fake.goal_script = [("plan", "# Plan v2\n\n1. Add tests\n2. Add docs\n3. Ship")]
  181. reject_resume_index = len(fake.goal_assignments)
  182. act(auth, pending["answer_action"], {"answer": "Add documentation please"})
  183. check(wait_for(lambda: goal_state(goal1)["status"] == "plan_review" or False,
  184. timeout=60, what="revised plan enters review") is not None,
  185. "feedback sends the agent back to planning; the revision re-enters review")
  186. # The rejection resume carried NO context_update (nothing in the
  187. # instruction context had changed).
  188. check(all("context_update" not in p
  189. for p in fake.goal_assignments[reject_resume_index:]),
  190. "a resume without context changes carries no context_update")
  191. state = goal_state(goal1)
  192. revisions = state["plan_revisions"]
  193. # Newest-first since the ordering sweep (73dcec9): index 1 is the
  194. # older rejected revision, index 0 the pending re-submission.
  195. check(len(revisions) == 2
  196. and revisions[1]["answered"] and "Add documentation please" in revisions[1]["answer"]
  197. and not revisions[0]["answered"],
  198. "rejected revisions stay as answered history (historical-blocks data)")
  199. # Plan 16 S1 pin: goals.plan is accept-gated — the pending revision
  200. # lives only in its block; nothing lands on the goal before accept.
  201. conn = db()
  202. plan_column = conn.execute(
  203. "select plan from goals where id = ?", (goal1,)).fetchone()[0]
  204. conn.close()
  205. check((plan_column is None or plan_column == "")
  206. and (state["plan"] or "") == "",
  207. "goals.plan stays empty while the revision is only pending (accept-gated, S1)")
  208. # Approve: goal.plan = v2, status active, agent resumes and parks.
  209. # Plan 16 S2: the accept posts the verdict MARKER — the server
  210. # resolves the goal-plan kind and owns the constant.
  211. # (The parking guard: the agent parks only with something live to
  212. # wait on — the blocked worker below is this park's wake source.)
  213. plant_live_worker(goal1)
  214. fake.goal_script = [("suspend",)]
  215. pending = state["plan_pending_block"]
  216. act(auth, pending["answer_action"], {"verdict": "accept"})
  217. check(wait_for(lambda: goal_state(goal1)["status"] == "active" or False,
  218. timeout=60, what="approved plan activates the goal") is not None,
  219. "approving the plan activates the goal")
  220. state = goal_state(goal1)
  221. check(state["plan"] == "# Plan v2\n\n1. Add tests\n2. Add docs\n3. Ship",
  222. "the approved plan markdown is stored on the goal (the accept verdict's write)")
  223. # The approval resume carries the plan delta as a context_update
  224. # turn (the prompt-cache-friendly replacement for the daemon's
  225. # old system-prompt refresh).
  226. check(wait_for(lambda: len(fake.goal_assignments) > 0 and
  227. "context_update" in fake.goal_assignments[-1] or False,
  228. timeout=30, what="approval resume carries context_update") is not None,
  229. "the plan-approval resume carries a context_update turn")
  230. approval_update = fake.goal_assignments[-1]["context_update"]
  231. check("Plan approved" in approval_update and "# Plan v2" in approval_update,
  232. "the context_update summarises the approved plan markdown")
  233. check(wait_for(lambda: task_state(auth, run_task)["status"] == "suspended"
  234. or False, timeout=60, what="goal agent parks after approval") is not None,
  235. "the resumed goal agent parks (saddle suspend)")
  236. print("== plan 12: task fan-out via the task CLI callbacks ==")
  237. # The goal agent (parked on the plan review — D4: its session row
  238. # closed 'parked', still the record the CLI keys on) creates,
  239. # enqueues and deletes its tasks through /api/saddle/task/*.
  240. conn = db()
  241. agent_session = conn.execute(
  242. "select max(id) from sessions where task_id = ?",
  243. (run_task,)).fetchone()[0]
  244. conn.close()
  245. status, body = saddle_api("POST", "/api/saddle/task/create", fake_token, {
  246. "session_id": agent_session, "title": "Flange tests",
  247. "mandate": "Write the test suite for the flange module.",
  248. "mode": "test"})
  249. check(status == 200 and body.get("ok") is True,
  250. "task create under the goal succeeds (%s)" % body.get("error"))
  251. fan_task = int(body["task_id"])
  252. detail = task_state(auth, fan_task)
  253. check(detail["status"] == "draft" and detail["mode"] == "test"
  254. and detail["creator"] == "goal" and detail.get("goal_id") == goal1,
  255. "the created task is a draft goal task with its mode and creator")
  256. check(detail["task_branch"] == "task/%d-%d" % (goal1, fan_task)
  257. and detail["branch_from"] == "goal/flange",
  258. "goal worker tasks get ephemeral branches cut from the goal branch")
  259. check(any(e["kind"] == "task-created" and int(e["task_id"]) == fan_task
  260. for e in goal_state(goal1)["events"]),
  261. "the narrative records the agent's task creation")
  262. # Draft until the agent enqueues; humans may not (creator-only).
  263. check(detail.get("enqueue") is None,
  264. "the human Enqueue action is not offered for goal tasks (creator-only)")
  265. status, body = saddle_api("POST", "/api/saddle/task/enqueue", fake_token, {
  266. "session_id": agent_session, "task_id": fan_task})
  267. check(status == 200 and task_state(auth, fan_task)["status"] == "queued",
  268. "task enqueue by the goal agent queues the draft")
  269. # Ownership + shape errors, verbatim for the CLI.
  270. status, body = saddle_api("POST", "/api/saddle/task/enqueue", fake_token, {
  271. "session_id": agent_session, "task_id": 999999})
  272. check(status == 400 and "No task exists" in (body.get("error") or ""),
  273. "enqueue of a missing task errors verbatim")
  274. create_queued(auth, {"project_id": "1", "title": "Innocent bystander", "priority": "4"})
  275. bystander = task_id_of("Innocent bystander")
  276. status, body = saddle_api("POST", "/api/saddle/task/delete", fake_token, {
  277. "session_id": agent_session, "task_id": bystander})
  278. check(status == 400 and "does not belong to your goal" in (body.get("error") or ""),
  279. "the goal agent cannot touch tasks outside its goal")
  280. status, body = saddle_api("POST", "/api/saddle/task/create", fake_token, {
  281. "session_id": agent_session, "title": "Bad mode", "mandate": "m", "mode": "party"})
  282. check(status == 400 and "valid modes" in (body.get("error") or ""),
  283. "an unknown mode rejects with the valid list")
  284. status, body = saddle_api("POST", "/api/saddle/task/create", fake_token, {
  285. "session_id": agent_session, "title": "Bad tag", "mandate": "m",
  286. "capabilities": ["warp-drive"]})
  287. check(status == 400 and "warp-drive" in (body.get("error") or "")
  288. and "valid tags" in (body.get("error") or ""),
  289. "an unknown capability tag rejects naming the valid set")
  290. # A plain task's session is not a goal-agent identity.
  291. status, body = saddle_api("POST", "/api/saddle/task/create", fake_token,
  292. {"session_id": 999999, "title": "x", "mandate": "m"})
  293. check(status == 400 and "session" in (body.get("error") or ""),
  294. "an unknown session_id errors verbatim")
  295. # Delete: draft + owned.
  296. status, body = saddle_api("POST", "/api/saddle/task/create", fake_token, {
  297. "session_id": agent_session, "title": "Scratch task", "mandate": "m"})
  298. scratch = int(body["task_id"])
  299. status, body = saddle_api("POST", "/api/saddle/task/delete", fake_token, {
  300. "session_id": agent_session, "task_id": scratch})
  301. check(status == 200 and not any(
  302. t["id"] == scratch for t in slot_public(auth, "/tasks", "tasks")["tasks"]),
  303. "task delete removes the owned draft")
  304. status, body = saddle_api("POST", "/api/saddle/task/delete", fake_token, {
  305. "session_id": agent_session, "task_id": fan_task})
  306. check(status == 400 and "draft" in (body.get("error") or ""),
  307. "deleting a queued task refuses (draft only)")
  308. print("== plan 12: goal context in the worker assignment + coalesced resume ==")
  309. # The queued fan-out task dispatches to the daemon with the goal
  310. # reference + ephemeral branch; its completion schedules the parked
  311. # goal agent's resume (>= SADDLE_GOAL_RESUME_S after the event).
  312. check(wait_for(lambda: any(int(p.get("task", {}).get("id") or 0) == fan_task
  313. for p in fake.start_payloads) or False,
  314. timeout=60, what="fan-out task dispatches") is not None,
  315. "the goal worker task dispatches")
  316. fan_payload = [p for p in fake.start_payloads
  317. if int(p.get("task", {}).get("id") or 0) == fan_task][-1]
  318. goal_ctx = fan_payload.get("goal") or {}
  319. check(goal_ctx.get("id") == goal1 and goal_ctx.get("branch") == "goal/flange"
  320. and goal_ctx.get("base_branch") == "main"
  321. and "flange module" in goal_ctx.get("brief", "")
  322. and "# Plan v2" in goal_ctx.get("plan", ""),
  323. "the worker assignment carries the goal context (read-only brief + plan)")
  324. check(fan_payload.get("ephemeral_branch") == "task/%d-%d" % (goal1, fan_task)
  325. and fan_payload.get("kind") == "task" and fan_payload.get("mode") == "test"
  326. and fan_payload.get("creator") == "goal",
  327. "the worker assignment carries the ephemeral branch + identity triplet")
  328. check(wait_task_done(auth, fan_task, timeout=60) is not None,
  329. "the goal worker task completes")
  330. check(any(e["kind"] == "task-done" and int(e["task_id"]) == fan_task
  331. for e in goal_state(goal1)["events"]),
  332. "the narrative records the task's completion")
  333. goal_runs_before = len(fake.goal_assignments)
  334. check(wait_for(lambda: len(fake.goal_assignments) > goal_runs_before or False,
  335. timeout=30, what="parked goal agent resumes") is not None,
  336. "the task-terminal event resumes the parked goal agent")
  337. resume_payload = fake.goal_assignments[-1]
  338. notice = resume_payload.get("resume_notice") or ""
  339. check("Task statuses" in notice and "Flange tests" in notice
  340. and "done" in notice,
  341. "the resume notice batches ALL plan task statuses")
  342. check("New completion reports" in notice and "fake daemon finished" in notice,
  343. "the resume notice carries the new completion report")
  344. check("context_update" not in resume_payload,
  345. "a later resume with no context changes omits context_update entirely")
  346. check(resume_payload.get("kind") == "goal_run"
  347. and resume_payload.get("mode") == "orchestrate",
  348. "the resumed goal agent carries its identity triplet")
  349. # The resumed agent parked again (script): two events at once must
  350. # coalesce into ONE resume within the window.
  351. plant_live_worker(goal1, "Live supervision worker two")
  352. check(wait_for(lambda: task_state(auth, run_task)["status"] == "suspended"
  353. or False, timeout=60, what="goal agent re-parks") is not None,
  354. "the resumed goal agent parks again")
  355. print("== plan 16 S3: delta resume notices — one-line statuses, bodies once ==")
  356. # A MULTI-LINE completion lands while the agent is parked. The next
  357. # notice lists every status ONE-LINE (first line of the summary
  358. # only) and embeds each NEW completion's full body exactly once —
  359. # no statuses-section/reports-section double embedding. (Timestamps
  360. # are second-granular: each fixture waits out the park's second so
  361. # completed_at is strictly newer than suspended_at.)
  362. def run_park_second_passed():
  363. conn = db()
  364. try:
  365. row = conn.execute("select suspended_at from tasks where id = ?",
  366. (run_task,)).fetchone()
  367. if not row or not row[0]:
  368. return False
  369. return time.time() > float(row[0]) + 1.05
  370. finally:
  371. conn.close()
  372. check(wait_for(run_park_second_passed, timeout=30,
  373. what="S3 park second passes") is not None,
  374. "the park stamp is in a strictly older second")
  375. conn = db()
  376. conn.execute("insert into tasks (project_id, goal_id, kind, title, status,"
  377. " priority, summary, completed_at, created_at) values (1, ?, 'task',"
  378. " 'Multiline report', 'done', 5,"
  379. " 'Headline: the flange calibrates\n\nBody: the full report with all"
  380. " the detail lines.',"
  381. " strftime('%s','now'),"
  382. " strftime('%s','now','-30 seconds'))", (goal1,))
  383. conn.commit()
  384. conn.close()
  385. s3_before = len(fake.goal_assignments)
  386. check(wait_for(lambda: len(fake.goal_assignments) > s3_before or False,
  387. timeout=60, what="S3 delta resume fires") is not None,
  388. "the multiline completion resumes the parked agent")
  389. s3_notice = fake.goal_assignments[-1].get("resume_notice") or ""
  390. s3_statuses = s3_notice.split("## Task statuses", 1)[1] \
  391. .split("## New completion reports", 1)[0]
  392. check("Headline: the flange calibrates" in s3_statuses
  393. and "the full report with all the detail lines" not in s3_statuses,
  394. "the statuses section carries ONE LINE per task (the summary's first line)")
  395. check(s3_notice.count("the full report with all the detail lines.") == 1
  396. and "### #" in s3_notice,
  397. "each new completion's full body embeds EXACTLY ONCE (no double embedding)")
  398. plant_live_worker(goal1, "Live supervision worker three")
  399. check(wait_for(lambda: task_state(auth, run_task)["status"] == "suspended"
  400. or False, timeout=60, what="S3 agent re-parks") is not None,
  401. "the S3 agent re-parks")
  402. # Size constant per event, not cumulative: the NEXT resume (armed
  403. # with a fresh single-line completion) reports only the NEW body —
  404. # the consumed multiline report never re-embeds.
  405. check(wait_for(run_park_second_passed, timeout=30,
  406. what="S3 second park second passes") is not None,
  407. "the second park stamp is in a strictly older second")
  408. conn = db()
  409. conn.execute("insert into tasks (project_id, goal_id, kind, title, status,"
  410. " priority, summary, completed_at, created_at) values (1, ?, 'task',"
  411. " 'Fresh completion', 'done', 5, 'fresh one-liner',"
  412. " strftime('%s','now'),"
  413. " strftime('%s','now','-30 seconds'))", (goal1,))
  414. conn.commit()
  415. conn.close()
  416. s3_more = len(fake.goal_assignments)
  417. check(wait_for(lambda: len(fake.goal_assignments) > s3_more or False,
  418. timeout=60, what="S3 second delta resume") is not None,
  419. "the fresh completion resumes the agent again")
  420. s3_next = fake.goal_assignments[-1].get("resume_notice") or ""
  421. check("the full report with all the detail lines." not in s3_next
  422. and "### #" in s3_next and "fresh one-liner" in s3_next,
  423. "the consumed completion's body never re-embeds (notice size constant per event)")
  424. plant_live_worker(goal1, "Live supervision worker four")
  425. check(wait_for(lambda: task_state(auth, run_task)["status"] == "suspended"
  426. or False, timeout=60, what="S3 agent parks a third time") is not None,
  427. "the S3 agent parks for the pause section")
  428. print("== feedback: goal page parity — progress feed, current activity, runs ==")
  429. # The goal agent's runs feed the page's live surfaces: progress
  430. # lines (green latest-first feed), the parked round's newest
  431. # activity line and its hosting saddle. Parked rounds keep their
  432. # session record (closed 'parked', D4) — the page reads the
  433. # newest row, so fixtures land as rows without disturbing the
  434. # scripted daemon.
  435. conn = db()
  436. open_run_session = conn.execute(
  437. "select max(id) from sessions where task_id = ?",
  438. (run_task,)).fetchone()[0]
  439. conn.execute(
  440. "insert into events (session_id, seq, kind, actor, scope_type, scope_id,"
  441. " payload, created_at) values (?, 8001, 'progress', 'model', 'task', ?,"
  442. " ?, strftime('%s','now'))",
  443. (open_run_session, run_task, json.dumps({"text": "Overseeing the flange fan-out"})))
  444. conn.execute(
  445. "insert into events (session_id, seq, kind, actor, scope_type, scope_id,"
  446. " payload, created_at) values (?, 8002, 'tool_call', 'model', 'task', ?,"
  447. " ?, strftime('%s','now'))",
  448. (open_run_session, run_task,
  449. json.dumps({"tool": "run_command",
  450. "args": {"command": "git -C demo log --oneline -5"}})))
  451. conn.commit()
  452. conn.close()
  453. # A manual refresh records the daemon's health snapshot (the
  454. # parked daemon answers /status with its suspended state).
  455. act(auth, saddle_card(auth, "fake-saddle")["refresh"], {})
  456. state = goal_state(goal1)
  457. check(state["progress"] and state["progress"][0]["summary"]
  458. == "Overseeing the flange fan-out",
  459. "the goal page carries the agent's live progress feed")
  460. check(state["current_command"] == "run_command: git -C demo log --oneline -5"
  461. and state["current_command_running"] is True,
  462. "the current activity shows the open round's running command")
  463. check(state["agent_saddle"] == "fake-saddle"
  464. and "fake-saddle" in state["saddle_health"],
  465. "the assigned agent saddle and its health display")
  466. check(state["runs"] and state["runs"][0]["saddle_name"] == "fake-saddle"
  467. and state["runs"][0]["terminal_url"] == "/tasks/%d/log" % run_task,
  468. "the run table carries host saddles and the event log link")
  469. goal_page = http_get_page(auth, "/goals/%d" % goal1)
  470. check('stm-markdown="goal.brief"' in goal_page
  471. and 'stm-markdown="goal.plan"' in goal_page
  472. and 'stm-markdown="comment.body"' in goal_page
  473. and 'stm-markdown="event.text"' in goal_page
  474. and 'stm-markdown="block.body"' in goal_page,
  475. "every long-form goal body renders through stm-markdown")
  476. # Goal 41 T7: the run table's log links navigate SAME-TAB (a new
  477. # tab severed the back button; the log is one back-click away and
  478. # the task page embeds the same feed as its Log tab).
  479. check('stm-attribute.href="run.terminal_url"' in goal_page
  480. and 'target="_blank"' not in goal_page.split("run.terminal_url")[1][:200],
  481. "the run table's terminal links navigate in the same tab")
  482. check("progress-latest" in goal_page and "Agent progress" in goal_page,
  483. "the green progress feed renders on the goal page")
  484. # Plan revision history keeps its verdicts (approved + rejected).
  485. revisions = goal_state(goal1)["plan_revisions"]
  486. # Newest-first since the ordering sweep (73dcec9): index 0 is the
  487. # approved revision, index 1 the older rejected one.
  488. check(revisions[1]["answered"] is True and revisions[1]["approved"] is False
  489. and revisions[0]["approved"] is True,
  490. "plan revision history badges approved and rejected verdicts")
  491. print("== plan 12: pause freezes supervision; events coalesce on resume ==")
  492. state = goal_state(goal1)
  493. act(auth, state["pause"], {})
  494. check(goal_state(goal1)["status"] == "paused"
  495. and goal_state(goal1).get("resume") is not None,
  496. "an active goal pauses and offers resume")
  497. # Terminal events under a paused goal queue without waking anybody.
  498. # (The real completion path — complete_task — records the goal's
  499. # task-done narrative event; the resume machinery keys on exactly
  500. # those terminal kinds, never on the agent's own enqueue narrative.)
  501. conn = db()
  502. conn.execute("insert into tasks (project_id, goal_id, kind, title, status, priority,"
  503. " summary, completed_at, created_at) values (1, ?, 'task',"
  504. " 'Quiet task', 'done', 5, 'finished while paused',"
  505. " strftime('%s','now'), strftime('%s','now'))", (goal1,))
  506. quiet_id = conn.execute("select max(id) from tasks").fetchone()[0]
  507. conn.commit()
  508. conn.close()
  509. paused_runs = len(fake.goal_assignments)
  510. check(quiet_window(quiet_seconds(),
  511. lambda: len(fake.goal_assignments) == paused_runs,
  512. "paused goal does not resume"),
  513. "a paused goal's events queue without scheduling a resume")
  514. state = goal_state(goal1)
  515. act(auth, state["resume"], {})
  516. check(goal_state(goal1)["status"] == "active",
  517. "resume returns the goal to active")
  518. check(wait_for(lambda: len(fake.goal_assignments) > paused_runs or False,
  519. timeout=30, what="queued events flush on resume") is not None,
  520. "the queued events coalesce into the post-resume wake")
  521. check("finished while paused" in (fake.goal_assignments[-1].get("resume_notice") or ""),
  522. "the flushed notice reports the task that finished while paused")
  523. plant_live_worker(goal1, "Live supervision worker five")
  524. check(wait_for(lambda: task_state(auth, run_task)["status"] == "suspended"
  525. or False, timeout=60, what="goal agent parks again") is not None,
  526. "the goal agent parks again after the flush")
  527. # The watermark consumed the flushed transition: with nothing new
  528. # under the goal, no further resume fires (parked stays parked).
  529. flushed_assignments = len(fake.goal_assignments)
  530. check(quiet_window(quiet_seconds(),
  531. lambda: len(fake.goal_assignments) == flushed_assignments,
  532. "consumed watermark never re-fires"),
  533. "the consumed watermark never re-fires a resume")
  534. print("== S6: repeated-failure signature in the resume notice ==")
  535. # The fail-loop shape (web-db health plan A1/A2): worker tasks
  536. # failing with IDENTICAL summary signatures since the park. The
  537. # notice must name the cluster and forbid re-cloning; distinct
  538. # signatures, or failures older than the park, must NOT cluster.
  539. check(wait_for(run_park_second_passed, timeout=30,
  540. what="S6 park second passes") is not None,
  541. "the S6 park stamp is in a strictly older second")
  542. shared_sig = ("curl exited 22: JSON parse error: Invalid UTF-8 middle byte"
  543. " 0x5c — the clamped plan slice cuts a multi-byte character"
  544. " mid-character at byte 3000 of the shared goal plan")
  545. conn = db()
  546. for tail in (" (clone attempt one)", " (clone attempt two)"):
  547. conn.execute("insert into tasks (project_id, goal_id, kind, title, status,"
  548. " priority, summary, completed_at, created_at) values"
  549. " (1, ?, 'task', 'Flange clone', 'failed', 5, ?,"
  550. " strftime('%s','now'), strftime('%s','now','-30 seconds'))",
  551. (goal1, shared_sig + tail))
  552. clone_ids = [int(r[0]) for r in conn.execute(
  553. "select id from tasks where title = 'Flange clone'").fetchall()]
  554. conn.commit()
  555. conn.close()
  556. check(len(clone_ids) == 2,
  557. "the S6 fixture lands two same-signature failed workers")
  558. s6_before = len(fake.goal_assignments)
  559. check(wait_for(lambda: len(fake.goal_assignments) > s6_before or False,
  560. timeout=60, what="S6 repeated-failure resume fires") is not None,
  561. "the repeated failures resume the parked agent")
  562. s6_notice = fake.goal_assignments[-1].get("resume_notice") or ""
  563. check("REPEATED FAILURE" in s6_notice
  564. and "do NOT recreate the same task again" in s6_notice
  565. and "change the approach, narrow the mandate, or split the scope"
  566. in s6_notice,
  567. "the notice carries the repeated-failure instruction")
  568. check(("#%d" % clone_ids[0]) in s6_notice
  569. and ("#%d" % clone_ids[1]) in s6_notice,
  570. "the repeated-failure section names both cluster members")
  571. check("Invalid UTF-8 middle byte 0x5c" in s6_notice,
  572. "the section names the shared signature's first line")
  573. plant_live_worker(goal1, "Live supervision worker six")
  574. check(wait_for(lambda: task_state(auth, run_task)["status"] == "suspended"
  575. or False, timeout=60, what="S6 agent re-parks") is not None,
  576. "the S6 agent re-parks")
  577. # Negative round: one pre-park failure sharing the signature (the
  578. # watermark excludes it) plus one NEW failure with a distinct
  579. # signature — neither forms a ≥2 cluster, so no section renders.
  580. check(wait_for(run_park_second_passed, timeout=30,
  581. what="S6 second park second passes") is not None,
  582. "the S6 negative-round park stamp is strictly older")
  583. conn = db()
  584. conn.execute("insert into tasks (project_id, goal_id, kind, title, status,"
  585. " priority, summary, completed_at, created_at) values"
  586. " (1, ?, 'task', 'Stale signature clone', 'failed', 5, ?,"
  587. " strftime('%s','now','-120 seconds'),"
  588. " strftime('%s','now','-150 seconds'))",
  589. (goal1, shared_sig + " (stale pre-park clone)"))
  590. stale_id = conn.execute(
  591. "select id from tasks where title = 'Stale signature clone'"
  592. ).fetchone()[0]
  593. conn.execute("insert into tasks (project_id, goal_id, kind, title, status,"
  594. " priority, summary, completed_at, created_at) values"
  595. " (1, ?, 'task', 'Distinct failure', 'failed', 5,"
  596. " 'tests failed: the flange widget lacks a widget prop',"
  597. " strftime('%s','now'), strftime('%s','now','-30 seconds'))",
  598. (goal1,))
  599. distinct_id = conn.execute(
  600. "select id from tasks where title = 'Distinct failure'"
  601. ).fetchone()[0]
  602. conn.commit()
  603. conn.close()
  604. s6_more = len(fake.goal_assignments)
  605. check(wait_for(lambda: len(fake.goal_assignments) > s6_more or False,
  606. timeout=60, what="S6 negative-round resume fires") is not None,
  607. "the negative-round failures resume the agent")
  608. s6_next = fake.goal_assignments[-1].get("resume_notice") or ""
  609. check("REPEATED FAILURE" not in s6_next,
  610. "no repeated-failure section without a same-signature pair"
  611. " newer than the park")
  612. check(("#%d" % distinct_id) in s6_next and ("#%d" % stale_id) in s6_next,
  613. "the negative-round notice still reports both failures as statuses")
  614. plant_live_worker(goal1, "Live supervision worker seven")
  615. check(wait_for(lambda: task_state(auth, run_task)["status"] == "suspended"
  616. or False, timeout=60, what="S6 negative agent re-parks")
  617. is not None,
  618. "the S6 negative round re-parks")
  619. print("== S5: the goal page's aggregate LLM usage stat line ==")
  620. # usage_summary_for_goal sums the usage events across every task
  621. # of the goal (the agent's run plus its workers); a goal with no
  622. # usage renders no line.
  623. conn = db()
  624. conn.execute("insert into goals (project_id, title, brief, branch_name,"
  625. " status, created_at) values (1, 'Usage goal', 'Sum the usage.',"
  626. " 'goal/usage', 'active', strftime('%s','now'))")
  627. usage_goal = conn.execute(
  628. "select id from goals where title = 'Usage goal'").fetchone()[0]
  629. for title, payloads in (
  630. ("Usage worker one", ({"prompt_tokens": 1000000, "completion_tokens": 5000,
  631. "elapsed_ms": 3600000},
  632. {"prompt_tokens": 500000, "completion_tokens": 2500,
  633. "elapsed_ms": 1800000})),
  634. ("Usage worker two", ({"prompt_tokens": 2500000, "completion_tokens": 12000,
  635. "elapsed_ms": 5400000},))):
  636. conn.execute("insert into tasks (project_id, goal_id, kind, title, status,"
  637. " priority, created_at) values (1, ?, 'task', ?, 'done', 5,"
  638. " strftime('%s','now'))", (usage_goal, title))
  639. worker = conn.execute(
  640. "select id from tasks where title = ?", (title,)).fetchone()[0]
  641. for payload in payloads:
  642. conn.execute(
  643. "insert into events (kind, actor, scope_type, scope_id, payload,"
  644. " created_at) values ('usage', 'harness', 'task', ?, ?,"
  645. " strftime('%s','now'))",
  646. (worker, json.dumps(payload)))
  647. conn.commit()
  648. conn.close()
  649. state = goal_state(usage_goal)
  650. check(state.get("usage_line") == "LLM: 3 calls · 4.0M in / 19.5K out tok · 3h 0m",
  651. "the goal state sums usage across its tasks (%r)" % state.get("usage_line"))
  652. goal_page = http_get_page(auth, "/goals/%d" % usage_goal)
  653. check('stm-if="goal.usage_line.length &gt; 0"' in goal_page
  654. and 'stm-text="goal.usage_line"' in goal_page,
  655. "the goal page renders the usage stat line in the status head")
  656. check(goal_state(goal_elsewhere).get("usage_line") == "",
  657. "a goal whose tasks recorded no usage carries no stat line")
  658. print("== plan 12: completion review, push state, accept ==")
  659. # The daemon exposes goal-branch push state on /status; the web
  660. # caches it on the goal when it probes the goal agent's saddle.
  661. def status_extra():
  662. return None
  663. fake.status_extra = {"goal_branches": [
  664. {"branch": "goal/flange", "pushed": True},
  665. {"branch": "goal/other-repo", "pushed": False}]}
  666. fake_card = saddle_card(auth, "fake-saddle")
  667. act(auth, fake_card["refresh"], {})
  668. check(wait_for(lambda: goal_state(goal1).get("push_branches") or False,
  669. timeout=30, what="push state lands on the goal") is not None,
  670. "the daemon's /status goal_branches exposure caches on the goal")
  671. branches = goal_state(goal1)["push_branches"]
  672. check({(b["branch"], b["pushed"]) for b in branches}
  673. == {("goal/flange", True), ("goal/other-repo", False)},
  674. "the push state renders per branch")
  675. # the daemon's real shape carries repo too — multi-repo rows stay
  676. # distinguishable as "repo: branch" labels
  677. fake.status_extra = {"goal_branches": [
  678. {"repo": "repoa", "branch": "goal/flange", "pushed": True},
  679. {"repo": "repob", "branch": "goal/flange", "pushed": False}]}
  680. act(auth, fake_card["refresh"], {})
  681. check(wait_for(lambda: {b["branch"] for b in goal_state(goal1).get("push_branches") or []}
  682. >= {"repoa: goal/flange"}, timeout=30,
  683. what="repo-qualified push state lands") is not None,
  684. "repo-qualified goal_branches rows render as 'repo: branch'")
  685. fake.status_extra = None
  686. # `saddle done` in goal context: the completion review.
  687. fake.goal_script = [("done", "# Completion\n\nThe flange ships.")]
  688. # Wake the parked agent: a terminal transition newer than the
  689. # watermark (the pause-flush consumed everything up to it, so arm
  690. # one directly with a fresh row).
  691. conn = db()
  692. conn.execute("insert into tasks (project_id, goal_id, kind, title, status,"
  693. " priority, completed_at, created_at) values (1, ?, 'task',"
  694. " 'Warmup', 'done', 5,"
  695. " strftime('%s','now'),"
  696. " strftime('%s','now','-30 seconds'))", (goal1,))
  697. conn.commit()
  698. conn.close()
  699. check(wait_for(lambda: goal_state(goal1)["status"] == "completion_review" or False,
  700. timeout=60, what="goal agent submits its completion") is not None,
  701. "`saddle done` in goal context enters completion review")
  702. state = goal_state(goal1)
  703. # Plan 16 S1 (canonical-on-accept): while the review is PENDING the
  704. # goal row carries NO report — the document lives only in its
  705. # block; completion_report is written solely by the accept verdict.
  706. conn = db()
  707. pending_report_column = conn.execute(
  708. "select completion_report from goals where id = ?", (goal1,)).fetchone()[0]
  709. conn.close()
  710. check((pending_report_column is None or pending_report_column == "")
  711. and (state["completion_report"] or "") == ""
  712. and "# Completion" in (state["completion_pending_block"].get("body") or "")
  713. and state["completion_pending"] is True
  714. and state["completion_pending_block"] is not None
  715. and state["completion_pending_block"] is not False,
  716. "the pending report lives ONLY in its block (goal row clean, S1)")
  717. check(task_state(auth, run_task)["status"] == "blocked",
  718. "the goal-run task parks on the completion review")
  719. # Plan 16 S5 (U3): label reconciliation — the goal awaiting its
  720. # completion review reads "awaiting your review" and the run-task
  721. # status card agrees (never a bare "blocked" for a review park).
  722. check(goal_state(goal1)["display"] == "awaiting your review"
  723. and task_state(auth, run_task)["display"] == "awaiting your review",
  724. "the goal and the run-task card agree: 'awaiting your review' (U3)")
  725. check(state["compare_url"].endswith("/compare/main...goal/flange"),
  726. "the completion review links the compare view")
  727. # Reject first: feedback returns the goal to active. Plan 16 S2:
  728. # the goal page's reject posts raw `feedback` — the SERVER wraps
  729. # it with the completion-review prefix (byte-identical to the
  730. # historical client-built answer).
  731. plant_live_worker(goal1, "Live supervision worker eight")
  732. fake.goal_script = [("suspend",)]
  733. act(auth, state["completion_pending_block"]["answer_action"],
  734. {"feedback": "Not done — the docs are missing"})
  735. check(wait_for(lambda: goal_state(goal1)["status"] == "active" or False,
  736. timeout=30, what="rejected completion reactivates") is not None,
  737. "rejecting the completion returns the goal to active")
  738. conn = db()
  739. rejected_answer = conn.execute(
  740. "select answer from task_blocks where task_id = ?"
  741. " and type = 'completion' order by id desc", (run_task,)).fetchone()[0]
  742. conn.close()
  743. check(rejected_answer
  744. == "Review feedback received:\n\nNot done — the docs are missing",
  745. "the completion rejection wraps feedback with the stable prefix (%r)"
  746. % rejected_answer)
  747. conn = db()
  748. rejected_report = conn.execute(
  749. "select completion_report from goals where id = ?", (goal1,)).fetchone()[0]
  750. conn.close()
  751. check(rejected_report is None or rejected_report == "",
  752. "reject/revise leaves the goal row clean (no report leaked, S1)")
  753. # Round 2: submit again and accept.
  754. fake.goal_script = [("done", "# Completion v2\n\nThe flange ships, with docs.")]
  755. conn = db()
  756. conn.execute("insert into tasks (project_id, goal_id, kind, title, status,"
  757. " priority, completed_at, created_at) values (1, ?, 'task',"
  758. " 'Warmup2', 'done', 5,"
  759. " strftime('%s','now'),"
  760. " strftime('%s','now','-30 seconds'))", (goal1,))
  761. conn.commit()
  762. conn.close()
  763. check(wait_for(lambda: goal_state(goal1)["status"] == "completion_review" or False,
  764. timeout=60, what="resubmitted completion") is not None,
  765. "the agent resubmits its completion after feedback")
  766. state = goal_state(goal1)
  767. # Regression (plan-15 followup): the daemon's done-park ALSO pushes a
  768. # review-kind block payload (question "Goal completion review", the
  769. # real daemon's wording) after the done entry raised the completion
  770. # row — the two paths must converge on ONE row, not twin reviews.
  771. fake.push_results("blocked", block={
  772. "question": "Goal completion review", "type": "review",
  773. "body": "# Completion v2\n\nThe flange ships, with docs.",
  774. "options": None})
  775. conn = db()
  776. rows = conn.execute(
  777. "select count(*) from task_blocks where task_id = ?"
  778. " and answer is null and type in ('completion','review')",
  779. (run_task,)).fetchone()[0]
  780. conn.close()
  781. check(rows == 1,
  782. "the daemon's review-kind completion push adopts the pending row (no twin)")
  783. state = goal_state(goal1)
  784. review_family_in_open = [b for b in state.get("open_blocks", [])
  785. if b.get("is_completion_review")]
  786. check(state["completion_pending"] is True and len(review_family_in_open) == 1,
  787. "the pending completion review renders ONCE — as the rich attention block")
  788. fake.pending_block = None
  789. # Raise-side single-row invariant (the render guard and the
  790. # twin-answer sweep are gone — fresh DBs cannot hold pre-dedupe
  791. # twins, and 4884259's raise-side cross-dedupe keeps it that way):
  792. # a REDELIVERED review-kind completion push adopts the same pending
  793. # row instead of stacking a twin.
  794. fake.push_results("blocked", block={
  795. "question": "Goal completion review", "type": "review",
  796. "body": "# Completion v2\n\nThe flange ships, with docs.",
  797. "options": None})
  798. fake.pending_block = None
  799. conn = db()
  800. rows = conn.execute(
  801. "select count(*) from task_blocks where task_id = ?"
  802. " and answer is null and type in ('completion','review')",
  803. (run_task,)).fetchone()[0]
  804. conn.close()
  805. check(rows == 1,
  806. "a redelivered review-kind push adopts the pending row (raise-side single-row invariant)")
  807. # Goal 21 (accept-gate removal): the accept-time push gate is GONE.
  808. # The push-before-review rule lives daemon-side, at the agent's
  809. # completion attempt — a goal agent parked in this review has no
  810. # open session, so the web's probe flags are necessarily stale and
  811. # an accept-time check could only misfire (error 88 wedged the
  812. # goal in completion_review forever). Regression: with the cached
  813. # flags still reading repob UNPUSHED, the accept must go straight
  814. # through — the flags feed only the goal page's advisory chips.
  815. fake.status_extra = {"goal_branches": [
  816. {"repo": "repoa", "branch": "goal/flange", "pushed": True},
  817. {"repo": "repob", "branch": "goal/flange", "pushed": False}]}
  818. act(auth, fake_card["refresh"], {})
  819. check(wait_for(lambda: (goal_state(goal1).get("push_branches") or [])
  820. and any((b["branch"], b["pushed"]) == ("repob: goal/flange", False)
  821. for b in goal_state(goal1)["push_branches"])
  822. or False, timeout=30, what="unpushed flags land") is not None,
  823. "the chips still render the live (unpushed) probe flags")
  824. conn = db()
  825. no_push_column = conn.execute(
  826. "select count(*) from pragma_table_info('goals')"
  827. " where name = 'branch_push_state'").fetchone()[0] == 0
  828. conn.close()
  829. check(no_push_column,
  830. "no stored push snapshot exists (the goal row never records it)")
  831. act(auth, state["completion_pending_block"]["answer_action"],
  832. {"verdict": "accept"})
  833. check(wait_for(lambda: goal_state(goal1)["status"] == "complete" or False,
  834. timeout=30, what="accepted completion completes") is not None,
  835. "the accept COMPLETES the goal while cached flags read unpushed (no gate)")
  836. state = goal_state(goal1)
  837. conn = db()
  838. accepted_report = conn.execute(
  839. "select completion_report from goals where id = ?", (goal1,)).fetchone()[0]
  840. conn.close()
  841. check(state["terminal"] is True and state.get("cancel") is None
  842. and accepted_report == "# Completion v2\n\nThe flange ships, with docs."
  843. and "# Completion v2" in state["completion_report"],
  844. "a complete goal is terminal with the WINNING block's body as its report (S1)")
  845. check(wait_for(lambda: task_state(auth, run_task)["status"] == "done" or False,
  846. timeout=30, what="goal run task closes") is not None,
  847. "approving the completion closes the goal-run task done")
  848. conn = db()
  849. twins_left = conn.execute(
  850. "select count(*) from task_blocks where task_id = ?"
  851. " and answer is null and type in ('completion','review')",
  852. (run_task,)).fetchone()[0]
  853. conn.close()
  854. check(twins_left == 0,
  855. "the verdict answered the pending completion-family row (single-row invariant)")
  856. # Plan 16 D4 (web half): the verdict path closes what it closes —
  857. # after the accept NO session row of the run task dangles open,
  858. # and every parked round carries outcome 'parked' (the daemon's
  859. # file-log park brackets name these same rows).
  860. conn = db()
  861. open_after_accept = conn.execute(
  862. "select count(*) from sessions where task_id = ? and ended_at is null",
  863. (run_task,)).fetchone()[0]
  864. parked_rounds = conn.execute(
  865. "select count(*) from sessions where task_id = ? and outcome = 'parked'",
  866. (run_task,)).fetchone()[0]
  867. conn.close()
  868. check(open_after_accept == 0,
  869. "the accept leaves no dangling open session (D4: every park closed its row)")
  870. check(parked_rounds >= 1,
  871. "the run's parked rounds all closed 'parked' (D4 outcome rename)")
  872. print("== plan 15 C3: the narrative derives from the recorded shape ==")
  873. # Content equivalence with the old projection on this run's exact
  874. # shape: every kind the goal_events projection produced, keyed the
  875. # same way, plus the watermark and no-ingest guarantees.
  876. narrative = goal_state(goal1)["events"]
  877. kinds = [e["kind"] for e in narrative]
  878. for expected in ("created", "started", "plan-submitted", "plan-rejected",
  879. "plan-approved", "task-created", "task-done",
  880. "completion-submitted", "completion-rejected", "goal-approved"):
  881. check(expected in kinds,
  882. "the derived narrative carries the %s row" % expected)
  883. check(all(k != "task-enqueued" and k != "resumed" and k != "retry" for k in kinds),
  884. "timestamp-less bookkeeping kinds never return")
  885. stamps = [e["at_display"] for e in narrative if e.get("at_display")]
  886. check(stamps == sorted(stamps, reverse=True),
  887. "the narrative reads newest first")
  888. conn = db()
  889. no_event_table = conn.execute(
  890. "select count(*) from sqlite_master where type = 'table'"
  891. " and name = 'goal_events'").fetchone()[0] == 0
  892. conn.close()
  893. check(no_event_table,
  894. "the goal_events table is gone (no narrative rows on ingest)")
  895. print("== plan 12: cancel cascades to the goal's tasks ==")
  896. goal2 = create_goal({"project_id": "1", "title": "Doomed goal",
  897. "brief": "This goal will be cancelled.",
  898. "branch_name": "goal/doomed"})
  899. plant_live_worker(goal2, "Doomed live worker")
  900. fake.goal_script = [("suspend",)]
  901. act(auth, goal_state(goal2)["start"], {})
  902. conn = db()
  903. run2 = conn.execute("select id from tasks where goal_id = ? and kind = 'goal_run'",
  904. (goal2,)).fetchone()[0]
  905. conn.close()
  906. check(wait_for(lambda: task_state(auth, run2)["status"] == "suspended" or False,
  907. timeout=60, what="doomed goal agent parks") is not None,
  908. "the doomed goal's agent parks")
  909. conn = db()
  910. agent_session2 = conn.execute(
  911. "select max(id) from sessions where task_id = ?",
  912. (run2,)).fetchone()[0]
  913. conn.close()
  914. status, body = saddle_api("POST", "/api/saddle/task/create", fake_token, {
  915. "session_id": agent_session2, "title": "Doomed draft", "mandate": "m"})
  916. doomed_draft = int(body["task_id"])
  917. doomed_queued_task = None
  918. status, body = saddle_api("POST", "/api/saddle/task/create", fake_token, {
  919. "session_id": agent_session2, "title": "Doomed runner", "mandate": "m"})
  920. doomed_runner = int(body["task_id"])
  921. saddle_api("POST", "/api/saddle/task/enqueue", fake_token,
  922. {"session_id": agent_session2, "task_id": doomed_runner})
  923. act(auth, goal_state(goal2)["cancel"], {})
  924. check(goal_state(goal2)["status"] == "cancelled",
  925. "the goal cancels from active (parked-agent) state")
  926. check(task_state(auth, run2)["status"] == "failed",
  927. "the cascade cancels the goal agent's run task")
  928. check(wait_for(lambda: task_state(auth, doomed_runner)["status"] == "failed"
  929. or False, timeout=30, what="cascade cancels the queued task") is not None,
  930. "the cascade cancels the goal's queued task")
  931. check(not any(t["id"] == doomed_draft
  932. for t in slot_public(auth, "/tasks", "tasks")["tasks"]),
  933. "the cascade deletes the goal's draft tasks")
  934. # A cancelled goal frees its branch name.
  935. reuse = create_goal({"project_id": "1", "title": "Reused name",
  936. "brief": "b", "branch_name": "goal/doomed"})
  937. check(reuse > goal2, "a cancelled goal's branch name is reusable")
  938. print("== plan 13 B3: goal failure cascades to non-terminal tasks ==")
  939. # A goal holding a draft, a queued task and a working task: the
  940. # agent's failure cascades like a cancellation — drafts deleted (no
  941. # orphan stays dispatchable), queued/working cancelled — and the
  942. # narrative records the cascade.
  943. collapse = create_goal({"project_id": "1", "title": "Collapse goal",
  944. "brief": "Fails with tasks in flight.",
  945. "branch_name": "goal/collapse"})
  946. plant_live_worker(collapse, "Collapse live worker")
  947. fake.goal_script = [("suspend",)]
  948. act(auth, goal_state(collapse)["start"], {})
  949. conn = db()
  950. run3 = conn.execute("select id from tasks where goal_id = ? and kind = 'goal_run'",
  951. (collapse,)).fetchone()[0]
  952. conn.close()
  953. check(wait_for(lambda: task_state(auth, run3)["status"] == "suspended" or False,
  954. timeout=60, what="collapse goal agent parks") is not None,
  955. "the collapse goal's agent parks first")
  956. conn = db()
  957. run3_session = conn.execute(
  958. "select max(id) from sessions where task_id = ?",
  959. (run3,)).fetchone()[0]
  960. conn.close()
  961. def agent_task(title):
  962. status, body = saddle_api("POST", "/api/saddle/task/create", fake_token, {
  963. "session_id": run3_session, "title": title, "mandate": "m"})
  964. task_id = int(body["task_id"])
  965. saddle_api("POST", "/api/saddle/task/enqueue", fake_token,
  966. {"session_id": run3_session, "task_id": task_id})
  967. return task_id
  968. status, body = saddle_api("POST", "/api/saddle/task/create", fake_token, {
  969. "session_id": run3_session, "title": "Collapse draft", "mandate": "m"})
  970. collapse_draft = int(body["task_id"]) # stays draft (never enqueued)
  971. collapse_queued = agent_task("Collapse queued")
  972. collapse_worker = agent_task("Collapse worker")
  973. # Both queued tasks race for the one saddle; freeze the daemon the
  974. # moment the first dispatches so one stays genuinely WORKING (and
  975. # the other queued) when the goal fails.
  976. fake.hold_state = True
  977. def collapse_working():
  978. for task_id in (collapse_queued, collapse_worker):
  979. if task_state(auth, task_id)["status"] == "working":
  980. return task_id
  981. return False
  982. running_one = wait_for(collapse_working, timeout=60, what="a collapse task works")
  983. check(running_one is not None,
  984. "a collapse task reaches working before the failure")
  985. queued_one = collapse_worker if running_one == collapse_queued else collapse_queued
  986. status, _ = saddle_api("POST", "/api/saddle/results", fake_token,
  987. {"session_id": run3_session, "state": "failed",
  988. "summary": "the foundation crumbled"})
  989. check(status == 200 and goal_state(collapse)["status"] == "failed",
  990. "the agent's failure fails the goal")
  991. check(task_state(auth, run3)["status"] == "failed",
  992. "the failure fails the goal agent's run task")
  993. check(wait_for(lambda: task_state(auth, queued_one)["status"] == "failed"
  994. and task_state(auth, running_one)["status"] == "failed" or False,
  995. timeout=30, what="failure cascade settles") is not None,
  996. "the failure cascade cancels the queued and working tasks")
  997. check(not any(t["id"] == collapse_draft
  998. for t in slot_public(auth, "/tasks", "tasks")["tasks"]),
  999. "the failure cascade deletes the goal's draft tasks")
  1000. state = goal_state(collapse)
  1001. check(any(e["kind"] == "cascade" for e in state["events"]),
  1002. "the narrative records the failure cascade")
  1003. check(state["draft_tasks"] == 0 and state["queued_tasks"] == 0,
  1004. "nothing under the failed goal stays dispatchable (no orphan drafts)")
  1005. check(len([e for e in state["events"] if e["kind"] == "goal-failed"]) == 1,
  1006. "the failing goal records exactly one goal-failed event")
  1007. # Plan 16 S1: the FAILURE report keeps its goal-side home (failure
  1008. # is terminal — no pending duplication exists, so unlike the
  1009. # completion report it is written at failure time).
  1010. conn = db()
  1011. failure_report = conn.execute(
  1012. "select completion_report from goals where id = ?", (collapse,)).fetchone()[0]
  1013. conn.close()
  1014. check(failure_report == "the foundation crumbled",
  1015. "fail_goal's failure report keeps its goal-side home (S1)")
  1016. # Plan 16 S4: the narrative renders the task-failed rows from the
  1017. # terminal summary's lead — the run thread carries NO duplicate
  1018. # agent comment of the goal failure text.
  1019. failed_rows = [e for e in state["events"] if e["kind"] == "task-failed"]
  1020. check(failed_rows
  1021. and all(": Cancelled by user" in e["text"] for e in failed_rows),
  1022. "task-failed narrative rows render the summary lead (S4: summary is the terminal cache)")
  1023. run_comments = [c for c in task_state(auth, run3)["comments"]
  1024. if c["author"] == "agent"]
  1025. check(not any("the foundation crumbled" in c["body"] for c in run_comments),
  1026. "the goal failure text never duplicates as an agent comment (S4)")
  1027. fake.hold_state = False
  1028. print("== plan 12: auto-enqueue-after for human tasks ==")
  1029. act(auth, slot_public(auth, "/tasks", "tasks")["create"], {
  1030. "project_id": "1", "title": "Predecessor task", "priority": "4"})
  1031. predecessor = task_id_of("Predecessor task")
  1032. enqueue_task(auth, predecessor)
  1033. act(auth, slot_public(auth, "/tasks", "tasks")["create"], {
  1034. "project_id": "1", "title": "Successor task", "priority": "4",
  1035. "enqueue_after_task_id": str(predecessor)})
  1036. successor = task_id_of("Successor task")
  1037. check(task_state(auth, successor)["status"] == "draft"
  1038. and task_state(auth, successor).get("enqueue_after_task_id") == predecessor,
  1039. "a human task can carry an auto-enqueue predecessor")
  1040. check(wait_task_done(auth, predecessor, timeout=60) is not None,
  1041. "the predecessor completes")
  1042. check(wait_for(lambda: task_state(auth, successor)["status"] in
  1043. ("queued", "assigned", "working") or False,
  1044. timeout=30, what="successor auto-enqueues") is not None,
  1045. "the done predecessor auto-enqueues its dependent")
  1046. # Cycle validation refuses a predecessor chain that loops.
  1047. act(auth, slot_public(auth, "/tasks", "tasks")["create"], {
  1048. "project_id": "1", "title": "Cycle A", "priority": "4"})
  1049. cycle_a = task_id_of("Cycle A")
  1050. act(auth, slot_public(auth, "/tasks", "tasks")["create"], {
  1051. "project_id": "1", "title": "Cycle B", "priority": "4",
  1052. "enqueue_after_task_id": str(cycle_a)})
  1053. cycle_b = task_id_of("Cycle B")
  1054. cycle_directives = act(auth, task_state(auth, cycle_a)["update"], {
  1055. "title": "Cycle A", "description": "x", "priority": "4",
  1056. "enqueue_after_task_id": str(cycle_b)})
  1057. check(any(d.get("type") == "notify" and d.get("kind") == "error"
  1058. and "cycle" in (d.get("message") or "").lower()
  1059. for d in cycle_directives),
  1060. "a predecessor cycle rejects with a naming error")
  1061. # A failed predecessor leaves the dependent in draft.
  1062. fake.fail_next = True
  1063. act(auth, slot_public(auth, "/tasks", "tasks")["create"], {
  1064. "project_id": "1", "title": "Failing predecessor", "priority": "1"})
  1065. failing_pred = task_id_of("Failing predecessor")
  1066. enqueue_task(auth, failing_pred)
  1067. act(auth, slot_public(auth, "/tasks", "tasks")["create"], {
  1068. "project_id": "1", "title": "Stranded successor", "priority": "4",
  1069. "enqueue_after_task_id": str(failing_pred)})
  1070. stranded = task_id_of("Stranded successor")
  1071. check(wait_for(lambda: task_state(auth, failing_pred)["status"] == "failed"
  1072. or False, timeout=60, what="failing predecessor fails") is not None,
  1073. "the scripted-failure predecessor fails")
  1074. check(quiet_window(quiet_seconds(),
  1075. lambda: task_state(auth, stranded)["status"] == "draft",
  1076. "failed predecessor leaves the draft"),
  1077. "a failed predecessor leaves its dependent in draft")
  1078. # Successor cleanup: let the queued successor finish so the
  1079. # shard's app shuts down with an empty queue.
  1080. check(wait_task_done(auth, successor, timeout=90) is not None,
  1081. "the auto-enqueued successor completes")
  1082. print("== feedback: context_update carries rule/note deltas on resume ==")
  1083. # A standalone task parks on a question; while it is parked a rule
  1084. # is attached to the project and a note lands; the resuming
  1085. # assignment carries both as the context_update markdown turn.
  1086. probe_id = create_queued(auth, {"project_id": "1", "title": "Context probe",
  1087. "priority": "4"})
  1088. check(wait_for(lambda: fake.running_task == probe_id
  1089. and task_state(auth, probe_id)["status"] == "working" or False,
  1090. timeout=60, what="probe task runs") is not None,
  1091. "the probe task dispatches")
  1092. fake.set_state("blocked")
  1093. fake.push_results("blocked", block={"type": "question",
  1094. "question": "Which database dialect?"})
  1095. check(wait_for(lambda: task_state(auth, probe_id)["status"] == "blocked" or False,
  1096. timeout=60, what="probe task blocks") is not None,
  1097. "the probe task parks on its question")
  1098. detail = task_state(auth, probe_id)
  1099. open_block = [b for b in detail["blocks"] if not b["answered"]][0]
  1100. # Backdate the parked round's start: stamps are second-resolution
  1101. # epochs, and the delta compares strictly-later — the park must
  1102. # span a real interval like an operator's would.
  1103. conn = db()
  1104. conn.execute("update sessions set started_at = strftime('%s','now') - 60"
  1105. " where id = (select min(id) from sessions where task_id = ?)",
  1106. (probe_id,))
  1107. conn.commit()
  1108. conn.close()
  1109. # While parked: a new rule attached to the project + a new note.
  1110. rules_state = slot_public(auth, "/rules", "rules")
  1111. act(auth, rules_state["create"], {
  1112. "name": "Nightly gate", "body": "Every PR must pass the nightly suite before merge."})
  1113. conn = db()
  1114. rule_id = conn.execute("select id from rules where name = 'Nightly gate'").fetchone()[0]
  1115. conn.close()
  1116. act(auth, slot_public(auth, "/projects/1", "project")["attach_rule"],
  1117. {"rule_id": str(rule_id)})
  1118. conn = db()
  1119. conn.execute("insert into project_notes (project_id, body, created_at)"
  1120. " values (1, 'The demo repo moved its CI to nightly builds.',"
  1121. " strftime('%s','now'))")
  1122. conn.commit()
  1123. check(conn.execute("select count(*) from rule_projects where rule_id = ?",
  1124. (rule_id,)).fetchone()[0] == 1,
  1125. "the rule attaches to the project while the task is parked")
  1126. conn.close()
  1127. probe_assignments = len([p for p in fake.start_payloads
  1128. if int(p.get("task", {}).get("id") or 0) == probe_id])
  1129. act(auth, open_block["answer_action"], {"answer": "postgres"})
  1130. check(wait_for(lambda: len([p for p in fake.start_payloads
  1131. if int(p.get("task", {}).get("id") or 0) == probe_id])
  1132. > probe_assignments or False, timeout=60,
  1133. what="probe task resumes") is not None,
  1134. "answering the block resumes the probe task")
  1135. probe_resume = [p for p in fake.start_payloads
  1136. if int(p.get("task", {}).get("id") or 0) == probe_id][-1]
  1137. delta = probe_resume.get("context_update") or ""
  1138. check("Rules changed" in delta and "Nightly gate" in delta
  1139. and "nightly suite" in delta,
  1140. "the resume's context_update summarises the newly attached rule")
  1141. check("New project notes" in delta and "nightly builds" in delta,
  1142. "the resume's context_update summarises the new project note")
  1143. check("Plan approved" not in delta,
  1144. "standalone resumes carry no plan section")
  1145. check(wait_task_done(auth, probe_id, timeout=60) is not None,
  1146. "the probe task completes after its resume")
  1147. print("== goal 4 t1: force wake — the human resume for a parked agent ==")
  1148. # The goal-2 stall class: the parked run has NO pending wake (a
  1149. # draft task never enqueued → no terminal transition → parked
  1150. # forever). Force wake is the human recovery lever. A fresh goal
  1151. # owns the scenario (goal1's run completed with its goal).
  1152. # (Goal 27 refinements — the parking guard: the park needs a live
  1153. # wake source now, so the blocked worker rides along; the never-
  1154. # enqueued DRAFT below still produces no transition, which is the
  1155. # stall the wake recovers.)
  1156. stall_goal = create_goal({"project_id": "1", "title": "Stall goal",
  1157. "brief": "Parks with a never-enqueued draft.",
  1158. "branch_name": "goal/stall"})
  1159. plant_live_worker(stall_goal, "Stall live worker")
  1160. fake.goal_script = [("plan", "# Stall plan\n\n1. Wait for the stall")]
  1161. act(auth, goal_state(stall_goal)["start"], {})
  1162. check(wait_for(lambda: goal_state(stall_goal)["status"] == "plan_review" or False,
  1163. timeout=60, what="stall plan enters review") is not None,
  1164. "the stall goal reaches plan review")
  1165. act(auth, goal_state(stall_goal)["plan_pending_block"]["answer_action"],
  1166. {"verdict": "accept"})
  1167. check(wait_for(lambda: goal_state(stall_goal)["status"] == "active" or False,
  1168. timeout=30, what="stall goal activates") is not None,
  1169. "the stall goal activates")
  1170. fake.goal_script = [("suspend",)]
  1171. conn = db()
  1172. run_task = conn.execute(
  1173. "select id from tasks where goal_id = ? and kind = 'goal_run'",
  1174. (stall_goal,)).fetchone()[0]
  1175. conn.close()
  1176. check(wait_for(lambda: task_state(auth, run_task)["status"] == "suspended"
  1177. or False, timeout=60, what="agent parks for force wake") is not None,
  1178. "the goal agent parks before the force-wake scenario")
  1179. # The stall fixture: an agent-created draft that was never enqueued
  1180. # (typo'd enqueue) — statuses must name it as NOT enqueued.
  1181. conn = db()
  1182. agent_session = conn.execute(
  1183. "select max(id) from sessions where task_id = ?", (run_task,)).fetchone()[0]
  1184. conn.close()
  1185. status, body = saddle_api("POST", "/api/saddle/task/create", fake_token, {
  1186. "session_id": agent_session, "title": "QA sweep",
  1187. "mandate": "Run the QA sweep.", "mode": "test"})
  1188. check(status == 200 and body.get("ok") is True,
  1189. "the stall fixture draft task creates (%s)" % body.get("error"))
  1190. stall_draft = int(body["task_id"])
  1191. goal_page = http_get_page(auth, "/goals/%d" % stall_goal)
  1192. check('stm-action="goal.force_wake"' in goal_page,
  1193. "the goal page authors the Force wake button while the run is parked")
  1194. check('stm-if="goal.force_wake != null"' in goal_page,
  1195. "the Force wake button is gated on the action being offered")
  1196. # No wake without the press: parked stays parked.
  1197. parked_runs = len(fake.goal_assignments)
  1198. check(quiet_window(quiet_seconds(),
  1199. lambda: len(fake.goal_assignments) == parked_runs,
  1200. "no press, no wake"),
  1201. "without a press the parked agent never wakes")
  1202. state = goal_state(stall_goal)
  1203. check(state.get("force_wake") is not None,
  1204. "the goal state exposes the offered force-wake action")
  1205. act(auth, state["force_wake"], {})
  1206. check(wait_for(lambda: len(fake.goal_assignments) > parked_runs or False,
  1207. timeout=30, what="force-woke agent resumes") is not None,
  1208. "the force-woke agent resumes immediately (no coalesce wait)")
  1209. woke_notice = fake.goal_assignments[-1].get("resume_notice") or ""
  1210. check("force-woke this goal's supervision" in woke_notice
  1211. and "no pending wake" in woke_notice,
  1212. "the force-wake resume leads with the explicit wake line")
  1213. check(("draft (NOT enqueued" in woke_notice)
  1214. and ("saddle task enqueue %d" % stall_draft) in woke_notice,
  1215. "the statuses list names the never-enqueued draft with the enqueue fix")
  1216. check("Task statuses" in woke_notice,
  1217. "the force-wake notice still carries the standard batched report")
  1218. # The flag is consumed: re-park (script), no second wake fires.
  1219. check(wait_for(lambda: task_state(auth, run_task)["status"] == "suspended"
  1220. or False, timeout=60, what="woken agent re-parks") is not None,
  1221. "the woken agent parks again (script)")
  1222. woke_runs = len(fake.goal_assignments)
  1223. check(quiet_window(quiet_seconds(),
  1224. lambda: len(fake.goal_assignments) == woke_runs,
  1225. "consumed flag never re-fires"),
  1226. "the consumed flag does not re-wake (one wake per press)")
  1227. # Guards: a paused goal refuses the wake (Resume is its lever).
  1228. state = goal_state(stall_goal)
  1229. act(auth, state["pause"], {})
  1230. conn = db()
  1231. paused_flag = conn.execute("select force_wake from goals where id = ?",
  1232. (stall_goal,)).fetchone()
  1233. conn.close()
  1234. check(paused_flag is not None and paused_flag[0] in (0, False),
  1235. "pausing does not set the flag")
  1236. state = goal_state(stall_goal)
  1237. check(state.get("force_wake") is None,
  1238. "a paused goal does not author the Force wake action")
  1239. act(auth, state["resume"], {})
  1240. check(wait_for(lambda: goal_state(stall_goal)["status"] == "active" or False,
  1241. timeout=30, what="goal reactivates") is not None,
  1242. "the goal reactivates for the guard checks")
  1243. # Non-suspended run refuses: run is parked again (script parked it
  1244. # above), wake it, let it park, then a second press while parked is
  1245. # fine — the 400 path needs a NON-suspended run. Start there: wake
  1246. # once more and press while it is working.
  1247. state = goal_state(stall_goal)
  1248. act(auth, state["force_wake"], {})
  1249. check(wait_for(lambda: len(fake.goal_assignments) > woke_runs or False,
  1250. timeout=30, what="second wake fires") is not None,
  1251. "a second press wakes again (re-press is allowed)")
  1252. # While it is mid-script (not yet parked), the action is hidden.
  1253. check(goal_state(stall_goal).get("force_wake") is None
  1254. or task_state(auth, run_task)["status"] != "suspended",
  1255. "the wake action hides while the run is not parked")
  1256. check(wait_for(lambda: task_state(auth, run_task)["status"] == "suspended"
  1257. or False, timeout=60, what="run parks for cleanup") is not None,
  1258. "the run parks again")