| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 |
- """The lineage bundle (moved verbatim from the pre-split web_stack_test.py;
- see test/harness.py for the shared machinery and the runner)."""
- from harness import * # noqa: F401,F403 — the shared wire helpers
- # =============================================================================
- # Bundle: lineage — the plan-12 goal lineage: goal context on worker
- # dispatches (read-only reference + ephemeral branch), the daemon's
- # task-reading API (goal members, no parent/plan), and the goal
- # agent's workspace affinity while parked.
- # =============================================================================
- def run(ctx):
- auth, fake, fake_id, fake_token = ctx.auth, ctx.fake, ctx.fake_id, ctx.fake_token
- app = ctx.app
- r4_token = fake_token
- # gpu exists only as a hold (no saddle carries it): the held goal
- # worker stays queued until the release loop lifts it.
- saddles_state = slot_public(auth, "/saddles", "saddles")
- act(auth, saddles_state["create_capability"], {"name": "gpu"})
- def create_goal_row(fields):
- act(auth, slot_public(auth, "/goals", "goals")["create"], fields)
- rows = [g for g in slot_public(auth, "/goals", "goals")["goals"]
- if g["title"] == fields["title"]]
- return int(rows[0]["id"])
- goal = create_goal_row({"project_id": "1", "title": "Lineage goal",
- "brief": "Carry the goal lineage.",
- "branch_name": "goal/lineage"})
- fake.goal_script = [("plan", "# Lineage plan\n\n1. Fan out\n2. Verify")]
- act(auth, goal_state(goal)["start"], {})
- check(wait_for(lambda: goal_state(goal)["status"] == "plan_review" or False,
- timeout=60, what="lineage goal plans") is not None,
- "the lineage goal reaches plan review")
- fake.goal_script = [("suspend",)]
- act(auth, goal_state(goal)["plan_pending_block"]["answer_action"],
- {"answer": "Plan approved. Proceed with implementation."})
- conn = db()
- auto_parent = conn.execute(
- "select id from tasks where goal_id = ? and kind = 'goal_run'",
- (goal,)).fetchone()[0]
- conn.close()
- check(wait_for(lambda: task_state(auth, auto_parent)["status"] == "suspended"
- or False, timeout=60, what="lineage goal agent parks") is not None,
- "the lineage goal agent parks")
- conn = db()
- agent_session = conn.execute(
- "select max(id) from sessions where task_id = ?",
- (auto_parent,)).fetchone()[0]
- conn.close()
- # Two workers under the goal: one free, one held by the gpu tag.
- status, body = saddle_api("POST", "/api/saddle/task/create", r4_token, {
- "session_id": agent_session, "title": "Auto child",
- "mandate": "build the widget", "mode": "implement"})
- auto_child = int(body["task_id"])
- status, body = saddle_api("POST", "/api/saddle/task/create", r4_token, {
- "session_id": agent_session, "title": "Auto grandchild",
- "mandate": "verify the widget", "mode": "test",
- "capabilities": ["gpu"]})
- auto_grand = int(body["task_id"])
- status, body = saddle_api("POST", "/api/saddle/task/create", r4_token, {
- "session_id": agent_session, "title": "Wire the flange",
- "description": "torque to spec"})
- check(status == 400 and "mandate is required" in (body.get("error") or ""),
- "task create without a mandate errors verbatim (%s)" % body.get("error"))
- status, body = saddle_api("POST", "/api/saddle/task/create", r4_token, {
- "session_id": agent_session, "title": "Wire the flange",
- "mandate": "torque to spec"})
- flange_task = int(body["task_id"])
- for held in (auto_child, auto_grand, flange_task):
- saddle_api("POST", "/api/saddle/task/enqueue", r4_token,
- {"session_id": agent_session, "task_id": held})
- check(wait_for(lambda: task_state(auth, auto_child)["status"] in
- ("queued", "assigned", "working", "done") or False,
- timeout=30, what="free goal worker dispatches") is not None,
- "the free goal worker dispatches")
- check(quiet_window(3, lambda: task_state(auth, auto_grand)["status"] == "queued",
- "held goal worker stays queued"),
- "the gpu-held goal worker stays queued")
- # A goal worker's dispatch carries the read-only goal context.
- lineage_push = [p for p in fake.start_payloads
- if int(p.get("task", {}).get("id") or 0) == auto_child][-1]
- goal_ctx = lineage_push.get("goal") or {}
- check(int(goal_ctx.get("id") or 0) == goal
- and goal_ctx.get("branch") == "goal/lineage"
- and "Carry the goal lineage." in goal_ctx.get("brief", "")
- and "# Lineage plan" in goal_ctx.get("plan", ""),
- "the worker dispatch carries the goal context (brief + approved plan)")
- check(lineage_push.get("ephemeral_branch") == "task/%d-%d" % (goal, auto_child)
- and lineage_push.get("kind") == "task"
- and lineage_push.get("mode") == "implement"
- and lineage_push.get("creator") == "goal",
- "the worker dispatch carries the ephemeral branch + identity triplet")
- check("parent" not in lineage_push and "subtasks" not in lineage_push,
- "the plan-12 payload drops the parent/subtasks members")
- print("== R5 B2: task-reading API ==")
- status, _ = saddle_api("GET", "/api/saddle/tasks", None)
- check(status == 401, "tasks endpoint rejects a missing token")
- status, _ = saddle_api("GET", "/api/saddle/task/%d" % auto_child, None)
- check(status == 401, "task detail endpoint rejects a missing token")
- # Fixture: a second project whose tasks all carry the "Diff " title
- # prefix for the project_id filter checks — created first so the
- # unfiltered list has the population the count check expects.
- state = slot_public(auth, "/projects", "projects")
- act(auth, state["create"], {"name": "diff-fabrica",
- "git_url": "ssh://fabrica.unitatem.net:222/clanker/clanker-saddle.git"})
- conn = db()
- other_project = int([p["id"] for p in slot_public(auth, "/projects", "projects")["projects"]
- if p["name"] == "diff-fabrica"][0])
- for suffix in ("fabrica", "scp"):
- conn.execute("insert into tasks (project_id, title, status, priority, created_at)"
- " values (?, ?, 'done', 5, strftime('%s','now'))",
- (other_project, "Diff %s" % suffix))
- conn.commit()
- conn.close()
- status, body = saddle_api("GET", "/api/saddle/tasks", r4_token)
- check(status == 200 and len(body.get("tasks", [])) > 5,
- "tasks endpoint lists tasks without filters")
- row = [t for t in body["tasks"] if int(t["id"]) == auto_child][0]
- check(row["title"] == "Auto child" and int(row["goal_id"] or 0) == goal
- and row["status"] in ("done", "queued", "working"),
- "compact rows carry id/title/status/goal_id")
- status, body = saddle_api("GET", "/api/saddle/tasks?project_id=1", r4_token)
- check(all(True for _ in [1]) # project 1 dominates the data set
- and not any(int(t["id"]) == 0 for t in body["tasks"]),
- "project_id filter accepted")
- status, body = saddle_api("GET",
- "/api/saddle/tasks?project_id=%d" % other_project, r4_token)
- check(all(t["title"].startswith("Diff") for t in body["tasks"]),
- "other project lists only its own tasks")
- status, body = saddle_api("GET", "/api/saddle/tasks?q=FLANGE", r4_token)
- titles = [t["title"] for t in body["tasks"]]
- check("Wire the flange" in titles,
- "q matches titles case-insensitively")
- status, body = saddle_api("GET", "/api/saddle/tasks?q=torque%20to%20spec", r4_token)
- check(any(t["title"] == "Wire the flange" for t in body["tasks"]),
- "q matches mandate text")
- status, body = saddle_api("GET", "/api/saddle/task/%d" % auto_child, r4_token)
- task_view = body.get("task", {})
- check(status == 200 and int(task_view.get("id") or 0) == auto_child
- and task_view.get("goal_title") == "Lineage goal"
- and int(task_view.get("goal_id") or 0) == goal,
- "single-task view joins the goal title")
- check("status" in task_view and "description" in task_view
- and "summary" in task_view,
- "single-task view carries the full detail fields")
- check("plan" not in task_view and "parent_task_id" not in task_view,
- "the single-task view drops the deleted parent/plan members")
- status, body = saddle_api("GET", "/api/saddle/task/99999", r4_token)
- check(status == 404, "unknown task id returns 404")
- print("== goal 9: the read-only goals API (list_goals wire) ==")
- # The daemon's list_goals tool reads this endpoint: bearer-only,
- # project-scoped (primary OR linked), newest first, with a
- # goal_id detail carrying the plan/report presence flags.
- status, _ = saddle_api("GET", "/api/saddle/goals", None)
- check(status == 401, "goals endpoint rejects a missing token")
- status, _ = saddle_api("GET", "/api/saddle/goals?project_id=1", r4_token)
- check(status == 200, "goals endpoint ignores stray params (project is by name)")
- # A linked-project goal: the primary's rows plus a goal that only
- # references the project through goal_links.
- conn = db()
- conn.execute("insert into projects (name, git_url, default_branch, created_at)"
- " values ('goal-fabrica',"
- " 'ssh://fabrica.unitatem.net:222/clanker/clanker-saddle.git',"
- " 'main', strftime('%s','now'))")
- conn.commit()
- conn.close()
- state = slot_public(auth, "/projects", "projects")
- goal_fabrica = int([p["id"] for p in state["projects"]
- if p["name"] == "goal-fabrica"][0])
- goal_via_link = create_goal_row({
- "project_id": "1", "title": "Linked-context goal",
- "brief": "Touches goal-fabrica only through its link.",
- "branch_name": "goal/linked-context"})
- act(auth, goal_state(goal_via_link)["add_link"],
- {"linked_project_id": str(goal_fabrica),
- "relationship": "co-developed"})
- status, body = saddle_api("GET", "/api/saddle/goals?project=demo",
- r4_token)
- check(status == 200 and any(int(g["id"]) == goal for g in body.get("goals", [])),
- "the primary project's goals list (name lookup)")
- row = [g for g in body["goals"] if int(g["id"]) == goal_via_link][0]
- check(row["title"] == "Linked-context goal"
- and row["status"] in ("draft", "planning", "plan_review", "active", "cancelled", "done")
- and row["branch"] == "goal/linked-context"
- and len(row.get("updated_at") or "") > 0,
- "compact goal rows carry id/title/status/branch/updated_at")
- ids = [int(g["id"]) for g in body["goals"]]
- check(ids == sorted(ids, reverse=True),
- "the goal list reads newest first")
- check(not any(int(g["id"]) == goal_via_link
- for g in body["goals"] if int(g["id"]) != goal_via_link),
- "no duplicate rows for a goal listed once")
- status, body = saddle_api("GET", "/api/saddle/goals?project=goal-fabrica",
- r4_token)
- check(status == 200
- and [int(g["id"]) for g in body.get("goals", [])] == [goal_via_link],
- "a linked-only project lists its linked goals (not the primary's)")
- # No project param: the recent-goals context answers with every
- # goal newest-first (the daemon's list_goals defaults to the task's
- # primary project, but a standalone task sends no filter — an empty
- # answer there would read as "no goals ever", goal 9 task 9).
- status, body = saddle_api("GET", "/api/saddle/goals", r4_token)
- unfiltered = [int(g["id"]) for g in body.get("goals", [])]
- check(status == 200 and unfiltered
- and unfiltered == sorted(unfiltered, reverse=True)
- and goal_via_link in unfiltered,
- "no project param lists every goal newest-first (the whole-repo view)")
- status, body = saddle_api("GET",
- "/api/saddle/goals?project=no-such-project", r4_token)
- check(status == 200 and body.get("goals") == [],
- "an unknown project name yields an empty list, not an error")
- status, body = saddle_api("GET",
- "/api/saddle/goals?goal_id=%d" % goal, r4_token)
- detail = body.get("goal") or {}
- check(status == 200 and int(detail.get("id") or 0) == goal
- and detail.get("title") == "Lineage goal"
- and detail.get("branch") == "goal/lineage"
- and "Carry the goal lineage." in detail.get("brief", ""),
- "the goal_id detail carries id/title/branch/brief")
- check(detail.get("has_plan") is True and detail.get("has_report") is False,
- "the detail's has_plan/has_report reflect the goal's documents")
- detail_link = saddle_api("GET", "/api/saddle/goals?goal_id=%d" % goal_via_link,
- r4_token)[1].get("goal") or {}
- check(detail_link.get("has_plan") is False,
- "a goal without an approved plan reports has_plan false")
- status, body = saddle_api("GET", "/api/saddle/goals?goal_id=99999", r4_token)
- check(status == 404, "an unknown goal_id returns 404")
- print("== settle: released workers resume the parked agent (coalesced) ==")
- # Release the held workers: their completions wake the parked goal
- # agent (affinity — the same fake hosts it) with the batched
- # notice; the free worker already completed.
- check(wait_task_done(auth, auto_child, timeout=90) is not None,
- "the free worker settles first")
- def agent_push_count():
- return len([p for p in fake.goal_assignments
- if int(p.get("task", {}).get("id") or 0) == auto_parent])
- before_pushes = agent_push_count()
- for held_task in (auto_grand, flange_task):
- caps = task_state(auth, held_task).get("capabilities") or []
- if caps:
- act(auth, caps[0]["detach"], {})
- check(wait_task_done(auth, held_task, timeout=90) is not None,
- "released worker settles (%d)" % held_task)
- check(wait_for(lambda: agent_push_count() > before_pushes or False,
- timeout=90, what="goal agent resumes") is not None,
- "the workers' completions resume the parked goal agent")
- notice = ""
- for p in fake.goal_assignments:
- if int(p.get("task", {}).get("id") or 0) == auto_parent:
- notice = p.get("resume_notice") or ""
- check("Auto child" in notice and "Auto grandchild" in notice
- and "Wire the flange" in notice,
- "the coalesced resume notice covers every plan task")
- check("New completion reports" in notice and "fake daemon finished" in notice,
- "the notice carries the new completion reports")
- act(auth, goal_state(goal)["cancel"], {})
- check(wait_for(lambda: goal_state(goal)["status"] == "cancelled" or False,
- timeout=30, what="lineage goal cancelled") is not None,
- "the lineage goal cancels (drain)")
|