parity.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. """The parity bundle (goal 9): the agent-facing wire the restored tool
  2. surface reads — GET /api/saddle/goals behind list_goals (bearer auth,
  3. project scoping over primary AND linked projects, the goal_id detail
  4. shape) and the yeehaw ledger event rendering in the task log page."""
  5. from harness import * # noqa: F401,F403 — the shared wire helpers
  6. def run(ctx):
  7. auth, fake, fake_id, fake_token = ctx.auth, ctx.fake, ctx.fake_id, ctx.fake_token
  8. print("== goals endpoint: auth ==")
  9. status, body = saddle_api("GET", "/api/saddle/goals", None)
  10. check(status == 401, "goals: no bearer token is 401 (%s)" % status)
  11. status, body = saddle_api("GET", "/api/saddle/goals", "not-a-token")
  12. check(status == 401, "goals: an unknown token is 401 (%s)" % status)
  13. print("== goals endpoint: rows and project scoping ==")
  14. # a second project so scoping is observable
  15. conn = db()
  16. conn.execute("insert into projects (name, git_url, default_branch, created_at)"
  17. " values ('parity-repo', 'https://example.com/parity.git', 'main',"
  18. " strftime('%s','now'))")
  19. conn.commit()
  20. conn.close()
  21. def create_goal(fields):
  22. act(auth, slot_public(auth, "/goals", "goals")["create"], fields)
  23. rows = [g for g in slot_public(auth, "/goals", "goals")["goals"]
  24. if g["title"] == fields["title"]]
  25. return int(rows[0]["id"])
  26. goal_a = create_goal({"project_id": "1", "title": "Parity alpha",
  27. "brief": "First parity goal.",
  28. "branch_name": "goal/parity-a", "priority": "3"})
  29. goal_b = create_goal({"project_id": "2", "title": "Parity beta",
  30. "brief": "Second parity goal.",
  31. "branch_name": "goal/parity-b", "priority": "3"})
  32. # link goal_a -> parity-repo (project 2) as reference: linked projects
  33. # count for scoping too
  34. state = goal_state(goal_a)
  35. act(auth, state["add_link"], {"linked_project_id": "2", "relationship": "reference"})
  36. status, body = saddle_api("GET", "/api/saddle/goals", fake_token)
  37. check(status == 200 and body.get("ok") is True and isinstance(body.get("goals"), list),
  38. "goals: unfiltered GET returns the rows envelope (%s)" % status)
  39. ids = [row.get("id") for row in body.get("goals", [])]
  40. check(goal_a in ids and goal_b in ids,
  41. "goals: both goals appear unfiltered")
  42. row_a = [row for row in body.get("goals", []) if row.get("id") == goal_a][0]
  43. check(row_a.get("status") in ("draft", "planning") and row_a.get("branch") == "goal/parity-a"
  44. and len(row_a.get("updated_at", "")) > 0 and row_a.get("title") == "Parity alpha",
  45. "goals: a row carries id/title/status/branch/updated_at (%s)" % row_a)
  46. # project=demo lists goal_a (primary) but NOT goal_b (disjoint)
  47. status, body = saddle_api("GET", "/api/saddle/goals?project=demo", fake_token)
  48. scoped_ids = [row.get("id") for row in body.get("goals", [])]
  49. check(goal_a in scoped_ids and goal_b not in scoped_ids,
  50. "goals: project=demo scopes to the primary project")
  51. # project=parity-repo lists goal_a (LINKED) and goal_b (primary)
  52. status, body = saddle_api("GET", "/api/saddle/goals?project=parity-repo", fake_token)
  53. scoped_ids = [row.get("id") for row in body.get("goals", [])]
  54. check(goal_a in scoped_ids and goal_b in scoped_ids,
  55. "goals: linked (reference) projects scope too")
  56. # unknown project: an empty view, not an error
  57. status, body = saddle_api("GET", "/api/saddle/goals?project=ghost", fake_token)
  58. check(status == 200 and body.get("goals") == [],
  59. "goals: an unknown project is an empty view (%s)" % status)
  60. print("== goals endpoint: goal_id detail ==")
  61. status, body = saddle_api("GET", "/api/saddle/goals?goal_id=%d" % goal_a, fake_token)
  62. check(status == 200 and body.get("ok") is True and "goal" in body,
  63. "goals: goal_id returns the detail envelope (%s)" % status)
  64. detail = body.get("goal") or {}
  65. check(detail.get("id") == goal_a and detail.get("brief") == "First parity goal."
  66. and detail.get("has_plan") is False and detail.get("has_report") is False,
  67. "goals: the detail carries brief + has_plan/has_report (%s)" % detail)
  68. status, body = saddle_api("GET", "/api/saddle/goals?goal_id=99999", fake_token)
  69. check(status == 404, "goals: an unknown goal_id is 404 (%s)" % status)
  70. # a plan makes has_plan true: start the goal, wait for the plan
  71. # review (the PENDING block alone must NOT flip it — only the
  72. # accept-gated goals.plan column does), approve, then re-read
  73. fake.goal_script = [("plan", "# Parity plan\n\n1. Check the goals wire")]
  74. act(auth, goal_state(goal_a)["start"], {})
  75. check(wait_for(lambda: goal_state(goal_a)["status"] == "plan_review" or False,
  76. timeout=120, what="parity goal enters plan review") is not None,
  77. "goals: the goal agent submitted its plan")
  78. status, body = saddle_api("GET", "/api/saddle/goals?goal_id=%d" % goal_a, fake_token)
  79. check((body.get("goal") or {}).get("has_plan") is False,
  80. "goals: a PENDING plan review leaves has_plan false (accept-gated)")
  81. fake.goal_script = [("suspend",)]
  82. # (Goal 27 refinements — the parking guard: the scripted park
  83. # needs a live wake source under the goal.)
  84. conn = db()
  85. conn.execute("insert into tasks (project_id, goal_id, kind, title, status,"
  86. " priority, mode, creator, created_at) values (1, ?, 'task',"
  87. " 'Parity live worker', 'blocked', 5, 'implement', 'goal',"
  88. " strftime('%s','now'))", (goal_a,))
  89. conn.commit()
  90. conn.close()
  91. pending = goal_state(goal_a)["plan_pending_block"]
  92. act(auth, pending["answer_action"], {"verdict": "accept"})
  93. check(wait_for(lambda: goal_state(goal_a)["status"] == "active" or False,
  94. timeout=60, what="parity plan approved") is not None,
  95. "goals: the approved plan activates the goal")
  96. status, body = saddle_api("GET", "/api/saddle/goals?goal_id=%d" % goal_a, fake_token)
  97. check((body.get("goal") or {}).get("has_plan") is True,
  98. "goals: a stored plan flips has_plan (%s)" % (body.get("goal") or {}))
  99. check((body.get("goal") or {}).get("status") == "active",
  100. "goals: the detail reflects the live status")
  101. print("== yeehaw: the celebration renders in the task log ==")
  102. task_id = create_queued(auth, {"project_id": "1", "title": "Parity yeehaw run",
  103. "priority": "3", "description": "Push a yeehaw."})
  104. check(wait_for(lambda: fake.running_task == task_id or False, timeout=60,
  105. what="yeehaw task dispatches") is not None,
  106. "yeehaw: the task reaches the fake saddle")
  107. fake.push_events([{"kind": "yeehaw", "actor": "model",
  108. "payload": {"message": "shipped the parity wire — delightful task"}}])
  109. status, page = http("GET", "/tasks/%d/log" % task_id, timeout=15)
  110. # The badge classes and kind handling live in the compiled
  111. # task_log.js resource (plan 17 b3+), not the page HTML.
  112. status_js, log_js = http("GET", "/_statum/resource/task_log.js", timeout=15)
  113. check(status == 200 and "data-log-feed" in page,
  114. "yeehaw: the task log page renders")
  115. check(status_js == 200 and "badge-yeehaw" in log_js,
  116. "yeehaw: the page ships the festive badge class")
  117. check("'yeehaw'" in log_js or '"yeehaw"' in log_js,
  118. "yeehaw: the page's client script knows the kind")