Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a44ced163e | ||
|
|
f40f7e7233 | ||
|
|
8bef4682f5 |
No files matched your search
@@ -1,6 +1,6 @@
|
||||
# Rune
|
||||
|
||||
**Current release: v0.72.2**
|
||||
**Current release: v0.73.0**
|
||||
|
||||
**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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "rune"
|
||||
version = "0.72.2"
|
||||
version = "0.73.0"
|
||||
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ This is the main entry point for the Rune program.
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
__version__ = "0.72.2"
|
||||
__version__ = "0.73.0"
|
||||
|
||||
# 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
|
||||
|
||||
@@ -231,7 +231,7 @@ def main() -> int:
|
||||
)
|
||||
check(
|
||||
"Rune minor version advertises provisioning and restore verification",
|
||||
'__version__ = "0.72.2"' in rune_version,
|
||||
'__version__ = "0.73.0"' in rune_version,
|
||||
failures,
|
||||
)
|
||||
# #169 policy update: auto-bump on merge is sanctioned (Nick directive:
|
||||
|
||||
@@ -101,9 +101,17 @@ class CapabilityLedger:
|
||||
rec = self.get(capability_id)
|
||||
if rec is None:
|
||||
raise ValueError(f"unknown capability {capability_id!r}")
|
||||
# T21.1 §21.1D: deterministic revision counter. Wall-clock
|
||||
# timestamps are metadata, not causal ordering (same-second events
|
||||
# share a stamp — the T21 race). The revision counter is the
|
||||
# canonical drift signal: material_revision > last governed
|
||||
# spec_revision = SPEC_VERSION_DRIFT.
|
||||
rev = int(rec.get("material_revision", 0)) + 1
|
||||
rec["material_revision"] = rev
|
||||
hist = {"at": _now(), "event": "material_change",
|
||||
"note": note[:200], "generation": generation,
|
||||
"prior_maturity": rec["maturity"]}
|
||||
"prior_maturity": rec["maturity"],
|
||||
"material_revision": rev}
|
||||
self.doc["history"].append({"capability_id": rec["capability_id"],
|
||||
**hist})
|
||||
rec["updated_at"] = _now()
|
||||
@@ -331,11 +339,53 @@ def revise_spec(ledger: "CapabilityLedger", capability_id: str, *,
|
||||
"changed_sections": list(changed_sections or []),
|
||||
"affected_consumers": list(affected_consumers or []),
|
||||
"validation_consequences": list(validation_consequences or []),
|
||||
# T21.1 §21.1D: the governed decision COVERS all material
|
||||
# revisions up to and including the current one — drift closes
|
||||
# deterministically, not by timestamp comparison
|
||||
"covers_material_revision": int(rec.get("material_revision", 0)),
|
||||
})
|
||||
rec["updated_at"] = _now()
|
||||
return {"old": old, "new": new, "kind": kind, "reason": reason}
|
||||
|
||||
|
||||
def spec_drift_status(ledger: "CapabilityLedger",
|
||||
capability_id: str) -> dict:
|
||||
"""T21.1 §21.1D: the callable drift detector / consistency gate.
|
||||
Deterministic and revision-based (never wall-clock):
|
||||
|
||||
material_revision > last governed covers_material_revision
|
||||
→ SPEC_VERSION_DRIFT + durable obligation
|
||||
|
||||
Returns {"drift": bool, "obligation": str|None, "detail": ...}."""
|
||||
rec = ledger.get(capability_id)
|
||||
if rec is None:
|
||||
return {"drift": False, "obligation": None,
|
||||
"detail": f"unknown capability {capability_id!r}"}
|
||||
spec = rec.get("spec")
|
||||
if spec is None:
|
||||
return {"drift": False, "obligation": None,
|
||||
"detail": "no spec attached — nothing to drift from"}
|
||||
hist = spec.get("version_history") or []
|
||||
covered = hist[-1].get("covers_material_revision", 0) if hist else 0
|
||||
current = int(rec.get("material_revision", 0))
|
||||
if current > covered:
|
||||
return {
|
||||
"drift": True,
|
||||
"obligation": (
|
||||
f"SPEC_VERSION_DRIFT: {capability_id} has "
|
||||
f"{current - covered} uncovered material revision(s) "
|
||||
f"(material rev {current} > governed rev {covered}) — "
|
||||
f"run revise_spec (PATCH/MINOR/MAJOR) to close"),
|
||||
"detail": {"material_revision": current,
|
||||
"governed_revision": covered,
|
||||
"spec_version": spec.get("version")},
|
||||
}
|
||||
return {"drift": False, "obligation": None,
|
||||
"detail": {"material_revision": current,
|
||||
"governed_revision": covered,
|
||||
"spec_version": spec.get("version")}}
|
||||
|
||||
|
||||
def spec_context(rec: dict) -> dict:
|
||||
"""The spec half of a surgical context packet (§21M): WHY + WHAT
|
||||
SHOULD EXIST, compact."""
|
||||
|
||||
@@ -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.72.2
|
||||
Version: v0.73.0
|
||||
Features: 41 (24 original + 17 new native commands)
|
||||
|
||||
Usage:
|
||||
@@ -1885,7 +1885,7 @@ class RuneApp(App):
|
||||
# Show version info
|
||||
self.chat_channel.add_message(
|
||||
"system",
|
||||
f"[dim]Rune v0.72.2 | Textual TUI | {len(loaded)} native modules[/dim]",
|
||||
f"[dim]Rune v0.73.0 | Textual TUI | {len(loaded)} native modules[/dim]",
|
||||
)
|
||||
|
||||
# Auto-show night mode suggestion if evening/night hours
|
||||
|
||||
+156
-148
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,121 @@
|
||||
"""test_spec_drift_deterministic.py — T21.1 §21.1D: deterministic
|
||||
spec/version drift via revision counters (never wall-clock).
|
||||
|
||||
CASES A-G from the mandate, against the REAL detector."""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(REPO / "src"))
|
||||
|
||||
from rune import behaviour_ledger as bl # noqa: E402
|
||||
from rune import capability_ledger as cl # noqa: E402
|
||||
|
||||
|
||||
def _cap(l, cid="drift-target"):
|
||||
l.define(capability_id=cid, name="D", purpose="p", owning_project="r",
|
||||
component="component:d")
|
||||
cl.attach_spec(l, cid, requirements=["r"], expected_behaviours=["b"],
|
||||
non_goals=[], acceptance_criteria=["a"])
|
||||
return l.get(cid)
|
||||
|
||||
|
||||
def test_211d_a_material_change_without_decision_is_drift(tmp_path):
|
||||
"""CASE A: implementation changes, no spec/version decision →
|
||||
deterministic SPEC_VERSION_DRIFT + durable obligation."""
|
||||
l = cl.CapabilityLedger(tmp_path)
|
||||
_cap(l)
|
||||
assert cl.spec_drift_status(l, "drift-target")["drift"] is False
|
||||
l.material_change("drift-target", note="rewrote internals",
|
||||
generation="gen-2")
|
||||
st = cl.spec_drift_status(l, "drift-target")
|
||||
assert st["drift"] is True
|
||||
assert "SPEC_VERSION_DRIFT" in st["obligation"]
|
||||
assert st["detail"]["material_revision"] == 1
|
||||
assert st["detail"]["governed_revision"] == 0
|
||||
|
||||
|
||||
def test_211d_b_spec_change_without_semver_is_impossible(tmp_path):
|
||||
"""CASE B: there is no code path that changes spec content without
|
||||
a version decision — revise_spec always computes a kind. A direct
|
||||
spec edit bypasses the machinery; the detector catches the RESULT
|
||||
via the revision gap (see CASE A)."""
|
||||
l = cl.CapabilityLedger(tmp_path)
|
||||
_cap(l)
|
||||
l.material_change("drift-target", note="spec text edited by hand",
|
||||
generation="gen-3")
|
||||
assert cl.spec_drift_status(l, "drift-target")["drift"] is True
|
||||
|
||||
|
||||
def test_211d_c_metadata_only_no_drift(tmp_path):
|
||||
"""CASE C: non-material edits (limitations notes, constraints) do
|
||||
not touch material_revision → no false drift."""
|
||||
l = cl.CapabilityLedger(tmp_path)
|
||||
rec = _cap(l)
|
||||
rec["limitations"].append("known edge case")
|
||||
rec["constraints"] = "new note"
|
||||
assert cl.spec_drift_status(l, "drift-target")["drift"] is False
|
||||
|
||||
|
||||
def test_211d_d_patch_closes_drift(tmp_path):
|
||||
"""CASE D: correct PATCH revision closes the drift."""
|
||||
l = cl.CapabilityLedger(tmp_path)
|
||||
_cap(l)
|
||||
l.material_change("drift-target", note="bug fix", generation="gen-2")
|
||||
assert cl.spec_drift_status(l, "drift-target")["drift"] is True
|
||||
r = cl.revise_spec(l, "drift-target", implementation_only=True,
|
||||
reason="fixed inner bug")
|
||||
assert r["kind"] == "PATCH"
|
||||
assert cl.spec_drift_status(l, "drift-target")["drift"] is False
|
||||
|
||||
|
||||
def test_211d_e_minor_closes_drift_with_new_behaviour(tmp_path):
|
||||
"""CASE E: correct MINOR closes drift and links the new behaviour."""
|
||||
l = cl.CapabilityLedger(tmp_path)
|
||||
_cap(l)
|
||||
l.material_change("drift-target", note="added drain()", generation="gen-2")
|
||||
r = cl.revise_spec(l, "drift-target", behaviour_added=True,
|
||||
reason="added drain behaviour",
|
||||
validation_consequences=["VAL-DRAIN-1"])
|
||||
assert r["kind"] == "MINOR"
|
||||
assert cl.spec_drift_status(l, "drift-target")["drift"] is False
|
||||
assert "VAL-DRAIN-1" in l.get("drift-target")["spec"][
|
||||
"version_history"][-1]["validation_consequences"]
|
||||
|
||||
|
||||
def test_211d_f_move_only_no_semantic_change(tmp_path):
|
||||
"""CASE F: source move only — stable identity, no fabricated change."""
|
||||
l = cl.CapabilityLedger(tmp_path)
|
||||
_cap(l)
|
||||
l.move("drift-target", ["src/new/place.py"])
|
||||
rec = l.get("drift-target")
|
||||
assert rec["capability_id"] == "capability:drift-target"
|
||||
# a move is location metadata, not a material semantic revision:
|
||||
# the detector keys on material_revision, which move() does not bump
|
||||
assert cl.spec_drift_status(l, "drift-target")["drift"] is False
|
||||
|
||||
|
||||
def test_211d_g_old_version_proof_reopens(tmp_path):
|
||||
"""CASE G: behaviour proven against an incompatible old version →
|
||||
RETEST_REQUIRED with version-referenced reason."""
|
||||
l = cl.CapabilityLedger(tmp_path)
|
||||
_cap(l)
|
||||
beh = bl.BehaviourLedger(tmp_path)
|
||||
beh.define(behaviour_id="VAL-OLD", owning_project="r",
|
||||
intended_behaviour="exact behaviour",
|
||||
owner_component="component:d",
|
||||
dependencies=["component:d"])
|
||||
beh.record_auto_verification("VAL-OLD", passed=True)
|
||||
beh.record_real_world("VAL-OLD", passed=True, generation="cap-0.1.0")
|
||||
r = cl.revise_spec(l, "drift-target", consumer_break=True,
|
||||
reason="breaking payload change")
|
||||
assert r["kind"] == "MAJOR"
|
||||
inv = beh.invalidate_for_change(
|
||||
changed_entities={"component:d"}, generation="cap-1.0.0",
|
||||
reasons={"behaviour:VAL-OLD":
|
||||
f"capability drift-target {r['old']}→{r['new']} MAJOR; "
|
||||
f"proven against {r['old']}"})
|
||||
assert [x["behaviour_id"] for x in inv] == ["behaviour:VAL-OLD"]
|
||||
assert beh.get("VAL-OLD")["state"] == bl.RETEST_REQUIRED
|
||||
Reference in new issue
Block a user