CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m22s
Development Plan M0.1. Every section reference in code, tests, specs/, the example config, the tuning guide, and the CLAUDE.md and README files now cites docs/Architecture.md (P, L, D, O, X, Q) instead of the old spec's A-H, and docs/Architecture-Build-Spec.md is deleted (git keeps it). The plan's old-to-new table goes with it. Mostly mechanical, with a per-site choice where one old section split: A4 became L2 where it meant stop/abandon/the loop and L3 where it meant approvals; A10 became L6 in the context-budget code and L7 elsewhere; B2 became D4 for outbound Gitea and publishing and D5 for review intake; 'A3 step N' became the matching step of L8's boot sequence. Comments describing behaviour the architecture drops (planning always escalating, thresholds tuned at an old build step) now describe the current code and point at the Development Plan. docs/Architecture.md gains one line the rewrite missed: intake outages delay but never fail work (old B2), now in L5, where the poller and Matrix code cite it. Comments and docs only; no behaviour change. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
66 lines
2.2 KiB
Python
66 lines
2.2 KiB
Python
"""Integration: a restart mid-gate resumes the open gate with its revision
|
|
history intact; unresolved intents gate the whole boot to AWAIT_INPUT
|
|
(architecture L8 and L3 together)."""
|
|
|
|
import io
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from nisse_dev.control import (
|
|
AWAIT_ARCH_SIGNOFF,
|
|
AWAIT_INPUT,
|
|
AWAIT_MILESTONE_SIGNOFF,
|
|
Orchestrator,
|
|
StubAgent,
|
|
)
|
|
from nisse_dev.oplog import OpLog
|
|
from nisse_dev.state import StateStore
|
|
from nisse_dev.transport import InMemoryTransport
|
|
|
|
pytestmark = pytest.mark.integration
|
|
|
|
|
|
def boot(db: Path) -> tuple[Orchestrator, StateStore, InMemoryTransport]:
|
|
store = StateStore(db, oplog=OpLog("nisse-dev", io.StringIO()))
|
|
transport = InMemoryTransport()
|
|
orch = Orchestrator(store, transport, StubAgent(), operators={"don"})
|
|
orch.start()
|
|
return orch, store, transport
|
|
|
|
|
|
def test_restart_mid_gate_resumes_with_revisions_intact(tmp_path: Path) -> None:
|
|
db = tmp_path / "state.db"
|
|
_, store, transport = boot(db)
|
|
transport.receive("don", "use demo")
|
|
transport.receive("don", "needs work") # arch gate now at rev-2
|
|
task_id = store.oplog.task_id
|
|
store.close()
|
|
|
|
_, store2, transport2 = boot(db)
|
|
state = store2.get_state()
|
|
assert state is not None
|
|
assert state.state == AWAIT_ARCH_SIGNOFF
|
|
assert store2.oplog.task_id == task_id # correlation survives restart
|
|
notices = [m.text for m in transport2.outbound]
|
|
assert any("rev-2" in n and "still open" in n for n in notices)
|
|
|
|
transport2.receive("don", "approve rev-1") # stale across the restart too
|
|
assert store2.get_state().state == AWAIT_ARCH_SIGNOFF
|
|
transport2.receive("don", "approve rev-2")
|
|
assert store2.get_state().state == AWAIT_MILESTONE_SIGNOFF
|
|
|
|
|
|
def test_restart_with_unresolved_intent_gates_boot(tmp_path: Path) -> None:
|
|
db = tmp_path / "state.db"
|
|
_, store, transport = boot(db)
|
|
transport.receive("don", "use demo")
|
|
store.begin_intent("push_branch", {"branch": "m1"}, cause="test")
|
|
store.close()
|
|
|
|
_, store2, transport2 = boot(db)
|
|
assert store2.get_state().state == AWAIT_INPUT
|
|
question = transport2.outbound[-1]
|
|
assert question.kind == "question"
|
|
assert "unresolved work" in question.text
|