Files
nisse/nisse-dev/src/nisse_dev/state.py
T
dongarandClaude Opus 5.5 e351d0a74e
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m37s
CI / e2e (pull_request) Successful in 4m5s
nisse-core: group modules into packages by architecture section
A pure move; behaviour unchanged.

- models (L9, L7): llm, catalog, probe, render, render_anthropic, context
- log (L7, L6): canonical, views
- state (L8): store (was state), reconcile, worker, cancel, dump
- knowledge (L10, L6.4): vault, memory
- conversation (L5): transport, matrix
- execution (L13): ssh (was execution), testing
- authority (P3, P4, L4): actions
- gitea, oplog, envfile stay at the top until each gains a second module.

Every import, monkeypatch target, script and doc reference rewritten.

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

99 lines
3.5 KiB
Python

"""nisse-dev's state store (architecture L8): the shared one, with the dev
agent's own roles, views and tables (build VM blocks, D3)."""
import sqlite3
from collections.abc import Mapping
from dataclasses import dataclass
from nisse_core.state import store as state
from nisse_core.state.store import Migration, utc_now
_VM_BLOCKS = """
CREATE TABLE vm_blocks (
host TEXT PRIMARY KEY,
cause TEXT NOT NULL,
reason TEXT NOT NULL,
work_item TEXT,
since TEXT NOT NULL
)
"""
def _add_vm_blocks(conn: sqlite3.Connection) -> None:
conn.execute(_VM_BLOCKS)
@dataclass(frozen=True)
class VMBlock:
"""A build VM nothing may run on until it is recovered (D3): every
project on it waits."""
host: str
cause: str # broken (the driver said so), unreachable, host key (#137)
reason: str
work_item: str | None # the item that found it
since: str
class StateStore(state.StateStore):
AGENT = "nisse-dev"
SCHEMA_VERSION = 5
SCHEMA = (_VM_BLOCKS,)
MIGRATIONS: Mapping[int, Migration] = {4: _add_vm_blocks}
# planner and executor are the old loop's (until M2.8), kept so its
# records still read.
ACTORS = state.ACTORS | {
"planner",
"executor",
"toolchain", # the toolchain step (D3): the driver's model, apart
"memory", # the memory pass at an item's close (L6.4), apart too
}
# What each role is shown of the others' work (``nisse_core.log.views``).
SHOWN: Mapping[str, Mapping[str, str]] = {
"driver": {"helper": "report"},
"helper": {"driver": "narrate"},
# The toolchain step works in its own transcript: the driver sees
# only its report (a tool result, or facts), the step the driver's
# work.
"toolchain": {"driver": "narrate"},
# The memory pass at an item's close (L6.4): the item as it went.
"memory": {"driver": "narrate"},
}
# --- build VMs (D3) ----------------------------------------------------
def block_vm(
self, host: str, *, cause: str, reason: str, work_item: str | None
) -> None:
"""Block a build VM; a second block on it keeps the first."""
row = self._conn.execute(
"INSERT INTO vm_blocks (host, cause, reason, work_item, since) "
"VALUES (?, ?, ?, ?, ?) ON CONFLICT(host) DO NOTHING RETURNING host",
(host, cause, reason, work_item, utc_now()),
).fetchone()
if row is not None:
self.oplog.emit("vm.block", cause=cause, host=host, work_item=work_item)
def reblock_vm(self, host: str, *, cause: str, reason: str) -> None:
"""A blocked VM's block, for another cause (an unreachable VM that
answers with a new host key)."""
self._conn.execute(
"UPDATE vm_blocks SET cause = ?, reason = ? WHERE host = ?",
(cause, reason, host),
)
self.oplog.emit("vm.block", cause=cause, host=host)
def unblock_vm(self, host: str, *, cause: str) -> VMBlock | None:
"""Lift a build VM's block; returns it, or None if there was none."""
row = self._conn.execute(
"DELETE FROM vm_blocks WHERE host = ? RETURNING *", (host,)
).fetchone()
if row is None:
return None
self.oplog.emit("vm.unblock", cause=cause, host=host)
return VMBlock(**dict(row))
def vm_blocks(self) -> dict[str, VMBlock]:
rows = self._conn.execute("SELECT * FROM vm_blocks").fetchall()
return {row["host"]: VMBlock(**dict(row)) for row in rows}