Add durable state store and boot-time reconciliation (A2/A3) #2

Merged
dongar merged 1 commits from state-store-durability into main 2026-09-05 19:25:17 +00:00
Collaborator

Build-order step 1 (Part G): the state store + durability layer everything else sits on.

nisse_dev.state.StateStore (spec A2):

  • SQLite in WAL mode with synchronous=FULL — every commit is fsynced, so a power cut mid-write can never produce torn state
  • Checkpoint per transition: transition() writes the full state-machine position (state, project, data) as one immediately-committed statement
  • Durable intent log: begin_intent() records a unit of work before it starts; commit_intent/abort_intent resolve it; pending intents survive restart
  • schema_version from the first commit with a sequential migration hook; failed migrations roll back cleanly, and a state file newer than the code refuses to open

nisse_dev.reconcile.reconcile_on_boot (spec A3, first cut):

  • Pending intents are resolved against external truth via registered reconcilers — observable checks, never model self-assessment
  • Verified effect → intent committed (never repeated); verified no-effect → aborted; unverifiable or unknown kind → gate to AWAIT_INPUT, never blind-resume
  • Returns a BootReport with human-readable notes for the transport notice

Placement: in nisse_dev, not nisse-core — spec §0.1 says the library is extracted after the dev agent works (extraction is build step 10). The delegation-resume rule (A3 step 5) lands when delegations exist (step 7).

Tests: 18 new unit tests; an integration test spawns a subprocess that dies via os._exit mid-work and verifies state integrity plus intent visibility on the next boot. 22 unit + 3 integration green locally.

🤖 Generated with Claude Code

Build-order step 1 (Part G): the state store + durability layer everything else sits on. **`nisse_dev.state.StateStore`** (spec A2): - SQLite in **WAL mode with `synchronous=FULL`** — every commit is fsynced, so a power cut mid-write can never produce torn state - **Checkpoint per transition**: `transition()` writes the full state-machine position (state, project, data) as one immediately-committed statement - **Durable intent log**: `begin_intent()` records a unit of work before it starts; `commit_intent`/`abort_intent` resolve it; pending intents survive restart - **`schema_version` from the first commit** with a sequential migration hook; failed migrations roll back cleanly, and a state file newer than the code refuses to open **`nisse_dev.reconcile.reconcile_on_boot`** (spec A3, first cut): - Pending intents are resolved against external truth via registered **reconcilers** — observable checks, never model self-assessment - Verified effect → intent committed (never repeated); verified no-effect → aborted; unverifiable or unknown kind → **gate to `AWAIT_INPUT`**, never blind-resume - Returns a `BootReport` with human-readable notes for the transport notice **Placement**: in `nisse_dev`, not `nisse-core` — spec §0.1 says the library is extracted after the dev agent works (extraction is build step 10). The delegation-resume rule (A3 step 5) lands when delegations exist (step 7). **Tests**: 18 new unit tests; an integration test spawns a subprocess that dies via `os._exit` mid-work and verifies state integrity plus intent visibility on the next boot. 22 unit + 3 integration green locally. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
dev-agent added 1 commit 2026-09-05 18:59:26 +00:00
Add durable state store and boot-time reconciliation (A2/A3)
CI / checks (push) Successful in 8s
CI / checks (pull_request) Successful in 7s
4319882f35
Build-order step 1: the critical path everything else sits on.

- StateStore: SQLite in WAL mode with synchronous=FULL, so every
  state transition and intent-log write is committed and fsynced
  before returning; a power cut cannot tear state
- Durable intent log: begin_intent records a unit of work before it
  starts; commit/abort resolve it; pending intents survive restart
- schema_version stamped from the first commit, with a sequential
  migration hook; a newer state file than the code supports is
  refused rather than misread
- reconcile_on_boot (A3 first cut): pending intents are checked
  against external truth by registered reconcilers (observable
  checks, never model self-assessment); verified effects are
  committed, verified no-ops aborted, anything unverifiable gates
  to AWAIT_INPUT — never blind-resume
- Lives in nisse_dev per spec 0.1: the library is extracted after
  the dev agent works, not designed up front
- Integration test kills a subprocess mid-work with os._exit and
  verifies state integrity and intent visibility on reopen

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
dongar approved these changes 2026-09-05 19:25:11 +00:00
dongar merged commit 99a8c7b3a2 into main 2026-09-05 19:25:17 +00:00
dongar deleted branch state-store-durability 2026-09-05 19:25:17 +00:00
Sign in to join this conversation.
No Reviewers
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dev/nisse#2