|
|
@@ -0,0 +1,179 @@
|
|
|
+"""The knowledge-cycle bundle (goal 24 task 6): ONE end-to-end pass
|
|
|
+through the understanding and discoveries cycles, cross-cutting the
|
|
|
+surfaces the per-repo bundles could only check in isolation.
|
|
|
+
|
|
|
+Understanding: press Refresh on a project → the created task's OWN
|
|
|
+assignment payload (what the daemon actually receives) carries the
|
|
|
+brief's embedded old hash / old document / project notes AND the
|
|
|
+`understanding` member with the current revision → the saddle POSTs
|
|
|
+/api/saddle/understanding → the project page shows the new current
|
|
|
+document and a history row → a second POST makes the page show the
|
|
|
+newer one.
|
|
|
+
|
|
|
+Discoveries: POST via the API → the /discoveries page lists it and the
|
|
|
+dashboard shows it → update-by-id refines it → a fresh assignment
|
|
|
+payload carries it.
|
|
|
+"""
|
|
|
+from harness import * # noqa: F401,F403 — the shared wire helpers
|
|
|
+
|
|
|
+
|
|
|
+def run(ctx):
|
|
|
+ auth, fake, fake_id, fake_token = ctx.auth, ctx.fake, ctx.fake_id, ctx.fake_token
|
|
|
+
|
|
|
+ def db_rows(sql, params=()):
|
|
|
+ conn = db()
|
|
|
+ try:
|
|
|
+ return conn.execute(sql, params).fetchall()
|
|
|
+ finally:
|
|
|
+ conn.close()
|
|
|
+
|
|
|
+ def payload_for(task_id):
|
|
|
+ for payload in reversed(fake.start_payloads):
|
|
|
+ if int((payload.get("task") or {}).get("id") or 0) == int(task_id):
|
|
|
+ return payload
|
|
|
+ return None
|
|
|
+
|
|
|
+ # A dedicated project so the cycle owns every row it asserts on.
|
|
|
+ state = slot_public(auth, "/projects", "projects")
|
|
|
+ act(auth, state["create"], {"name": "cycle",
|
|
|
+ "git_url": "https://example.com/cycle.git"})
|
|
|
+ cycle_id = int([p["id"] for p in
|
|
|
+ slot_public(auth, "/projects", "projects")["projects"]
|
|
|
+ if p["name"] == "cycle"][0])
|
|
|
+
|
|
|
+ print("== cycle: the project page ships the section and Refresh ==")
|
|
|
+ cycle_state = slot_public(auth, "/projects/%d" % cycle_id, "project")
|
|
|
+ check(cycle_state.get("refresh_understanding") is not None,
|
|
|
+ "the cycle project's slot authors the Refresh action")
|
|
|
+ page_html = http_get_page(auth, "/projects/%d" % cycle_id)
|
|
|
+ check("Understanding" in page_html and "Refresh" in page_html,
|
|
|
+ "the project page renders the Understanding section with its button")
|
|
|
+
|
|
|
+ print("== cycle: seed a current document + notes, press Refresh ==")
|
|
|
+ status, body = saddle_api("POST", "/api/saddle/understanding", fake_token,
|
|
|
+ {"project": "cycle",
|
|
|
+ "body": "# Cycle v1\n\nThe old map.",
|
|
|
+ "commit_hash": "aaa1111", "task_id": 0})
|
|
|
+ check(status == 200 and body.get("ok") is True,
|
|
|
+ "seeding the first understanding via the API works")
|
|
|
+ conn = db()
|
|
|
+ conn.execute("insert into project_notes (project_id, source_task_id, body,"
|
|
|
+ " created_at) values (?, 0, ?, strftime('%s','now'))",
|
|
|
+ (cycle_id, "Cycle note: the fixtures live in test/bundles/."))
|
|
|
+ conn.commit()
|
|
|
+ conn.close()
|
|
|
+
|
|
|
+ cycle_state = slot_public(auth, "/projects/%d" % cycle_id, "project")
|
|
|
+ result = act(auth, cycle_state["refresh_understanding"], {})
|
|
|
+ check(any(d.get("type") == "notify" and d.get("kind") == "success" for d in result),
|
|
|
+ "pressing Refresh queues the explorer task")
|
|
|
+ rows = db_rows("select id, title, status from tasks"
|
|
|
+ " where title like 'Understanding refresh: cycle'")
|
|
|
+ check(len(rows) == 1, "Refresh created exactly one task")
|
|
|
+ refresh_id = rows[0][0]
|
|
|
+ check(rows[0][1].startswith("Understanding refresh:"),
|
|
|
+ "the refresh task's title carries the goal-24 prefix")
|
|
|
+ check(rows[0][2] in ("queued", "assigned", "working"),
|
|
|
+ "the refresh task is live (%s)" % rows[0][2])
|
|
|
+
|
|
|
+ print("== cycle: the refresh task's OWN assignment payload ==")
|
|
|
+ # The fake saddle asks when idle, so the dispatcher hands it the
|
|
|
+ # refresh task: the payload the daemon builds from the brief AND
|
|
|
+ # the understanding member must both be there.
|
|
|
+ check(wait_for(lambda: payload_for(refresh_id) is not None or False,
|
|
|
+ timeout=30, what="refresh assignment push") is not None,
|
|
|
+ "the dispatcher assigns the refresh task to the saddle")
|
|
|
+ payload = payload_for(refresh_id)
|
|
|
+ brief = (payload.get("task") or {}).get("description") or ""
|
|
|
+ check("Previous commit hash: aaa1111" in brief,
|
|
|
+ "the assigned brief embeds the OLD commit hash")
|
|
|
+ check("```markdown" in brief and "The old map." in brief,
|
|
|
+ "the assigned brief embeds the OLD document")
|
|
|
+ check("Cycle note: the fixtures live in test/bundles/." in brief,
|
|
|
+ "the assigned brief embeds the project notes from other agents")
|
|
|
+ check(payload.get("understanding") == "# Cycle v1\n\nThe old map.",
|
|
|
+ "the assignment payload's understanding member is the current doc")
|
|
|
+ check("discoveries" in payload and isinstance(payload.get("discoveries"), list),
|
|
|
+ "the assignment payload carries the discoveries array alongside")
|
|
|
+
|
|
|
+ print("== cycle: the explorer saves; the page flips ==")
|
|
|
+ fake.push_results("done", summary="walked the history and updated the doc")
|
|
|
+ check(wait_task_done(auth, refresh_id, timeout=30) is not None,
|
|
|
+ "the refresh task completes")
|
|
|
+ status, body = saddle_api("POST", "/api/saddle/understanding", fake_token,
|
|
|
+ {"project": "cycle",
|
|
|
+ "body": "# Cycle v2\n\nThe updated map.",
|
|
|
+ "commit_hash": "bbb2222",
|
|
|
+ "task_id": refresh_id})
|
|
|
+ check(status == 200 and int(body.get("understanding_id", 0)) > 0,
|
|
|
+ "the explorer's save_understanding POST lands")
|
|
|
+ cycle_state = slot_public(auth, "/projects/%d" % cycle_id, "project")
|
|
|
+ current = cycle_state.get("understanding_current") or {}
|
|
|
+ check(current.get("body") == "# Cycle v2\n\nThe updated map."
|
|
|
+ and current.get("commit_hash") == "bbb2222",
|
|
|
+ "the page shows the NEW current document after the save")
|
|
|
+ history = cycle_state.get("understanding_history") or []
|
|
|
+ check([h.get("commit_hash") for h in history] == ["aaa1111"]
|
|
|
+ and (history[0].get("body") or "").endswith("The old map."),
|
|
|
+ "the page keeps the previous revision as a history row")
|
|
|
+ page_html = http_get_page(auth, "/projects/%d" % cycle_id)
|
|
|
+ check("stm-markdown=\"project.understanding_current.body\"" in page_html,
|
|
|
+ "the rendered page binds the current document as markdown")
|
|
|
+
|
|
|
+ print("== cycle: a second save makes the page show the newer ==")
|
|
|
+ status, body = saddle_api("POST", "/api/saddle/understanding", fake_token,
|
|
|
+ {"project": "cycle",
|
|
|
+ "body": "# Cycle v3\n\nThe newest map.",
|
|
|
+ "commit_hash": "ccc3333"})
|
|
|
+ check(status == 200, "the second explorer POST lands")
|
|
|
+ cycle_state = slot_public(auth, "/projects/%d" % cycle_id, "project")
|
|
|
+ current = cycle_state.get("understanding_current") or {}
|
|
|
+ check(current.get("body") == "# Cycle v3\n\nThe newest map.",
|
|
|
+ "the page's current flips to the newest revision")
|
|
|
+ check([h.get("commit_hash") for h in
|
|
|
+ cycle_state.get("understanding_history") or []]
|
|
|
+ == ["bbb2222", "aaa1111"],
|
|
|
+ "the history grows newest-first with both older revisions")
|
|
|
+
|
|
|
+ print("== cycle: the discoveries leg ==")
|
|
|
+ status, body = saddle_api("POST", "/api/saddle/discovery", fake_token,
|
|
|
+ {"body": "cycle fact: meson test -C builddir runs the suite",
|
|
|
+ "task_id": refresh_id})
|
|
|
+ check(status == 200 and int(body.get("discovery_id", 0)) > 0,
|
|
|
+ "POST /api/saddle/discovery inserts and echoes the id")
|
|
|
+ discovery_id = int(body["discovery_id"])
|
|
|
+ discoveries_state = slot_public(auth, "/discoveries", "discoveries")
|
|
|
+ listed = [r for r in discoveries_state.get("discoveries") or []
|
|
|
+ if r.get("body") == "cycle fact: meson test -C builddir runs the suite"]
|
|
|
+ check(len(listed) == 1, "the /discoveries page lists the new row")
|
|
|
+ home_state = slot_public(auth, "/", "dashboard")
|
|
|
+ dashboard = [r for r in home_state.get("discoveries") or []
|
|
|
+ if r.get("body") == "cycle fact: meson test -C builddir runs the suite"]
|
|
|
+ check(len(dashboard) == 1, "the dashboard shows the discovery")
|
|
|
+
|
|
|
+ status, body = saddle_api("POST", "/api/saddle/discovery/update", fake_token,
|
|
|
+ {"id": discovery_id,
|
|
|
+ "body": "cycle fact: meson test -C builddir runs the suite (also task-log-js)"})
|
|
|
+ check(status == 200 and body.get("ok") is True, "update-by-id answers ok")
|
|
|
+ discoveries_state = slot_public(auth, "/discoveries", "discoveries")
|
|
|
+ rows_now = [r for r in discoveries_state.get("discoveries") or []
|
|
|
+ if r.get("id") == str(discovery_id)]
|
|
|
+ check(len(rows_now) == 1
|
|
|
+ and "(also task-log-js)" in rows_now[0].get("body", ""),
|
|
|
+ "the page shows the refined body in place (no duplicate row)")
|
|
|
+
|
|
|
+ # A fresh assignment carries the refined discovery.
|
|
|
+ payload_count = len(fake.start_payloads)
|
|
|
+ create_queued(auth, {"project_id": str(cycle_id),
|
|
|
+ "title": "Cycle dispatch",
|
|
|
+ "priority": "2"})
|
|
|
+ check(wait_for(lambda: len(fake.start_payloads) > payload_count or False,
|
|
|
+ timeout=30, what="cycle /start push") is not None,
|
|
|
+ "the dispatcher pushes the cycle task")
|
|
|
+ shipped = [r.get("body", "") for r in
|
|
|
+ fake.start_payloads[-1].get("discoveries") or []]
|
|
|
+ check(any("(also task-log-js)" in b for b in shipped),
|
|
|
+ "a fresh assignment payload carries the refined discovery")
|
|
|
+ check(fake.start_payloads[-1].get("understanding")
|
|
|
+ == "# Cycle v3\n\nThe newest map.",
|
|
|
+ "the fresh assignment also carries the newest understanding")
|