Files
nisse/tests/integration/test_crash_durability.py
T
dongarandClaude Opus 5.5 154a0c3fa8
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m42s
CI / e2e (pull_request) Successful in 4m0s
M7.1: extract nisse-core
What isn't the dev agent's moves to nisse-core, unchanged in behaviour,
with its tests: canonical, render, render_anthropic, llm, catalog, probe,
context, views, state, reconcile, worker, cancel, dump, transport, matrix,
execution, testing, vault, memory, oplog, envfile and gitea (whole).

- nisse_core.state.StateStore holds the tables every agent has; an
  agent's subclass adds its tables (version, migrations), roles and
  views. nisse_dev.state.StateStore adds the build VM blocks (v5).
- views.project takes what each role is shown, rather than a fixed table.
- Vault takes the agent's name (agent=), for provenance.
- A test that nisse_core never imports an agent.
- The loop, config, git, delegation, expert, tools and evals stay in
  nisse-dev until nisse-ops has a loop to share.

Docs: the Development Plan marks M7.1 done and notes that Phase 6 waits on
the agent host and Terraform; Architecture Part L names the package.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-13 18:37:01 +00:00

54 lines
1.7 KiB
Python

"""Integration: state written before an abrupt process death must be intact
and the in-flight intent visible on the next boot (architecture L8)."""
import subprocess
import sys
from pathlib import Path
import pytest
from nisse_core.reconcile import reconcile_on_boot
from nisse_dev.state import StateStore
pytestmark = pytest.mark.integration
CRASH_SCRIPT = """
import os
import sys
from nisse_core.canonical import UserMessage
from nisse_dev.state import StateStore
store = StateStore(sys.argv[1])
store.open_work_item("demo-1", title="t", scope="demo", position="RUNNING", data={"milestone": 2}, cause="test")
store.set_active("demo-1", cause="test")
store.begin_intent("push_branch", {"branch": "milestone-2"}, cause="test")
store.transcript("t", "operator").append(UserMessage(text="start milestone 2", source="m:1"))
os._exit(1) # die without closing anything, as a power cut would
"""
def test_state_survives_process_death(tmp_path: Path) -> None:
db = tmp_path / "state.db"
result = subprocess.run(
[sys.executable, "-c", CRASH_SCRIPT, str(db)],
capture_output=True,
text=True,
timeout=30,
check=False,
)
assert result.returncode == 1, result.stderr
with StateStore(db) as store:
item = store.active_item()
assert item is not None
assert item.position == "RUNNING"
assert item.data == {"milestone": 2}
report = reconcile_on_boot(store, {"push_branch": lambda intent: None})
assert report.action == "await_input"
[pending] = store.pending_intents()
assert pending.payload == {"branch": "milestone-2"}
[stored] = store.all_events()
assert stored.event.text == "start milestone 2"