goalui.py 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713
  1. """The goalui 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. def run(ctx):
  5. """Plan 14 workstream B + plan 15 C1: the goal supervision surface.
  6. B1 — goal-run control parity (Retry supervision / Requeue-to-resume
  7. on the status head, the /tasks/<goal-run-id> redirect, no
  8. notify-me toggle), B2 — the plan-review remarks queue, B4 —
  9. sessions.resume_notice + the terminal dropdown/resume panel, B5 —
  10. page order, effective rules and per-task capability badges, B6 —
  11. narrative footnote weights. C1 — the single-row block world: the
  12. goal's blocks ARE the run task's rows (answered from every
  13. surface through one path; no dual rows, no stragglers, at most
  14. one review-kind block per verdict path). C4 — the goal page
  15. batch: one muted head activity line, plan between attention and
  16. tasks, answered blocks behind an expander, footnote comments.
  17. C5 — the dashboard deep-link closure: goal pile cards link the
  18. goal page, every surface settles the single row, and no
  19. goal-side store exists to hold stragglers.
  20. """
  21. auth, fake, fake_id, fake_token = ctx.auth, ctx.fake, ctx.fake_id, ctx.fake_token
  22. def run_task_of(goal_id):
  23. conn = db()
  24. try:
  25. return int(conn.execute(
  26. "select id from tasks where goal_id = ? and kind = 'goal_run'",
  27. (goal_id,)).fetchone()[0])
  28. finally:
  29. conn.close()
  30. def wait_run(status, timeout=60, what="run status"):
  31. return wait_for(lambda: task_state(auth, run_task_of(goal))["status"] == status
  32. or False, timeout=timeout, what=what)
  33. def pile_questions():
  34. return [b.get("question") for b in
  35. slot_public(auth, "/", "dashboard")["pinned"]
  36. if b.get("item_type") == "goal"]
  37. def open_session(task_id):
  38. conn = db()
  39. try:
  40. row = conn.execute(
  41. "select id from sessions where task_id = ? and ended_at is null",
  42. (task_id,)).fetchone()
  43. return int(row[0]) if row else None
  44. finally:
  45. conn.close()
  46. print("== B1: the goal-run task page redirects to the goal ==")
  47. fake.goal_script = [("plan", "# Remarks plan\n\n## Steps\n\n1. First move")]
  48. act(auth, slot_public(auth, "/goals", "goals")["create"], {
  49. "project_id": "1", "title": "Goalui goal",
  50. "brief": "Drive the plan-14 goal surface end to end.",
  51. "branch_name": "goal/goalui"})
  52. conn = db()
  53. goal = conn.execute("select id from goals where title = 'Goalui goal'"
  54. ).fetchone()[0]
  55. conn.close()
  56. act(auth, goal_state(goal)["start"], {})
  57. wait_run("blocked", what="goalui goal agent parks on its plan") # plan review parks blocked
  58. run_task = run_task_of(goal)
  59. frames = directives("/tasks/%d" % run_task, auth)
  60. navigations = [d for d in frames if d.get("type") == "navigate"]
  61. check(any(d.get("uri") == "/goals/%d" % goal for d in navigations),
  62. "the goal-run task page redirects to /goals/<id> (navigate directive)")
  63. check(slot_public(auth, "/tasks/%d" % run_task, "task")["id"] == run_task,
  64. "the redirect still hydrates the task slot (sealed refresh + API readers)")
  65. status, page = http("GET", "/tasks/%d" % run_task, headers={
  66. "X-Statum-Slot": "key=%s; as_at=%s" % (auth["key"], auth["as_at"])}, timeout=15)
  67. check(status == 200, "the compiled goal-run page serves")
  68. act(auth, slot_public(auth, "/tasks", "tasks")["create"], {
  69. "project_id": "1", "title": "Goalui worker", "priority": "4",
  70. "description": "A plain worker under no goal."})
  71. conn = db()
  72. worker = conn.execute("select id from tasks where title = 'Goalui worker'"
  73. ).fetchone()[0]
  74. conn.close()
  75. check(not [d for d in directives("/tasks/%d" % worker, auth)
  76. if d.get("type") == "navigate"],
  77. "worker task pages render normally (no redirect)")
  78. state = goal_state(goal)
  79. check("toggle_notify" not in state and state.get("notify") is None,
  80. "no notify-me toggle on the goal (goals notify unconditionally)")
  81. goal_page = http_get_page(auth, "/goals/%d" % goal)
  82. check("Notify me" not in goal_page and "toggle-notify" not in goal_page,
  83. "the goal page template ships no notify control")
  84. check(state["branch_name"] == "goal/goalui" and state["base_branch"] == "main"
  85. and isinstance(state["push_branches"], list)
  86. and state["priority"] == 3
  87. and len(state["started_display"]) > 0
  88. and len(state["agent_saddle"]) > 0,
  89. "the status head carries the full field set (branch pair, chips list,"
  90. " priority, timestamps, agent saddle)")
  91. check("Retry supervision" in goal_page and "Requeue to resume" in goal_page,
  92. "the status head ships the run-control parity buttons")
  93. check("status-corner" not in goal_page and "goal complete" not in goal_page
  94. and goal_page.count("badge-yeehaw") == 1,
  95. "yeehaw is rows-only (no goal-head corner badge; the row badge remains)")
  96. check(goal_state(goal)["current_activity"] == "parked awaiting the plan review",
  97. "the head line reads 'parked awaiting the plan review' while one is open")
  98. goal_page = http_get_page(auth, "/goals/%d" % goal)
  99. check(goal_page.index("Goal agent blocks") < goal_page.index("<h3>Plan</h3>")
  100. < goal_page.index("<h3>Tasks</h3>"),
  101. "the plan section sits between attention and tasks")
  102. check("data-plan-review" in goal_page and "Approved plan &amp; revisions" in goal_page,
  103. "an open review renders expanded beside the collapsed approved-plan expander")
  104. print("== B2: the plan-review remarks queue ==")
  105. check(goal_state(goal)["status"] == "plan_review"
  106. and goal_state(goal)["plan_pending_block"] is not None,
  107. "the goal sits in plan review with a pending block")
  108. goal_page = http_get_page(auth, "/goals/%d" % goal)
  109. review_at = goal_page.index("data-plan-review")
  110. check(review_at > 0, "the plan review region is marked for the remarks queue")
  111. check(goal_page.index('data-block-id="goal.plan_pending_block.id"') > 0,
  112. "the queue keys on the pending goal block id")
  113. body_at = goal_page.index('data-plan-body')
  114. remarks_at = goal_page.index("data-plan-remarks")
  115. check(body_at < remarks_at,
  116. "the remarks queue renders at the bottom of the plan body")
  117. for marker in ("data-plan-accept", "data-plan-toggle", "data-plan-submit",
  118. "data-plan-remove", "plan-popover", "remarksByBlock"):
  119. check(marker in goal_page, "the remarks queue ships %s" % marker)
  120. check("(index + 1) + '. '" in goal_page,
  121. "the rejection payload numbers every queued remark")
  122. check("rejectOpenByBlock" in goal_page and "statum.afterEvaluate(syncPlanRemarks)" in goal_page,
  123. "the queue survives pulse refreshes (maps outside statum re-renders)")
  124. # The rejection payload exactly as the client composes it: the
  125. # stable verdict wording, then each remark NUMBERED in queue order
  126. # with its quote prefixed like a comment.
  127. payload = ("Plan feedback received:\n\n"
  128. "> ## Steps\n\n1. First move\n\n"
  129. "1. Add a rollback step\n\n"
  130. "2. Name the owner of the migration")
  131. act(auth, goal_state(goal)["plan_pending_block"]["answer_action"],
  132. {"answer": payload})
  133. state = goal_state(goal)
  134. check(state["status"] == "planning",
  135. "the remarks rejection returns the agent to planning")
  136. rejected = [r for r in state["plan_revisions"] if r.get("answered")]
  137. check(any(r["answer"] == payload for r in rejected),
  138. "the rejection carries the remarks verbatim, in order")
  139. check(payload.index("1. Add a rollback step") < payload.index("2. Name the owner"),
  140. "the numbering is queue-ordered")
  141. print("== B4: resume_notice lands on the resumed session row ==")
  142. fake.goal_script = [("question", "Which datastore?", ["SQLite", "Redis"])]
  143. check(wait_for(lambda: goal_state(goal)["status"] == "planning" or False,
  144. timeout=30, what="rejection observed") is not None,
  145. "the agent revises")
  146. # The parked plan agent resumes with the verdict; the next round
  147. # raises the scripted question.
  148. check(wait_for(lambda: any(b["question"] == "Which datastore?"
  149. and not b.get("answered")
  150. for b in goal_state(goal)["blocks"]) or False,
  151. timeout=60, what="question raised") is not None,
  152. "the resumed agent raises its question")
  153. conn = db()
  154. notices = dict(conn.execute(
  155. "select id, resume_notice from sessions where task_id = ?", (run_task,)
  156. ).fetchall())
  157. conn.close()
  158. resumed_ids = [i for i, text in notices.items() if text]
  159. fresh_ids = [i for i, text in notices.items() if not text]
  160. check(len(resumed_ids) >= 1 and any("Plan feedback received" in notices[i]
  161. for i in resumed_ids),
  162. "the resumed session stores the notice it was allocated")
  163. check(min(notices) in fresh_ids,
  164. "session 1 (the fresh opening round) carries no resume notice")
  165. print("== C1: the single run-task row IS the goal's block ==")
  166. check("Which datastore?" in pile_questions(),
  167. "the open goal question pins onto the dashboard pile")
  168. goal_rows = [b for b in goal_state(goal)["blocks"]
  169. if b["question"] == "Which datastore?"]
  170. run_rows = [b for b in task_state(auth, run_task)["blocks"]
  171. if b["question"] == "Which datastore?"]
  172. check(len(run_rows) == 1 and len(goal_rows) == 1
  173. and int(run_rows[0]["id"]) == int(goal_rows[0]["id"]),
  174. "the goal page and the run task read the SAME single row")
  175. pile_card = [b for b in slot_public(auth, "/", "dashboard")["pinned"]
  176. if b.get("question") == "Which datastore?"][0]
  177. check(pile_card.get("item_type") == "goal"
  178. and int(pile_card.get("goal_id") or 0) == goal
  179. and int(pile_card.get("id") or 0) == int(run_rows[0]["id"])
  180. and pile_card.get("goal_title") == "Goalui goal",
  181. "the pile card is the run-task row styled as a goal item")
  182. act(auth, [b for b in goal_state(goal)["blocks"]
  183. if b["question"] == "Which datastore?" and not b.get("answered")]
  184. [0]["answer_action"], {"option": "SQLite"})
  185. conn = db()
  186. unanswered = conn.execute(
  187. "select count(*) from task_blocks where question = 'Which datastore?'"
  188. " and answer is null").fetchone()[0]
  189. conn.close()
  190. check(unanswered == 0, "answering from the goal page closes the single row")
  191. check("Which datastore?" not in pile_questions(),
  192. "the answered row no longer pins the dashboard pile")
  193. # The task-page deep link (the AnswerBlockAction API surface)
  194. # answers the SAME row through the one goal path — there is no
  195. # second row to sweep and no stale copy can survive.
  196. conn = db()
  197. conn.execute("insert into task_blocks (task_id, type, question, created_at)"
  198. " values (?, 'question', 'Deep link probe?',"
  199. " strftime('%s','now'))", (run_task,))
  200. conn.commit()
  201. conn.close()
  202. check("Deep link probe?" in pile_questions(),
  203. "the fixture row pins like any goal question")
  204. deep_block = [b for b in task_state(auth, run_task)["blocks"]
  205. if b["question"] == "Deep link probe?" and not b.get("answered")][0]
  206. act(auth, deep_block["answer_action"], {"answer": "Answered via the deep link"})
  207. conn = db()
  208. answered_row = conn.execute(
  209. "select answer from task_blocks where question = 'Deep link probe?'"
  210. ).fetchone()[0]
  211. tables = {r[0] for r in conn.execute(
  212. "select name from sqlite_master where type = 'table'").fetchall()}
  213. conn.close()
  214. check(answered_row == "Answered via the deep link",
  215. "the task-page deep link answers the same single row (the API path)")
  216. check("Deep link probe?" not in pile_questions(),
  217. "the deep-link answer closes the pile card too")
  218. check("goal_blocks" not in tables,
  219. "the goal_blocks table is gone (run-task blocks are canonical)")
  220. print("== C5: deep-link closure on the single-row world ==")
  221. # The pile card's goal item deep-links the goal page (the answer
  222. # surface); answering from either surface settled THE row above.
  223. home_markup = http_get_page(auth, "/")
  224. check("'/goals/' + blockview.goal_id" in home_markup,
  225. "the pile card deep-links the goal page for goal items")
  226. conn = db()
  227. dupes = conn.execute(
  228. "select count(*) from (select question from task_blocks where task_id = ?"
  229. " group by question having count(*) > 1)", (run_task,)).fetchone()[0]
  230. stragglers = conn.execute(
  231. "select count(*) from task_blocks where task_id = ? and answer is null",
  232. (run_task,)).fetchone()[0]
  233. goal_side_tables = [r[0] for r in conn.execute(
  234. "select name from sqlite_master where type = 'table'"
  235. " and name in ('goal_blocks','goal_comments','goal_events')").fetchall()]
  236. conn.close()
  237. check(dupes == 0 and stragglers == 0,
  238. "one row per question on the run task, none left unanswered")
  239. check(not goal_side_tables,
  240. "no goal-side store exists — historical stragglers are impossible")
  241. print("== B4: the terminal session dropdown + resume-info panel ==")
  242. fake.goal_script = [("suspend",)]
  243. check(wait_for(lambda: task_state(auth, run_task)["status"] == "suspended"
  244. or False, timeout=60, what="agent parks suspended") is not None,
  245. "the agent parks suspended for the requeue scenario")
  246. terminal = slot_public(auth, "/tasks/%d/terminal" % run_task, "terminal")
  247. ids = [int(s["id"]) for s in terminal["sessions"]]
  248. check(ids == sorted(ids, reverse=True),
  249. "the session dropdown lists newest-first")
  250. check(int(terminal["session_id"]) == ids[0],
  251. "the terminal still defaults to the latest session")
  252. labels = {int(s["id"]): s["label"] for s in terminal["sessions"]}
  253. check(all(label.startswith("Session #") for label in labels.values()),
  254. "dropdown labels read 'Session #N — started <time>'")
  255. # Recompute at the check point: the C1 answers above each
  256. # resumed the agent too, so the earlier snapshot would go stale.
  257. conn = db()
  258. resumed_ids = [int(i) for (i, text) in conn.execute(
  259. "select id, resume_notice from sessions where task_id = ?",
  260. (run_task,)).fetchall() if text]
  261. conn.close()
  262. resumed_markers = [i for i in ids if "[resumed]" in labels[i]]
  263. check(set(resumed_markers) == set(resumed_ids),
  264. "the [resumed] marker rides exactly the notice-carrying sessions")
  265. notices_by_id = {int(s["id"]): s.get("notice") or "" for s in terminal["sessions"]}
  266. check("Opening brief" in notices_by_id[min(ids)]
  267. and "Drive the plan-14 goal surface" in notices_by_id[min(ids)],
  268. "session 1's panel shows the opening-brief framing (from the goal brief)")
  269. check(any("Plan feedback received" in (notices_by_id[i] or "") for i in resumed_ids),
  270. "a resumed session's panel shows the stored resume notice")
  271. terminal_page = http_get_page(auth, "/tasks/%d/terminal" % run_task)
  272. check('id="session-select"' in terminal_page
  273. and "window.terminalPickSession" in terminal_page,
  274. "the session picker is an options dropdown")
  275. check('stm-markdown="window.terminalSessionNotice(terminal)"' in terminal_page,
  276. "the panel renders the selected session's notice as markdown")
  277. check("No opening brief or resume notice recorded" in terminal_page,
  278. "the panel ships its graceful empty state")
  279. css = open(os.path.join(REPO, "src/pages/app.css")).read()
  280. check("resume-notice-panel" in css and "max-height: 200px" in css,
  281. "the panel is a 200px vertical-scroll strip (stylesheet)")
  282. print("== B1: requeue-to-resume from the goal head ==")
  283. conn = db()
  284. conn.execute("update saddles set status = 'offline' where id = ?", (fake_id,))
  285. conn.commit()
  286. conn.close()
  287. def requeue_offered():
  288. # Act on the SAME state that satisfied the wait: a second read
  289. # once raced the scripted daemon's ask reviving the saddle row,
  290. # and the conditionally-authored action vanished between the
  291. # two reads (KeyError on act).
  292. offered = goal_state(goal)
  293. return offered if offered.get("requeue_run") is not None else False
  294. state = wait_for(requeue_offered, timeout=30, what="requeue offered")
  295. check(state is not None,
  296. "a parked run on a dead saddle offers Requeue-to-resume")
  297. check(state.get("retry_run") is None,
  298. "an active goal offers no Retry supervision")
  299. open_before = open_session(run_task)
  300. def parked_session_ended():
  301. if open_before is None:
  302. return True
  303. conn = db()
  304. try:
  305. return conn.execute(
  306. "select ended_at is not null from sessions where id = ?",
  307. (open_before,)).fetchone()[0] == 1
  308. finally:
  309. conn.close()
  310. act(auth, state["requeue_run"], {})
  311. check(task_state(auth, run_task)["status"] == "queued",
  312. "the requeue returns the run to the queue")
  313. # The PARKED session itself must end (the restore-on-dispatch
  314. # successor may open immediately — the check keys the old row,
  315. # not the task's transient open-session window).
  316. check(wait_for(parked_session_ended, timeout=60,
  317. what="parked session closed") is not None,
  318. "the requeue closed the parked session (restore-on-dispatch)")
  319. conn = db()
  320. conn.execute("update saddles set status = 'idle' where id = ?", (fake_id,))
  321. conn.commit()
  322. conn.close()
  323. fake.goal_script = [("suspend",)]
  324. check(wait_for(lambda: task_state(auth, run_task)["status"] == "suspended"
  325. or False, timeout=60, what="requeued run re-dispatches") is not None,
  326. "the requeued run re-dispatches and re-parks")
  327. fresh_session = open_session(run_task)
  328. conn = db()
  329. fresh_notice = conn.execute(
  330. "select resume_notice from sessions where id = ?", (fresh_session,)
  331. ).fetchone()[0]
  332. conn.close()
  333. check(fresh_notice is None,
  334. "a requeue's fresh session carries no resume notice (empty panel state)")
  335. print("== B1: Retry supervision revives a failed goal ==")
  336. fake.push_results("failed", summary="the supervision collapsed (scripted)")
  337. check(wait_for(lambda: goal_state(goal)["status"] == "failed" or False,
  338. timeout=30, what="goal fails") is not None,
  339. "the run's failure fails the goal")
  340. check(wait_for(lambda: goal_state(goal).get("retry_run") is not None
  341. or False, timeout=30, what="retry offered") is not None,
  342. "a failed goal offers Retry supervision")
  343. fake.goal_script = [("suspend",)]
  344. act(auth, goal_state(goal)["retry_run"], {})
  345. state = goal_state(goal)
  346. check(state["status"] == "active",
  347. "the retry revives supervision (goal back to active)")
  348. check(task_state(auth, run_task)["status"] in ("queued", "assigned", "working",
  349. "suspended", "blocked"),
  350. "the retry re-offered the run task")
  351. check(wait_for(lambda: task_state(auth, run_task)["status"] == "suspended"
  352. or False, timeout=60, what="retried supervision parks") is not None,
  353. "the retried supervision dispatches and parks again")
  354. state = goal_state(goal)
  355. check(any(e["kind"] == "system" and "Supervision retried" in e["text"]
  356. for e in state["events"]),
  357. "the narrative records the supervision retry (a system row)")
  358. print("== B5 + C4: order, effective rules, capability badges ==")
  359. goal_page = http_get_page(auth, "/goals/%d" % goal)
  360. markers = [
  361. ('class="card goal-head"', "status head"),
  362. ("Agent progress", "progress"),
  363. ("Goal agent blocks", "attention blocks"),
  364. ("<h3>Plan</h3>", "plan"),
  365. ("<h3>Tasks</h3>", "tasks"),
  366. ("Initial brief", "brief"),
  367. ("Involved projects", "projects"),
  368. ("<h3>Rules</h3>", "rules"),
  369. ("Supervision narrative", "narrative"),
  370. ("<h3>Comments</h3>", "comments"),
  371. ("Goal agent runs", "runs"),
  372. ]
  373. positions = []
  374. for marker, _ in markers:
  375. check(marker in goal_page, "the goal page ships the %s section" % _)
  376. positions.append(goal_page.index(marker))
  377. check(positions == sorted(positions),
  378. "the page reads status → progress → attention → plan → tasks → brief →"
  379. " rules → narrative → comments → runs (in order)")
  380. check("goal.current_activity" in goal_page and "Current activity" not in goal_page,
  381. "the activity lives as ONE muted head line (the card is gone)")
  382. check(".goal-page .card" in open(os.path.join(REPO, "src/pages/app.css")).read(),
  383. "the stylesheet restores the goal page's card rhythm (padding regression)")
  384. rules_state = slot_public(auth, "/rules", "rules")
  385. act(auth, rules_state["create"], {"name": "goalui-primary", "body": "Primary rule"})
  386. act(auth, rules_state["create"], {"name": "goalui-codev", "body": "Co-dev rule"})
  387. act(auth, rules_state["create"], {"name": "goalui-ref", "body": "Reference rule"})
  388. conn = db()
  389. rule_ids = {name: int(conn.execute("select id from rules where name = ?",
  390. (name,)).fetchone()[0])
  391. for name in ("goalui-primary", "goalui-codev", "goalui-ref")}
  392. conn.execute("insert into rule_projects (rule_id, project_id, created_at)"
  393. " values (?, 1, strftime('%s','now'))", (rule_ids["goalui-primary"],))
  394. conn.execute("insert into projects (name, git_url, default_branch, created_at)"
  395. " values ('goalui-codev-project', 'https://example.com/g.git',"
  396. " 'main', strftime('%s','now'))")
  397. codev_project = conn.execute(
  398. "select id from projects where name = 'goalui-codev-project'").fetchone()[0]
  399. conn.execute("insert into projects (name, git_url, default_branch, created_at)"
  400. " values ('goalui-ref-project', 'https://example.com/r.git',"
  401. " 'main', strftime('%s','now'))")
  402. ref_project = conn.execute(
  403. "select id from projects where name = 'goalui-ref-project'").fetchone()[0]
  404. conn.execute("insert into rule_projects (rule_id, project_id, created_at)"
  405. " values (?, ?, strftime('%s','now'))",
  406. (rule_ids["goalui-codev"], codev_project))
  407. conn.execute("insert into rule_projects (rule_id, project_id, created_at)"
  408. " values (?, ?, strftime('%s','now'))",
  409. (rule_ids["goalui-ref"], ref_project))
  410. conn.commit()
  411. conn.close()
  412. act(auth, goal_state(goal)["add_link"], {"linked_project_id": str(codev_project),
  413. "relationship": "co-developed"})
  414. act(auth, goal_state(goal)["add_link"], {"linked_project_id": str(ref_project),
  415. "relationship": "reference"})
  416. rule_names = [r["name"] for r in goal_state(goal)["rules"]]
  417. check(rule_names == ["goalui-primary", "goalui-codev"],
  418. "the Rules section lists the goal's effective set (primary then"
  419. " co-developed; reference contributes nothing)")
  420. act(auth, slot_public(auth, "/saddles", "saddles")["create_capability"],
  421. {"name": "goalui-capability"})
  422. conn = db()
  423. capability = conn.execute(
  424. "select id from capabilities where name = 'goalui-capability'"
  425. ).fetchone()[0]
  426. # The early redirect-fixture worker joins the goal (a fanned
  427. # worker's shape) so its row carries the badge too.
  428. conn.execute("update tasks set goal_id = ? where id = ?", (goal, worker))
  429. conn.execute("insert into task_capabilities (task_id, capability_id)"
  430. " values (?, ?)", (worker, capability))
  431. conn.commit()
  432. conn.close()
  433. goal_worker = {"id": worker, "caps": None}
  434. act(auth, slot_public(auth, "/tasks", "tasks")["create"], {
  435. "project_id": "1", "title": "Goalui fanned worker", "priority": "4",
  436. "description": "A worker the agent fanned out."})
  437. conn = db()
  438. fanned = conn.execute(
  439. "select id from tasks where title = 'Goalui fanned worker'").fetchone()[0]
  440. conn.execute("update tasks set goal_id = ? where id = ?", (goal, fanned))
  441. conn.execute("insert into task_capabilities (task_id, capability_id)"
  442. " values (?, ?)", (fanned, capability))
  443. conn.commit()
  444. conn.close()
  445. rows = {int(t["id"]): t for t in goal_state(goal)["goal_tasks"]}
  446. check(rows[fanned].get("capabilities") == ["goalui-capability"]
  447. and rows[worker].get("capabilities") == ["goalui-capability"],
  448. "task rows carry their required capability tags")
  449. goal_page = http_get_page(auth, "/goals/%d" % goal)
  450. check('stm-for-capability-in="goal_task.capabilities"' in goal_page
  451. and "badge-capability" in goal_page,
  452. "the task rows render capability badges")
  453. print("== B6: narrative weights — events full, comments footnote ==")
  454. # An AGENT comment projects the narrative's comment event (human
  455. # comments only thread) — pushed through the entries channel while
  456. # parked, so the suspended state stays put.
  457. fake.push_results("suspended", entries=[{
  458. "id": 9001, "command": "saddle comment", "output": "An agent aside.",
  459. "exit_code": 0, "type": "comment", "timestamp": "2026-09-12T00:00:00Z"}])
  460. check(wait_for(lambda: any(e["kind"] == "comment"
  461. for e in goal_state(goal)["events"]) or False,
  462. timeout=15, what="comment event lands") is not None,
  463. "an agent comment lands as a narrative event")
  464. goal_page = http_get_page(auth, "/goals/%d" % goal)
  465. check("stm-attribute.class=\"'goal-event' + (event.kind == 'comment'"
  466. " ? ' footnote' : '')\"" in goal_page.replace("\n", " ").replace(" ", " ")
  467. or "'goal-event' + (event.kind == 'comment' ? ' footnote' : '')" in goal_page,
  468. "the WHOLE comment event row carries .footnote (badge included)")
  469. check("'badge badge-goal-' + event.kind" in goal_page,
  470. "non-comment events keep their full-weight kind badges")
  471. css = open(os.path.join(REPO, "src/pages/app.css")).read()
  472. check(".goal-event.footnote" in css and ".footnote {" in css,
  473. "the stylesheet ships the footnote row weight beside the utility")
  474. print("== C2: system rows render distinctly on the ONE thread ==")
  475. run_comments = task_state(auth, run_task)["comments"]
  476. system_rows = [c for c in run_comments if c.get("is_system")]
  477. check(system_rows and all(c["author"] == "system" for c in system_rows)
  478. and not any(c.get("from_agent") for c in system_rows),
  479. "the harness bookkeeping rows are authored 'system' (never agent)")
  480. goal_thread = goal_state(goal)["comments"]
  481. check(any(c.get("is_system") for c in goal_thread),
  482. "the goal page reads the same thread (the system rows included)")
  483. css = open(os.path.join(REPO, "src/pages/app.css")).read()
  484. check(".comment.system" in css and ".system-label" in css,
  485. "the stylesheet ships the distinct system-event styling")
  486. page = http_get_page(auth, "/goals/%d" % goal)
  487. check("comment.is_system" in page and "system-label" in page,
  488. "the goal page renders system rows with the labelled muted style")
  489. task_page = http_get_page(auth, "/tasks/%d" % worker)
  490. check("comment.is_system" in task_page and "system-label" in task_page,
  491. "the task page renders system rows with the same style")
  492. print("== S5 U2: mechanical rows filtered from user-facing timelines ==")
  493. # The daemon's mechanical system entries (the park notice, the
  494. # restore footnote) ride the wire typed 'system': the raw terminal
  495. # row STORES (session_commands), but no comment row, narrative
  496. # event or dashboard timeline row is authored for them.
  497. open_run = open_session(run_task)
  498. mechanical = [
  499. {"id": 9501, "command": "saddle suspend",
  500. "output": "goal agent parked until an owned task completes or"
  501. " fails; resume via /start resume_notice",
  502. "exit_code": 0, "type": "system", "timestamp": "2026-09-12T00:00:00Z"},
  503. {"id": 9502, "command": "harness resume",
  504. "output": "[harness] session context restored — task resumed",
  505. "exit_code": 0, "type": "system", "timestamp": "2026-09-12T00:00:01Z"},
  506. ]
  507. status, _ = saddle_api("POST", "/api/saddle/results", fake_token,
  508. {"session_id": open_run, "state": "suspended",
  509. "entries": mechanical})
  510. check(status == 200, "the mechanical system entries push cleanly")
  511. conn = db()
  512. raw_rows = conn.execute(
  513. "select count(*) from session_commands where session_id = ?"
  514. " and type = 'system'", (open_run,)).fetchone()[0]
  515. conn.close()
  516. check(raw_rows >= 2,
  517. "the raw terminal keeps the mechanical rows (typed entries store)")
  518. check(all("parked until" not in c["body"]
  519. and "session context restored" not in c["body"]
  520. for c in task_state(auth, run_task)["comments"]),
  521. "no mechanical comment row lands on the thread (U2)")
  522. check(all("parked until" not in e["text"]
  523. and "session context restored" not in e["text"]
  524. for e in goal_state(goal)["events"]),
  525. "no mechanical narrative event lands on the goal page (U2)")
  526. dash_events = slot_public(auth, "/", "dashboard")["activity"]
  527. check(all("parked until" not in e["text"]
  528. and "session context restored" not in e["text"]
  529. for e in dash_events),
  530. "no mechanical row lands on the dashboard timeline (U2)")
  531. print("== C4: head line, answered-blocks expander, footnote comments ==")
  532. state = goal_state(goal)
  533. check(state["current_activity"] == "parked awaiting its tasks' terminal transitions",
  534. "a suspended run folds into the one muted head line")
  535. check(len(state.get("answered_blocks") or []) >= 2
  536. and len(state.get("open_blocks") or []) == 0,
  537. "the historic answered blocks expose as their own vector (open ones empty)")
  538. goal_page = http_get_page(auth, "/goals/%d" % goal)
  539. check("Answered blocks (" in goal_page and "comment-meta" in goal_page,
  540. "the answered blocks and the footnote comment meta ship in the markup")
  541. check("timeless note for a future reader" in goal_page,
  542. "the composer hint states the timeless-comment rule")
  543. css = open(os.path.join(REPO, "src/pages/app.css")).read()
  544. check(".goal-page .card { margin-bottom" in css and ".comment-meta" in css,
  545. "the stylesheet ships the card rhythm and the comment footnote meta")
  546. print("== C1: one review block per verdict path (F6) ==")
  547. # The daemon's honest round pushes the ENTRY and the STATE
  548. # together — the done pair must raise exactly ONE completion
  549. # block, and a redelivered pair nothing more.
  550. fake.goal_script = [("done", "# Completion\n\nOne block only.")]
  551. conn = db()
  552. conn.execute("insert into tasks (project_id, goal_id, kind, title, status,"
  553. " priority, completed_at, created_at) values (1, ?, 'task',"
  554. " 'Warmup', 'done', 5,"
  555. " strftime('%s','now'),"
  556. " strftime('%s','now','-30 seconds'))", (goal,))
  557. conn.commit()
  558. conn.close()
  559. check(wait_for(lambda: goal_state(goal)["status"] == "completion_review" or False,
  560. timeout=60, what="goalui completion review opens") is not None,
  561. "the scripted done round enters completion review")
  562. def completion_block_count():
  563. conn = db()
  564. try:
  565. return conn.execute(
  566. "select count(*) from task_blocks where task_id = ?"
  567. " and type = 'completion'", (run_task,)).fetchone()[0]
  568. finally:
  569. conn.close()
  570. check(completion_block_count() == 1,
  571. "the done state raises exactly ONE completion block")
  572. # Plan 16 S5 (U1): the verdict-shaped block renders the rich review
  573. # form — explicit accept/reject affordances with the free-text
  574. # feedback path, never a free-text-only verdict.
  575. goal_page = http_get_page(auth, "/goals/%d" % goal)
  576. check("Accept goal" in goal_page and "Reject with feedback" in goal_page
  577. and 'name="feedback"' in goal_page,
  578. "the completion review presents explicit accept/reject + free-text feedback (U1)")
  579. # The burst shape: the done ENTRY and the done STATE in one push
  580. # (fresh entry ids), then the whole pair redelivered — the
  581. # pending-report idempotency leaves the one row alone.
  582. conn = db()
  583. burst_session = conn.execute(
  584. "select id from sessions where task_id = ? and ended_at is null",
  585. (run_task,)).fetchone()[0]
  586. conn.close()
  587. done_pair = [{"id": 9401, "command": "saddle done",
  588. "output": "# Completion\n\nOne block only.", "exit_code": 0,
  589. "type": "done", "timestamp": "2026-09-12T00:00:00Z"}]
  590. status, _ = saddle_api("POST", "/api/saddle/results", fake_token,
  591. {"session_id": burst_session, "state": "done",
  592. "entries": done_pair,
  593. "summary": "# Completion\n\nOne block only."})
  594. check(status == 200 and completion_block_count() == 1,
  595. "the done entry + state pair raises nothing more (one block, ever)")
  596. state = goal_state(goal)
  597. act(auth, state["completion_pending_block"]["answer_action"],
  598. {"verdict": "accept"})
  599. check(wait_for(lambda: goal_state(goal)["status"] == "complete" or False,
  600. timeout=30, what="goalui goal completes") is not None,
  601. "accepting the completion completes the goal")
  602. check(completion_block_count() == 1,
  603. "the accept path leaves exactly one review-kind block behind")
  604. conn = db()
  605. stragglers = conn.execute(
  606. "select count(*) from task_blocks where task_id = ? and answer is null",
  607. (run_task,)).fetchone()[0]
  608. conn.close()
  609. check(stragglers == 0,
  610. "no unanswered straggler rows survive the completion")
  611. print("== S2: server-owned verdicts + the container-resolved reject ==")
  612. # A fresh goal drives the verdict paths the goal page's forms now
  613. # use: verdict markers for accept, raw `feedback` for reject, and
  614. # the block resolution living in the review CONTAINER (not
  615. # goal.plan_pending_block — the diagnosed completion-review bug).
  616. fake.goal_script = [("plan", "# S2 plan\n\nOne step")]
  617. act(auth, slot_public(auth, "/goals", "goals")["create"], {
  618. "project_id": "1", "title": "S2 verdict goal",
  619. "brief": "Covers the plan-16 S2 verdict paths.",
  620. "branch_name": "goal/s2-verdicts"})
  621. conn = db()
  622. s2_goal = int(conn.execute(
  623. "select id from goals where title = 'S2 verdict goal'").fetchone()[0])
  624. conn.close()
  625. act(auth, goal_state(s2_goal)["start"], {})
  626. check(wait_for(lambda: goal_state(s2_goal)["status"] == "plan_review" or False,
  627. timeout=90, what="S2 goal enters plan review") is not None,
  628. "the S2 goal enters plan review")
  629. s2_state = goal_state(s2_goal)
  630. s2_pending = s2_state["plan_pending_block"]
  631. # (1) A remark-less, text-less rejection is refused: the action
  632. # errors and the block keeps waiting.
  633. refusal = act(auth, s2_pending["answer_action"], {"feedback": " "})
  634. check(any(d.get("type") == "notify" and d.get("kind") == "error"
  635. and "answer is required" in (d.get("message") or "").lower()
  636. for d in refusal),
  637. "a remark-less, text-less rejection is refused with an error")
  638. conn = db()
  639. s2_still_open = conn.execute(
  640. "select answer from task_blocks where id = ?",
  641. (s2_pending["id"],)).fetchone()[0]
  642. conn.close()
  643. check(s2_still_open is None,
  644. "the refused rejection leaves the block unanswered")
  645. # (3) The goal page template ships no verdict constants and its
  646. # script resolves the review block from ANY [data-plan-review]
  647. # container via the open-blocks list (the completion-review fix).
  648. # Fetched while the review is open so the accept form renders.
  649. s2_page = http_get_page(auth, "/goals/%d" % s2_goal)
  650. check("Plan approved. Proceed with implementation." not in s2_page
  651. and "Review approved. Continue with the task." not in s2_page,
  652. "the goal page ships no verdict constants (server-owned wording)")
  653. check('name="verdict" value="accept"' in s2_page,
  654. "the goal page's accept forms post the verdict marker")
  655. check("resolveReviewBlock" in s2_page and "goal.open_blocks" in s2_page,
  656. "the reject flow resolves its block from the open-blocks list (container-keyed)")
  657. check("statum.act(block.answer_action, { feedback: feedback })" in s2_page,
  658. "the reject flow posts raw feedback (the server wraps the prefix)")
  659. check("extraText.length === 0" in s2_page,
  660. "a remark-less rejection with textarea content is allowed (S2)")
  661. # (2) The goal page's reject rides `feedback` (the client composes
  662. # the numbered-remarks body) and the SERVER wraps it with the plan
  663. # prefix — byte-identical to the historical client-built answer.
  664. act(auth, s2_pending["answer_action"],
  665. {"feedback": "> ## One step\n\n1. Tighten it\n\n2. Name the owner"})
  666. conn = db()
  667. s2_wrapped = conn.execute(
  668. "select answer from task_blocks where id = ?",
  669. (s2_pending["id"],)).fetchone()[0]
  670. conn.close()
  671. check(s2_wrapped
  672. == "Plan feedback received:\n\n"
  673. "> ## One step\n\n1. Tighten it\n\n2. Name the owner",
  674. "the plan rejection wraps the client-shaped body with the stable prefix (%r)"
  675. % s2_wrapped)
  676. check(goal_state(s2_goal)["status"] == "planning",
  677. "the feedback rejection returns the agent to planning")