| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298 |
- """The replay 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: replay — plan 13 B1/B2 shapes, rebuilt on the plan-17 ledger:
- # the idempotent events push (UNIQUE(session_id, seq) — database state,
- # so it survives a web restart), the session-churn redelivery shape, and
- # repeated failure pushes that must still project exactly once.
- # =============================================================================
- def run(ctx):
- auth, fake, fake_id, fake_token = ctx.auth, ctx.fake, ctx.fake_id, ctx.fake_token
- app = ctx.app
- def rows_for(session):
- conn = db()
- try:
- return conn.execute(
- "select count(*) from events where session_id = ?",
- (session,)).fetchone()[0]
- finally:
- conn.close()
- def open_session_on_fake(task_id):
- """A fixture open session row on the shard's saddle (the churn
- shape: a fresh web-side run row the daemon never really drove)."""
- conn = db()
- try:
- conn.execute(
- "insert into sessions (task_id, saddle_id, started_at)"
- " values (?, ?, strftime('%s','now'))", (task_id, fake_id))
- conn.commit()
- return conn.execute(
- "select max(id) from sessions where task_id = ?",
- (task_id,)).fetchone()[0]
- finally:
- conn.close()
- print("== plan 17 §7.2: event pushes are idempotent per session ==")
- # A first task runs to completion; its session is the replay source.
- first = create_queued(auth, {"project_id": "1", "title": "Replay source task",
- "priority": "2"})
- check(wait_task_done(auth, first, timeout=60) is not None,
- "the replay-source task completes")
- conn = db()
- src_session = conn.execute(
- "select max(id) from sessions where task_id = ?", (first,)).fetchone()[0]
- conn.close()
- # The outbox batch: two events the daemon already delivered once.
- burst = [
- {"seq": 5001, "kind": "tool_call", "actor": "model",
- "payload": {"turn": 7, "tool": "run_command",
- "args": {"command": "make test"}}},
- {"seq": 5002, "kind": "tool_result", "actor": "harness",
- "payload": {"turn": 7, "stdout": "ok", "exit_code": 0}},
- ]
- status, body = saddle_api("POST", "/api/saddle/chunks", fake_token,
- {"session_id": src_session, "events": burst})
- check(status == 200 and int(body.get("stored") or 0) == 2,
- "the event batch stores under its session")
- stored_once = rows_for(src_session)
- # Redelivery under the SAME session: consumed, nothing new.
- status, body = saddle_api("POST", "/api/saddle/chunks", fake_token,
- {"session_id": src_session, "events": burst})
- check(status == 200 and int(body.get("stored") or 0) == 0
- and rows_for(src_session) == stored_once,
- "a same-session redelivery stores nothing new (consumed silently)")
- # Session churn: a mis-keyed outbox send under a brand-new session
- # id replays the SAME seqs. The ledger's idempotency key is
- # (session_id, seq) — the churned session is a different key, so
- # the events store under it (the daemon contract makes the session
- # id part of the key; the old daemon-global entry dedup is gone).
- conn = db()
- conn.execute(
- "insert into tasks (project_id, title, status, priority, created_at,"
- " completed_at) values (1, 'Churn target task', 'done', 5,"
- " strftime('%s','now'), strftime('%s','now'))")
- conn.commit()
- churn_task = conn.execute(
- "select max(id) from tasks where title = 'Churn target task'").fetchone()[0]
- conn.close()
- churn_session = open_session_on_fake(churn_task)
- status, body = saddle_api("POST", "/api/saddle/chunks", fake_token,
- {"session_id": churn_session, "events": burst})
- check(status == 200 and int(body.get("stored") or 0) == 2
- and rows_for(churn_session) == 2,
- "a churned-session replay stores under the new key (per-session seq)")
- # Web restart: the idempotency survives (database constraint, not
- # memory) — the same-session redelivery still stores nothing.
- app.terminate()
- app.wait(timeout=10)
- app = start_app()
- auth = login("admin", "adminpass123")
- ctx.app = app # teardown tracks the restarted process
- ctx.auth = auth
- # The restart wiped the in-memory ask bookkeeping; the daemon
- # re-establishes sleeping with a fresh ask (the real daemon's
- # idle-awake ask / 15-minute safety self-ask).
- fake.ask_now()
- status, body = saddle_api("POST", "/api/saddle/chunks", fake_token,
- {"session_id": churn_session, "events": burst})
- check(status == 200 and int(body.get("stored") or 0) == 0
- and rows_for(churn_session) == 2,
- "a post-restart redelivery still stores nothing (persistent)")
- print("== plan 13 B2: duplicate failed pushes project once (task) ==")
- # The post-mortem's honest burst, rebuilt on the state channel: the
- # failed state redelivered repeatedly — one transition, one
- # summary, ever (terminal pushes are consumed silently).
- def agent_comments(task_id, body=None):
- conn = db()
- try:
- if body is None:
- return conn.execute(
- "select count(*) from task_comments"
- " where task_id = ? and author = 'agent'",
- (task_id,)).fetchone()[0]
- return conn.execute(
- "select count(*) from task_comments"
- " where task_id = ? and author = 'agent' and body = ?",
- (task_id, body)).fetchone()[0]
- finally:
- conn.close()
- conn = db()
- conn.execute(
- "insert into tasks (project_id, title, status, priority, created_at)"
- " values (1, 'Burst failure task', 'working', 5, strftime('%s','now'))")
- conn.commit()
- burst_task = conn.execute(
- "select max(id) from tasks where title = 'Burst failure task'").fetchone()[0]
- conn.close()
- burst_session = open_session_on_fake(burst_task)
- status, _ = saddle_api("POST", "/api/saddle/results", fake_token,
- {"session_id": burst_session, "state": "failed",
- "summary": "bash -n rejected the script"})
- check(status == 200
- and task_state(auth, burst_task)["status"] == "failed",
- "the failure state push fails the task")
- check(agent_comments(burst_task, "bash -n rejected the script") == 0
- and "bash -n rejected the script"
- in (task_state(auth, burst_task).get("summary") or ""),
- "the failure explanation lives ONCE — in tasks.summary, the terminal cache (S4: no duplicate agent comment)")
- # The burst: the same failure redelivered under the session AND a
- # churned session — nothing re-runs.
- churned_burst = open_session_on_fake(burst_task)
- for target in (burst_session, churned_burst):
- status, _ = saddle_api("POST", "/api/saddle/results", fake_token,
- {"session_id": target, "state": "failed",
- "summary": "bash -n rejected the script"})
- check(status == 200,
- "the failed-state push under %s is consumed, not rejected" % (
- "its session" if target == burst_session else "a churned session"))
- check(task_state(auth, burst_task)["status"] == "failed"
- and agent_comments(burst_task) == 0
- and "bash -n rejected the script"
- in (task_state(auth, burst_task).get("summary") or ""),
- "duplicate failed pushes leave one transition, one summary, zero agent comments (S4)")
- print("== plan 13 B2: goal failure projects once ==")
- # Goal context: `saddle failure` fails the goal ONCE — the 115
- # goal-failed events of the live failure are structurally gone.
- def goal_failed_events(goal_id):
- # Plan 15 C3: the narrative derives at read time — the
- # failed-goal row IS the goal-failed record (one row, ever).
- return len([e for e in goal_state(goal_id)["events"]
- if e["kind"] == "goal-failed"])
- act(auth, slot_public(auth, "/goals", "goals")["create"], {
- "project_id": "1", "title": "Burst goal", "brief": "Fails honestly.",
- "branch_name": "goal/burst"})
- conn = db()
- burst_goal = conn.execute(
- "select id from goals where title = 'Burst goal'").fetchone()[0]
- conn.close()
- fake.goal_script = [("suspend",)]
- act(auth, goal_state(burst_goal)["start"], {})
- conn = db()
- run_task = conn.execute(
- "select id from tasks where goal_id = ? and kind = 'goal_run'",
- (burst_goal,)).fetchone()[0]
- conn.close()
- check(wait_for(lambda: task_state(auth, run_task)["status"] == "suspended"
- or False, timeout=60, what="burst goal agent parks") is not None,
- "the burst goal's agent parks first")
- conn = db()
- run_session = conn.execute(
- "select max(id) from sessions where task_id = ?",
- (run_task,)).fetchone()[0]
- conn.close()
- status, _ = saddle_api("POST", "/api/saddle/results", fake_token,
- {"session_id": run_session, "state": "failed",
- "summary": "the harness collapsed"})
- check(status == 200 and goal_state(burst_goal)["status"] == "failed",
- "the goal agent's failure fails the goal")
- check(goal_failed_events(burst_goal) == 1,
- "the failure push records exactly one goal-failed event")
- # Redeliveries — under the run's session AND a churned session —
- # plus the bare failed state again: still one.
- churned_run = open_session_on_fake(run_task)
- for target in (run_session, churned_run):
- status, _ = saddle_api("POST", "/api/saddle/results", fake_token,
- {"session_id": target, "state": "failed",
- "summary": "the harness collapsed"})
- check(status == 200, "the goal-failure redelivery under %s is consumed"
- % ("its session" if target == run_session else "a churned session"))
- check(goal_state(burst_goal)["status"] == "failed"
- and goal_failed_events(burst_goal) == 1,
- "redelivered failures never re-run the goal failure (one event, ever)")
- check(task_state(auth, run_task)["status"] == "failed",
- "the goal-run task stays failed after the replays")
- conn = db()
- burst_blocks = conn.execute(
- "select count(*) from task_blocks where task_id = ?", (run_task,)
- ).fetchone()[0]
- conn.close()
- check(burst_blocks == 0,
- "the goal-failed path raises ZERO blocks (nothing pins the pile)")
- print("== plan 17: agent remarks ride the ledger as comment events ==")
- # The heredoc `cat << 'EOF' | saddle comment` shape: the daemon
- # emits a comment event carrying the body. The body is the record —
- # no command echo ever renders, and the goal page's current-activity
- # line reads the newest remark.
- act(auth, slot_public(auth, "/goals", "goals")["create"], {
- "project_id": "1", "title": "Comment goal", "brief": "Reports cleanly.",
- "branch_name": "goal/comment"})
- conn = db()
- comment_goal = conn.execute(
- "select id from goals where title = 'Comment goal'").fetchone()[0]
- conn.close()
- fake.goal_script = [("suspend",)]
- act(auth, goal_state(comment_goal)["start"], {})
- conn = db()
- comment_run = conn.execute(
- "select id from tasks where goal_id = ? and kind = 'goal_run'",
- (comment_goal,)).fetchone()[0]
- conn.close()
- check(wait_for(lambda: task_state(auth, comment_run)["status"] == "suspended"
- or False, timeout=60, what="comment goal agent parks") is not None,
- "the comment goal's agent parks")
- conn = db()
- comment_session = conn.execute(
- "select max(id) from sessions where task_id = ?",
- (comment_run,)).fetchone()[0]
- conn.close()
- comment_body = "The flange weld holds. First sentence carries the information."
- status, body = saddle_api("POST", "/api/saddle/chunks", fake_token,
- {"session_id": comment_session,
- "events": [{
- "seq": 8001, "kind": "comment",
- "actor": "model",
- "payload": {"body": comment_body}}]})
- check(status == 200 and int(body.get("stored") or 0) == 1,
- "the comment event stores on the ledger")
- conn = db()
- tables = {r[0] for r in conn.execute(
- "select name from sqlite_master where type = 'table'").fetchall()}
- comment_events = conn.execute(
- "select count(*) from events where scope_id = ? and kind = 'comment'",
- (comment_run,)).fetchone()[0]
- conn.close()
- check(comment_events == 1
- and "goal_comments" not in tables,
- "the ledger carries the remark (the goal_comments table is gone)")
- narrative_comments = [e["text"] for e in goal_state(comment_goal)["events"]
- if e["kind"] == "comment"]
- check(len(narrative_comments) == 1
- and narrative_comments[0].startswith("The flange weld holds.")
- and "saddle comment" not in narrative_comments[0]
- and "EOF" not in narrative_comments[0],
- "the derived narrative's comment rows carry the body text")
- state = goal_state(comment_goal)
- check(state["current_command"] == comment_body,
- "the goal page's current-activity line shows the remark body")
- check(any(e["kind"] == "comment"
- and e["text"].startswith("The flange weld holds.")
- for e in state["events"]),
- "the goal slot carries the remark in its narrative")
- page = http_get_page(auth, "/goals/%d" % comment_goal)
- check("saddle comment" not in page,
- "the rendered goal page never shows the command echo")
- check("event.kind == 'comment' ? ' footnote'" in page,
- "the narrative's comment events render with footnote styling")
- # Drain: nothing queued remains for the shard's shutdown.
- check(queued_count() == 0, "the replay shard drains its queue")
|