Files
2026-07-13 13:12:33 +08:00

881 lines
29 KiB
Python

"""MetaRunWriter unit tests — round-trip, truncation, thread safety, redaction."""
from __future__ import annotations
import json
import os
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
import pytest
from yoyo import get_backend, read_migrations
from opensquilla.persistence.meta_run_writer import (
MetaRunWriter,
RunRecord, # noqa: F401 — explicit public-API surface assertion
StepRecord, # noqa: F401 — explicit public-API surface assertion
_gen_ulid,
_redact_inputs_json,
_serialize_plan,
_truncate,
open_meta_run_writer,
summarize_run_record,
)
from opensquilla.persistence.migrator import apply_pending
from opensquilla.skills.meta.types import MetaPlan, MetaResult, MetaStep
MIGRATIONS_DIR = Path(__file__).resolve().parents[1].parent / "migrations"
@pytest.fixture
def writer(tmp_path: Path):
db = str(tmp_path / "test.db")
apply_pending(db, MIGRATIONS_DIR)
w = open_meta_run_writer(db)
yield w
w.close()
def _make_plan(name: str = "demo") -> MetaPlan:
return MetaPlan(
name=name,
triggers=("demo trigger",),
priority=50,
steps=(
MetaStep(id="s1", skill="alpha", kind="agent"),
MetaStep(id="s2", skill="beta", kind="agent", depends_on=("s1",)),
),
)
def test_pragmas_set_on_connection(writer: MetaRunWriter) -> None:
cur = writer._conn.execute("PRAGMA foreign_keys")
assert cur.fetchone()[0] == 1
cur = writer._conn.execute("PRAGMA journal_mode")
assert cur.fetchone()[0].lower() == "wal"
cur = writer._conn.execute("PRAGMA synchronous")
assert cur.fetchone()[0] == 1 # NORMAL == 1
cur = writer._conn.execute("PRAGMA busy_timeout")
assert cur.fetchone()[0] == 5000
def test_begin_finish_run_roundtrip(writer: MetaRunWriter) -> None:
plan = _make_plan()
run_id = writer.begin_run_sync(
meta_skill_name=plan.name,
meta_plan=plan,
triggered_by="soft_meta_invoke",
inputs={"user_message": "hi"},
session_key="sess-1",
turn_id="turn-1",
)
assert len(run_id) == 26 # ULID
from opensquilla.skills.meta.types import MetaResult
writer.finish_run_sync(
run_id=run_id,
status="ok",
result=MetaResult(ok=True, final_text="hello"),
)
record = writer.get_run(run_id)
assert record is not None
assert record.meta_skill_name == "demo"
assert record.triggered_by == "soft_meta_invoke"
assert record.session_key == "sess-1"
assert record.status == "ok"
assert record.final_text == "hello"
assert record.owner_pid == os.getpid()
assert record.plan_snapshot_json # non-empty
assert len(record.meta_skill_digest) == 64 # sha256 hex
def test_begin_run_accepts_manual_command_trigger(writer: MetaRunWriter) -> None:
plan = _make_plan()
run_id = writer.begin_run_sync(
meta_skill_name=plan.name,
meta_plan=plan,
triggered_by="manual_command",
inputs={"user_message": "/meta demo"},
session_key="sess-manual",
turn_id="turn-manual",
)
assert run_id is not None
record = writer.get_run(run_id)
assert record is not None
assert record.triggered_by == "manual_command"
assert record.status == "running"
def test_step_lifecycle(writer: MetaRunWriter) -> None:
plan = _make_plan()
run_id = writer.begin_run_sync(
meta_skill_name=plan.name,
meta_plan=plan,
triggered_by="hard_takeover",
inputs={"q": "x"},
session_key=None,
turn_id=None,
)
writer.begin_step_sync(
run_id=run_id,
step=plan.steps[0],
effective_skill="alpha",
rendered_inputs={"q": "x"},
)
writer.finish_step_sync(
run_id=run_id,
step_id="s1",
status="ok",
output_text="alpha-output",
)
steps = writer.get_steps(run_id)
assert len(steps) == 1
assert steps[0].status == "ok"
assert steps[0].output_text == "alpha-output"
assert steps[0].effective_skill == "alpha"
def test_finish_step_works_after_usage_column_rollback(tmp_path: Path) -> None:
db = str(tmp_path / "v014_schema.db")
apply_pending(db, MIGRATIONS_DIR)
backend = get_backend(f"sqlite:///{db}")
migrations = read_migrations(str(MIGRATIONS_DIR))
by_id = {migration.id: migration for migration in migrations}
backend.rollback_migrations([by_id["V015__meta_skill_step_usage"]])
w = open_meta_run_writer(db)
try:
plan = _make_plan()
run_id = w.begin_run_sync(
meta_skill_name=plan.name,
meta_plan=plan,
triggered_by="hard_takeover",
inputs={"q": "x"},
session_key=None,
turn_id=None,
)
w.begin_step_sync(
run_id=run_id,
step=plan.steps[0],
effective_skill="alpha",
rendered_inputs={"q": "x"},
)
w.finish_step_sync(
run_id=run_id,
step_id="s1",
status="ok",
output_text="alpha-output",
usage={"input_tokens": 5, "output_tokens": 2},
)
[step] = w.get_steps(run_id)
assert step.status == "ok"
assert step.output_text == "alpha-output"
record = w.get_run(run_id)
assert record is not None
assert summarize_run_record(record)["usage"]["available"] is False
finally:
w.close()
def test_run_summary_reports_step_counts_duration_and_unavailable_cost(
writer: MetaRunWriter,
) -> None:
plan = _make_plan()
run_id = writer.begin_run_sync(
meta_skill_name=plan.name,
meta_plan=plan,
triggered_by="soft_meta_invoke",
inputs={"user_message": "summarize"},
session_key="sess-1",
turn_id="turn-1",
)
writer.begin_step_sync(
run_id=run_id,
step=plan.steps[0],
effective_skill="alpha",
rendered_inputs={"q": "x"},
)
writer.finish_step_sync(
run_id=run_id,
step_id="s1",
status="ok",
output_text="alpha-output",
)
writer.finish_run_sync(
run_id=run_id,
status="ok",
result=MetaResult(ok=True, final_text="done"),
)
record = writer.get_run(run_id)
assert record is not None
summary = summarize_run_record(record)
assert summary["run_id"] == run_id
assert summary["step_count"] == 1
assert summary["completed_step_count"] == 1
assert summary["failed_step_count"] == 0
assert summary["duration_ms"] is not None
assert summary["final_text_chars"] == 4
assert summary["step_output_chars"] == len("alpha-output")
assert summary["usage"]["available"] is False
assert summary["usage"]["cost_source"] == "unavailable"
assert summary["steps"][0]["output_chars"] == len("alpha-output")
def test_run_summary_aggregates_persisted_step_usage(writer: MetaRunWriter) -> None:
plan = _make_plan()
run_id = writer.begin_run_sync(
meta_skill_name=plan.name,
meta_plan=plan,
triggered_by="soft_meta_invoke",
inputs={"user_message": "summarize"},
session_key="sess-1",
turn_id="turn-1",
)
writer.begin_step_sync(
run_id=run_id,
step=plan.steps[0],
effective_skill="alpha",
rendered_inputs={"q": "x"},
)
writer.finish_step_sync(
run_id=run_id,
step_id="s1",
status="ok",
output_text="alpha-output",
usage={
"input_tokens": 12,
"output_tokens": 3,
"total_tokens": 15,
"cost_usd": 0.0042,
"estimated_cost_usd": 0.0042,
"billed_cost_usd": 0.0,
"cost_source": "opensquilla_estimate",
"model": "model-alpha",
},
)
writer.begin_step_sync(
run_id=run_id,
step=plan.steps[1],
effective_skill="beta",
rendered_inputs={"q": "y"},
)
writer.finish_step_sync(
run_id=run_id,
step_id="s2",
status="ok",
output_text="beta-output",
usage={
"input_tokens": 20,
"output_tokens": 5,
"total_tokens": 25,
"cost_usd": 0.01,
"billed_cost_usd": 0.01,
"cost_source": "provider_billed",
"model": "model-beta",
},
)
writer.finish_run_sync(
run_id=run_id,
status="ok",
result=MetaResult(ok=True, final_text="done"),
)
record = writer.get_run(run_id)
assert record is not None
summary = summarize_run_record(record)
assert summary["usage"]["available"] is True
assert summary["usage"]["input_tokens"] == 32
assert summary["usage"]["output_tokens"] == 8
assert summary["usage"]["total_tokens"] == 40
assert summary["usage"]["cost_usd"] == pytest.approx(0.0142)
assert summary["usage"]["cost_source"] == "mixed"
assert summary["steps"][0]["usage"]["model"] == "model-alpha"
assert summary["steps"][1]["usage"]["cost_source"] == "provider_billed"
def test_llm_chat_step_lifecycle(writer: MetaRunWriter) -> None:
plan = MetaPlan(
name="demo",
triggers=("demo trigger",),
priority=50,
steps=(MetaStep(id="baseline", skill="baseline", kind="llm_chat"),),
)
run_id = writer.begin_run_sync(
meta_skill_name=plan.name,
meta_plan=plan,
triggered_by="soft_meta_invoke",
inputs={"user_message": "x"},
session_key=None,
turn_id=None,
)
writer.begin_step_sync(
run_id=run_id,
step=plan.steps[0],
effective_skill="baseline",
rendered_inputs={"task": "same task"},
)
writer.finish_step_sync(
run_id=run_id,
step_id="baseline",
status="ok",
output_text="baseline-output",
)
steps = writer.get_steps(run_id)
assert len(steps) == 1
assert steps[0].step_kind == "llm_chat"
assert steps[0].status == "ok"
assert steps[0].output_text == "baseline-output"
def test_user_input_step_lifecycle(writer: MetaRunWriter) -> None:
plan = MetaPlan(
name="demo",
triggers=("demo trigger",),
priority=50,
steps=(MetaStep(id="collect", skill="collect", kind="user_input"),),
)
run_id = writer.begin_run_sync(
meta_skill_name=plan.name,
meta_plan=plan,
triggered_by="soft_meta_invoke",
inputs={"user_message": "x"},
session_key=None,
turn_id=None,
)
writer.begin_step_sync(
run_id=run_id,
step=plan.steps[0],
effective_skill="collect",
rendered_inputs={"topic": "travel"},
)
writer.finish_step_sync(
run_id=run_id,
step_id="collect",
status="ok",
output_text="collected",
)
steps = writer.get_steps(run_id)
assert len(steps) == 1
assert steps[0].step_kind == "user_input"
assert steps[0].status == "ok"
assert steps[0].output_text == "collected"
def test_on_step_failover_records_substitution(writer: MetaRunWriter) -> None:
"""C3: original failed step gets status='substituted' + substitute_step_id."""
plan = _make_plan()
run_id = writer.begin_run_sync(
meta_skill_name=plan.name,
meta_plan=plan,
triggered_by="hard_takeover",
inputs={},
session_key=None,
turn_id=None,
)
writer.begin_step_sync(
run_id=run_id, step=plan.steps[0], effective_skill="alpha", rendered_inputs={},
)
writer.on_step_failover_sync(
run_id=run_id,
failed_step_id="s1",
substitute_step_id="s_fallback",
error="alpha exploded",
)
steps = {s.step_id: s for s in writer.get_steps(run_id)}
assert steps["s1"].status == "substituted"
assert steps["s1"].substitute_step_id == "s_fallback"
assert steps["s1"].error == "alpha exploded"
def test_truncate_64kib_utf8_boundary() -> None:
"""W4/§4.2: truncate clips at UTF-8 boundary safely."""
multibyte = "中" * 30000 # each char = 3 bytes, total 90 KB
out, truncated = _truncate(multibyte, "x", max_bytes=64 * 1024)
assert truncated
assert out is not None
encoded = out.encode("utf-8")
assert len(encoded) <= 64 * 1024
# No malformed UTF-8: round-tripping must succeed
encoded.decode("utf-8")
def test_truncate_passthrough_for_small() -> None:
out, truncated = _truncate("hello", "x", max_bytes=64 * 1024)
assert not truncated
assert out == "hello"
def test_redactor_redacts_secret_keys() -> None:
raw = {
"user_message": "tell me about cats",
"api_key": "sk-abc123",
"nested": {"token": "Bearer xyz", "color": "blue"},
"AUTH_HEADER": "Bearer real-secret",
}
out = _redact_inputs_json(raw, max_bytes=64 * 1024)
parsed = json.loads(out)
assert parsed["user_message"] == "tell me about cats"
assert parsed["api_key"] == "[REDACTED]"
assert parsed["nested"]["token"] == "[REDACTED]"
assert parsed["nested"]["color"] == "blue"
assert parsed["AUTH_HEADER"] == "[REDACTED]"
def test_redactor_clips_large_strings() -> None:
raw = {"huge": "x" * 10_000}
out = _redact_inputs_json(raw, max_bytes=64 * 1024)
parsed = json.loads(out)
assert len(parsed["huge"]) <= 4100 # 4 KiB + suffix
def test_redactor_total_size_budget() -> None:
raw = {f"k{i}": "x" * 200 for i in range(1000)}
out = _redact_inputs_json(raw, max_bytes=4 * 1024)
assert len(out.encode("utf-8")) <= 4 * 1024 + 64 # tiny overhead allowed
parsed = json.loads(out)
assert parsed.get("_redaction_overflow") is True
def test_ulid_known_vector_length_and_alphabet() -> None:
"""I4: ULIDs are 26-char Crockford-base32 (no I, L, O, U)."""
forbidden = set("ILOU")
for _ in range(100):
u = _gen_ulid()
assert len(u) == 26
assert all(c.isalnum() for c in u)
assert not (set(u.upper()) & forbidden)
def test_ulid_same_ms_collision_uniqueness() -> None:
"""I4: 1000 ULIDs minted in a tight loop must all be unique."""
ids = {_gen_ulid() for _ in range(1000)}
assert len(ids) == 1000
def test_ulid_lexicographic_order_matches_time() -> None:
"""I3: time-ordered ULIDs sort lexicographically same as by start time."""
import time
pairs = []
for _ in range(20):
pairs.append((time.time_ns(), _gen_ulid()))
time.sleep(0.005)
sorted_by_time = [u for _, u in sorted(pairs)]
sorted_by_ulid = sorted([u for _, u in pairs])
assert sorted_by_time == sorted_by_ulid
def test_serialize_plan_deterministic() -> None:
"""C5: plan snapshot + digest must be deterministic for same plan."""
plan1 = _make_plan()
plan2 = _make_plan()
snap1, dig1 = _serialize_plan(plan1)
snap2, dig2 = _serialize_plan(plan2)
assert snap1 == snap2
assert dig1 == dig2
assert len(dig1) == 64
def test_thread_safety_executor(writer: MetaRunWriter) -> None:
"""W1 v2: writer must survive cross-thread access from default ThreadPoolExecutor."""
plan = _make_plan()
run_id = writer.begin_run_sync(
meta_skill_name=plan.name, meta_plan=plan,
triggered_by="soft_meta_invoke", inputs={}, session_key=None, turn_id=None,
)
def _do_step(i: int) -> None:
step = MetaStep(id=f"par{i}", skill=f"s{i}", kind="agent")
writer.begin_step_sync(
run_id=run_id, step=step, effective_skill=f"s{i}", rendered_inputs={},
)
writer.finish_step_sync(
run_id=run_id, step_id=f"par{i}", status="ok", output_text=f"o{i}",
)
# ThreadPoolExecutor default (multi-thread) — used to fail with check_same_thread=True
with ThreadPoolExecutor(max_workers=5) as ex:
list(ex.map(_do_step, range(20)))
steps = writer.get_steps(run_id)
assert len(steps) == 20
def test_cancelled_status_distinct(writer: MetaRunWriter) -> None:
"""W5: cancelled is distinct from failed and ok."""
plan = _make_plan()
run_id = writer.begin_run_sync(
meta_skill_name=plan.name, meta_plan=plan,
triggered_by="soft_meta_invoke", inputs={}, session_key=None, turn_id=None,
)
writer.finish_run_sync(run_id=run_id, status="cancelled", result=None)
record = writer.get_run(run_id)
assert record is not None
assert record.status == "cancelled"
assert record.final_text is None
def test_writer_failures_dont_raise(tmp_path: Path) -> None:
"""Fail-open contract: writer methods log + swallow."""
db = str(tmp_path / "test.db")
apply_pending(db, MIGRATIONS_DIR)
w = open_meta_run_writer(db)
w.close() # connection now closed
# Subsequent calls must not raise
assert w.begin_run_sync(
meta_skill_name="demo",
meta_plan=_make_plan(),
triggered_by="soft_meta_invoke",
inputs={},
session_key=None,
turn_id=None,
) is None
w.begin_step_sync(
run_id="bogus", step=MetaStep(id="s", skill="x", kind="agent"),
effective_skill="x", rendered_inputs={},
) # silently no-ops
def test_purge_for_session_cascades(writer: MetaRunWriter) -> None:
plan = _make_plan()
run_id = writer.begin_run_sync(
meta_skill_name=plan.name, meta_plan=plan,
triggered_by="soft_meta_invoke", inputs={}, session_key="sess-purge", turn_id=None,
)
writer.begin_step_sync(
run_id=run_id, step=plan.steps[0], effective_skill="alpha", rendered_inputs={},
)
writer.finish_step_sync(run_id=run_id, step_id="s1", status="ok", output_text="x")
writer.finish_run_sync(run_id=run_id, status="ok", result=None)
removed = writer.purge_for_session("sess-purge")
assert removed == 1
assert writer.get_run(run_id) is None
assert writer.get_steps(run_id) == []
def test_list_runs_filtering_and_ordering(writer: MetaRunWriter) -> None:
plan = _make_plan()
ids = []
for i in range(5):
rid = writer.begin_run_sync(
meta_skill_name=plan.name, meta_plan=plan,
triggered_by="soft_meta_invoke", inputs={"i": i},
session_key=f"s{i % 2}", turn_id=None,
)
writer.finish_run_sync(run_id=rid, status="ok" if i % 2 == 0 else "failed", result=None)
ids.append(rid)
all_runs = writer.list_runs(limit=10)
assert len(all_runs) == 5
# I3: list ordered by started_at_ms DESC, run_id DESC → newest first
assert all_runs[0].run_id == ids[-1]
failed = writer.list_runs(status="failed")
assert len(failed) == 2
assert all(r.status == "failed" for r in failed)
by_session = writer.list_runs(session_key="s0")
assert len(by_session) == 3
# ---------------------------------------------------------------------------
# Retention prune (opportunistic, write-time)
# ---------------------------------------------------------------------------
_DAY_MS = 24 * 60 * 60 * 1000
def _open_clocked_writer(
tmp_path: Path,
*,
retention_days: int = 90,
prune_every: int = 1,
) -> tuple[MetaRunWriter, dict[str, int]]:
"""Migrated writer with a mutable fake clock for retention tests."""
import sqlite3
db = str(tmp_path / "retention.db")
apply_pending(db, MIGRATIONS_DIR)
clock = {"now_ms": 0}
conn = sqlite3.connect(db, check_same_thread=False, isolation_level=None)
conn.row_factory = sqlite3.Row
conn.execute("PRAGMA foreign_keys = ON")
writer = MetaRunWriter(
conn,
retention_days=retention_days,
prune_every=prune_every,
clock=lambda: clock["now_ms"],
)
return writer, clock
def _begin(writer: MetaRunWriter, *, session_key: str | None) -> str:
plan = _make_plan()
run_id = writer.begin_run_sync(
meta_skill_name=plan.name, meta_plan=plan,
triggered_by="auto_cron", inputs={},
session_key=session_key, turn_id=None,
)
assert run_id is not None
return run_id
def test_retention_prunes_old_terminal_runs_and_cascades_steps(tmp_path: Path) -> None:
writer, clock = _open_clocked_writer(tmp_path, retention_days=90, prune_every=1)
try:
plan = _make_plan()
old_ok = _begin(writer, session_key="s-old-ok")
writer.begin_step_sync(
run_id=old_ok, step=plan.steps[0], effective_skill="alpha", rendered_inputs={},
)
writer.finish_step_sync(run_id=old_ok, step_id="s1", status="ok", output_text="x")
writer.finish_run_sync(run_id=old_ok, status="ok", result=None)
old_failed = _begin(writer, session_key="s-old-failed")
writer.finish_run_sync(
run_id=old_failed, status="failed",
result=MetaResult(ok=False, error="boom"),
)
# Live/parked rows must survive regardless of age.
old_running = _begin(writer, session_key="s-old-running")
old_awaiting = _begin(writer, session_key="s-old-awaiting")
assert writer.try_claim_awaiting(
run_id=old_awaiting, step_id="collect", schema_json="{}",
session_id="s-old-awaiting", inputs_json="{}",
step_outputs_json="{}", awaiting_since=1.0,
)
clock["now_ms"] = 91 * _DAY_MS
fresh = _begin(writer, session_key="s-fresh") # prune_every=1 → prunes now
assert writer.get_run(old_ok) is None
assert writer.get_steps(old_ok) == [] # FK CASCADE removed the steps
assert writer.get_run(old_failed) is None
running = writer.get_run(old_running)
assert running is not None and running.status == "running"
awaiting = writer.get_run(old_awaiting)
assert awaiting is not None and awaiting.status == "awaiting_user"
assert writer.get_run(fresh) is not None
finally:
writer.close()
def test_retention_keeps_terminal_runs_inside_window(tmp_path: Path) -> None:
writer, clock = _open_clocked_writer(tmp_path, retention_days=90, prune_every=1)
try:
recent_ok = _begin(writer, session_key="s-recent")
writer.finish_run_sync(run_id=recent_ok, status="ok", result=None)
clock["now_ms"] = 89 * _DAY_MS
_begin(writer, session_key="s-trigger")
assert writer.get_run(recent_ok) is not None
finally:
writer.close()
def test_retention_prune_cadence_honours_prune_every(tmp_path: Path) -> None:
writer, clock = _open_clocked_writer(tmp_path, retention_days=90, prune_every=3)
try:
old = _begin(writer, session_key="s-old") # begin #1 — no prune
writer.finish_run_sync(run_id=old, status="ok", result=None)
clock["now_ms"] = 200 * _DAY_MS
_begin(writer, session_key="s-2") # begin #2 — still no prune
assert writer.get_run(old) is not None
_begin(writer, session_key="s-3") # begin #3 — prune fires
assert writer.get_run(old) is None
finally:
writer.close()
def test_retention_defaults_via_open_meta_run_writer_kwargs(tmp_path: Path) -> None:
db = str(tmp_path / "kwargs.db")
apply_pending(db, MIGRATIONS_DIR)
w = open_meta_run_writer(db, retention_days=7, prune_every=16)
try:
assert w._retention_days == 7
assert w._prune_every == 16
finally:
w.close()
# Backward-compatible positional-only call keeps working with defaults.
w2 = open_meta_run_writer(db)
try:
assert w2._retention_days == 90
assert w2._prune_every == 64
finally:
w2.close()
# ---------------------------------------------------------------------------
# finish_run_sync status guard
# ---------------------------------------------------------------------------
def test_finish_run_sync_cannot_clobber_awaiting_user(writer: MetaRunWriter) -> None:
plan = _make_plan()
run_id = writer.begin_run_sync(
meta_skill_name=plan.name, meta_plan=plan,
triggered_by="soft_meta_invoke", inputs={},
session_key="s-guard", turn_id=None,
)
assert writer.try_claim_awaiting(
run_id=run_id, step_id="collect", schema_json="{}",
session_id="s-guard", inputs_json="{}",
step_outputs_json="{}", awaiting_since=1.0,
)
# Late finalize (e.g. a stale stream teardown) must lose the race.
writer.finish_run_sync(
run_id=run_id, status="ok",
result=MetaResult(ok=True, final_text="late finalize"),
)
record = writer.get_run(run_id)
assert record is not None
assert record.status == "awaiting_user"
assert record.final_text is None
def test_finish_run_sync_running_to_terminal_still_works(writer: MetaRunWriter) -> None:
plan = _make_plan()
run_id = writer.begin_run_sync(
meta_skill_name=plan.name, meta_plan=plan,
triggered_by="soft_meta_invoke", inputs={},
session_key="s-normal", turn_id=None,
)
writer.finish_run_sync(
run_id=run_id, status="ok",
result=MetaResult(ok=True, final_text="done"),
)
record = writer.get_run(run_id)
assert record is not None
assert record.status == "ok"
assert record.final_text == "done"
def test_finish_run_sync_allows_confirmed_preflight_rerun(writer: MetaRunWriter) -> None:
"""The preflight-confirmation flow re-runs a row parked as
cancelled/preflight_required (see _is_confirmable_preflight_run) and
must still be able to finalize it."""
plan = _make_plan()
run_id = writer.begin_run_sync(
meta_skill_name=plan.name, meta_plan=plan,
triggered_by="soft_meta_invoke", inputs={},
session_key="s-preflight", turn_id=None,
)
writer.finish_run_sync(
run_id=run_id, status="cancelled",
result=MetaResult(ok=False, error="preflight_required"),
)
writer.finish_run_sync(
run_id=run_id, status="ok",
result=MetaResult(ok=True, final_text="confirmed run"),
)
record = writer.get_run(run_id)
assert record is not None
assert record.status == "ok"
assert record.final_text == "confirmed run"
def test_finish_run_sync_does_not_clobber_user_cancelled(writer: MetaRunWriter) -> None:
plan = _make_plan()
run_id = writer.begin_run_sync(
meta_skill_name=plan.name, meta_plan=plan,
triggered_by="soft_meta_invoke", inputs={},
session_key="s-cancel", turn_id=None,
)
assert writer.try_claim_awaiting(
run_id=run_id, step_id="collect", schema_json="{}",
session_id="s-cancel", inputs_json="{}",
step_outputs_json="{}", awaiting_since=1.0,
)
writer.mark_cancelled(run_id=run_id, reason="user_cancel")
writer.finish_run_sync(run_id=run_id, status="ok", result=None)
record = writer.get_run(run_id)
assert record is not None
assert record.status == "cancelled"
assert record.error == "cancelled:user_cancel"
def test_finish_run_sweeps_steps_left_running(writer: MetaRunWriter) -> None:
"""A cancelled turn can abandon an offloaded step write; finalizing the
run must sweep still-running step rows so the record never reports
in-flight work under a terminal run."""
plan = _make_plan()
run_id = writer.begin_run_sync(
meta_skill_name=plan.name,
meta_plan=plan,
triggered_by="soft_meta_invoke",
inputs={},
session_key="sess-sweep",
turn_id=None,
)
assert run_id is not None
done_step, abandoned_step = plan.steps[0], plan.steps[1]
writer.begin_step_sync(
run_id=run_id, step=done_step, effective_skill=done_step.skill, rendered_inputs={},
)
writer.finish_step_sync(
run_id=run_id, step_id=done_step.id, status="ok", output_text="fine",
)
writer.begin_step_sync(
run_id=run_id,
step=abandoned_step,
effective_skill=abandoned_step.skill,
rendered_inputs={},
)
writer.finish_run_sync(run_id=run_id, status="cancelled", result=None)
steps = {step.step_id: step for step in writer.get_steps(run_id)}
assert steps[done_step.id].status == "ok" # completed steps untouched
swept = steps[abandoned_step.id]
assert swept.status == "failed"
assert swept.error == "run finalized before step completed"
assert swept.ended_at_ms is not None
def test_mark_orphans_repairs_running_steps_under_terminal_runs(
writer: MetaRunWriter,
) -> None:
"""A step INSERT abandoned by a cancelled turn can land after the run's
finalize sweep; the boot cleanup must repair it. Parked awaiting_user
runs keep their in-flight step untouched."""
plan = _make_plan()
run_id = writer.begin_run_sync(
meta_skill_name=plan.name,
meta_plan=plan,
triggered_by="soft_meta_invoke",
inputs={},
session_key="sess-orphan-step",
turn_id=None,
)
assert run_id is not None
writer.finish_run_sync(run_id=run_id, status="cancelled", result=None)
# Simulate the late, abandoned begin_step INSERT landing post-finalize.
late_step = plan.steps[0]
writer.begin_step_sync(
run_id=run_id, step=late_step, effective_skill=late_step.skill, rendered_inputs={},
)
assert writer.get_steps(run_id)[0].status == "running"
writer.mark_orphans_failed()
swept = writer.get_steps(run_id)[0]
assert swept.status == "failed"
assert swept.error == "run finalized before step completed"
assert swept.ended_at_ms is not None