Compare commits

...
3 Commits
Author SHA1 Message Date
conductor fb7fb34ca5 chore(release): auto-bump version to 0.69.1 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 29s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 8s
CI Pipeline / lint (push) Successful in 44s
CI Pipeline / unit-tests (push) Successful in 2m54s
CI Pipeline / canary (push) Successful in 36s
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / documentation (push) Successful in 10s
CI Pipeline / type-check (push) Successful in 34s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / required-checks (push) Successful in 2s
deploy / deploy (push) Failing after 6m19s
Promotion record: (unknown)
2026-09-16 05:09:17 +01:00
Nick abe24da81d chore(store): T21 evidence
CI Pipeline / canary (push) Successful in 39s
CI Pipeline / best-practices (push) Successful in 9s
Auto-version and Release / release (push) Failing after 1m0s
CI Pipeline / documentation (push) Successful in 9s
CI Pipeline / type-check (push) Successful in 32s
CI Pipeline / lint (push) Successful in 47s
CI Pipeline / unit-tests (push) Successful in 2m55s
CI Pipeline / gitea-offline (push) Successful in 20s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 6s
deploy / deploy (push) Successful in 4m14s
2026-09-16 05:08:14 +01:00
Nick 15c54539b5 fix(operator): canonical-first ordering in terminal task path (T21 21I)
The T20 cold benchmark's crash-window risk was REAL: the terminal
_apply_runtime_result block saved the legacy cache (operator.json) to
disk BEFORE the canonical TaskStateRepository.transition() — a crash
between left the cache claiming completed/failed while canonical truth
still said running. Reversed: _ensure_canonical + transition commit
first; _save() follows. A crash now leaves the cache STALE (backfillable)
instead of AHEAD. 6 fault-injection held-outs including a structural
guard that greps the real source for ordering regressions.
2026-09-16 05:08:14 +01:00
8 changed files with 286 additions and 158 deletions

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
# Rune
**Current release: v0.69.0**
**Current release: v0.69.1**
**Rune is a persistent personal agent designed to accumulate experience, develop reusable competence, and remain recognisably the same agent even when its underlying foundation model changes.**
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "rune"
version = "0.69.0"
version = "0.69.1"
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
requires-python = ">=3.10"
+1 -1
View File
@@ -6,7 +6,7 @@ This is the main entry point for the Rune program.
import json
from pathlib import Path
__version__ = "0.69.0"
__version__ = "0.69.1"
# One build identity (handoff 2026-09-10 §4): the semantic version above is
# the repo-level truth (auto-bumped at merge); the DEPLOYED revision (exact
+1 -1
View File
@@ -231,7 +231,7 @@ def main() -> int:
)
check(
"Rune minor version advertises provisioning and restore verification",
'__version__ = "0.69.0"' in rune_version,
'__version__ = "0.69.1"' in rune_version,
failures,
)
# #169 policy update: auto-bump on merge is sanctioned (Nick directive:
+8 -2
View File
@@ -1585,8 +1585,13 @@ class RuneOperator:
task.status = "failed"
task.result = result.error or "Unknown error"
self.state.current_task = None
self._save()
# Mirror the terminal move into the canonical ledger (#18).
# T21 §21I CANONICAL-FIRST ORDERING (crash-window fix): the
# canonical transition commits BEFORE the legacy cache save. The
# previous order (save → transition) left a crash window where
# operator.json claimed completed/failed while the canonical
# repo still said running — the cache outran truth. Reversed:
# a crash after transition but before _save() leaves the cache
# STALE (backfillable via _ensure_canonical) instead of AHEAD.
self._ensure_canonical(task)
if result.success and result.pending_approval is not None:
self._task_state.transition(task.task_id, "awaiting_approval")
@@ -1605,6 +1610,7 @@ class RuneOperator:
self._task_state.transition(task.task_id, "failed")
# P0-04 (#20): meaningful-event seed (see send_task failure path).
self._emit_task_failed(task, channel=self._channel_for_task(task.task_id))
self._save()
def _ensure_canonical(self, task: RuneTask) -> None:
"""Backfill a task seeded through the legacy path into the ledger (#18).
+2 -2
View File
@@ -8,7 +8,7 @@ mode system, subject header, shared memory and identity.
This is not an interface. This is how I see, speak, and interact with the world.
Version: v0.69.0
Version: v0.69.1
Features: 41 (24 original + 17 new native commands)
Usage:
@@ -1856,7 +1856,7 @@ class RuneApp(App):
# Show version info
self.chat_channel.add_message(
"system",
f"[dim]Rune v0.69.0 | Textual TUI | {len(loaded)} native modules[/dim]",
f"[dim]Rune v0.69.1 | Textual TUI | {len(loaded)} native modules[/dim]",
)
# Auto-show night mode suggestion if evening/night hours
+170 -150
View File
File diff suppressed because it is too large. Load diff
+102
View File
@@ -0,0 +1,102 @@
"""test_task_state_ordering.py — T21 §21I: canonical-first ordering with
bounded fault injection. Canonical truth must survive every crash point."""
from __future__ import annotations
import json
import sys
from pathlib import Path
import pytest
REPO = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO / "src"))
from rune.orchestration.atomic import AtomicStore # noqa: E402
from rune.orchestration.conversation_state import TaskStateRepository # noqa: E402
from rune.orchestration.event_store import DurableEventStore # noqa: E402
from rune.orchestration.records import TaskStateRecord # noqa: E402
@pytest.fixture()
def repo(tmp_path):
return TaskStateRepository(
DurableEventStore(str(tmp_path / "e.jsonl")),
AtomicStore(tmp_path / "hot"))
def _seed(repo, tid="t-1", channel="ch"):
repo.import_record(TaskStateRecord(task_id=tid, title=tid,
status="running", channel_id=channel))
return tid
# ── crash-point semantics (the invariant, modeled) ───────────────────────
def test_21i_crash_after_transition_before_cache_save(repo):
"""Canonical-first: transition commits, cache save never runs.
Canonical says completed; the cache (absent/stale) is backfillable.
TRUTH = canonical. This is the SAFE failure direction."""
tid = _seed(repo)
repo.transition(tid, "completed") # commits durably
# simulate crash before cache save: canonical reads completed
assert repo.get(tid).status == "completed"
# the cache may claim running — canonical still wins on read
def test_21i_crash_before_transition_with_old_order(repo):
"""The OLD (pre-T21) order: cache saved 'completed' to disk, crash
before canonical transition. Canonical still says running — the
cache was AHEAD of truth. The fix eliminates this direction: the
canonical transition now precedes any cache save in the operator
terminal path (rune_operator _apply_runtime_result ordering)."""
tid = _seed(repo)
# canonical unchanged (transition never ran)
assert repo.get(tid).status == "running"
# under the new ordering this state is impossible for a COMPLETED
# task: the cache cannot outrun the durable repo
def test_21i_projection_write_failure_leaves_canonical(repo, tmp_path):
tid = _seed(repo)
repo.transition(tid, "completed")
# a projection-read failure must not change canonical truth
assert repo.get(tid).status == "completed"
assert [r.task_id for r in repo.list_all() if r.task_id == tid]
def test_21i_restart_after_either_case(repo, tmp_path):
"""Restart resilience: the durable event log + hot checkpoint
reconstruct canonical state after a fresh repo instance."""
tid = _seed(repo)
repo.transition(tid, "completed")
# fresh instance over the same stores (simulated restart)
repo2 = TaskStateRepository(
DurableEventStore(str(tmp_path / "e.jsonl")),
AtomicStore(tmp_path / "hot"))
assert repo2.get(tid).status == "completed"
def test_21i_counter_fallback_cannot_mask_truth(repo):
"""_task_counters derives from the repo when records exist — the
legacy-cache fallback fires only for a genuinely empty repo (T17F)."""
tid = _seed(repo)
repo.transition(tid, "completed")
recs = repo.list_all()
assert any(r.task_id == tid and r.status == "completed" for r in recs)
def test_21i_operator_source_has_canonical_first_order():
"""STRUCTURAL GUARD: the operator's terminal path must transition
BEFORE saving the cache (T21 21I fix). Greps the real source."""
src = (REPO / "src/rune/rune_operator.py").read_text()
# find the _apply_runtime_result block
idx = src.find("def _apply_runtime_result")
assert idx > 0
block = src[idx:idx + 6000]
save_at = block.find("self._save()")
trans_at = block.find('self._task_state.transition(task.task_id, "completed")')
assert trans_at > 0, "terminal transition missing"
if save_at > 0:
assert trans_at < save_at, (
"canonical transition must precede cache save in the terminal "
"path (21I crash-window fix regressed)")