Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3497d2b7a9 | ||
|
|
60a21dc7da |
No files matched your search
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: land
|
||||
description: Review and land one small Rune change through the governed PR workflow.
|
||||
disable-model-invocation: true
|
||||
metadata:
|
||||
delta-action: land
|
||||
---
|
||||
|
||||
# Land one small diff
|
||||
|
||||
1. Read applicable `AGENTS.md` rules and the repo's PR/merge workflow.
|
||||
2. Confirm the issue, exact diff, target branch, and permission to publish and land.
|
||||
3. Keep one small, reversible change; preserve unrelated work.
|
||||
4. For bugs, follow `docs/bugfix-contract.md`: red test first, root cause, discharge.
|
||||
5. Run required tests, static analysis, canaries, and henxels; record results.
|
||||
6. Get independent `/review` per `docs/adversarial-review.md`; clear blockers.
|
||||
7. Inspect remotes; publish the PR to the approved forge, never `local`.
|
||||
8. Require human approval and green required CI on the exact PR head; recheck changed heads.
|
||||
9. Load `report-landed`; use the repo's merge workflow. Stop on conflicts; never force-push or bypass hooks.
|
||||
10. Verify the target commit and CI, then report the actual landing result and remaining blockers.
|
||||
@@ -81,7 +81,6 @@ jobs:
|
||||
SC_RUST_LIB: /tmp/rune-ci-target-${{ github.run_id }}/release/libscaffold_core.so
|
||||
MEMS_RUST_LIB: /tmp/rune-ci-target-${{ github.run_id }}/release/libmemory_storage.so
|
||||
MR_RUST_LIB: /tmp/rune-ci-target-${{ github.run_id }}/release/libmemory_retrieval.so
|
||||
CC_RUST_LIB: /tmp/rune-ci-target-${{ github.run_id }}/release/libcognition_core.so
|
||||
steps:
|
||||
# actions/checkout v4.2.2, pinned to the audited upstream tag commit.
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
@@ -136,7 +135,6 @@ jobs:
|
||||
cargo build --release --manifest-path src/rust/scaffold_core/Cargo.toml
|
||||
cargo build --release --manifest-path src/rust/memory_storage/Cargo.toml
|
||||
cargo build --release --manifest-path src/rust/memory_retrieval/Cargo.toml
|
||||
cargo build --release --manifest-path src/rust/cognition_core/Cargo.toml
|
||||
- name: Test with coverage (global floor + changed-lines ratchet)
|
||||
run: |
|
||||
# #149 coverage ratchet — both floors live in .coveragerc; raise,
|
||||
@@ -226,7 +224,6 @@ jobs:
|
||||
cargo build --release --manifest-path src/rust/scaffold_core/Cargo.toml
|
||||
cargo build --release --manifest-path src/rust/memory_storage/Cargo.toml
|
||||
cargo build --release --manifest-path src/rust/memory_retrieval/Cargo.toml
|
||||
cargo build --release --manifest-path src/rust/cognition_core/Cargo.toml
|
||||
- name: Run canary suite
|
||||
run: |
|
||||
FAILED=0
|
||||
@@ -265,9 +262,6 @@ jobs:
|
||||
src/rust/scaffold_core/canary_scaffold_core.py \
|
||||
src/rust/memory_storage/canary_memory_storage.py \
|
||||
src/rust/memory_retrieval/canary_memory_retrieval.py \
|
||||
src/rust/cognition_core/canary_growth_pins.py \
|
||||
src/rust/cognition_core/canary_persona.py \
|
||||
src/rust/cognition_core/canary_cognition_core.py \
|
||||
src/rune/canary_unbounded.py \
|
||||
src/rune/canary_full_system.py \
|
||||
src/rune/canary_native.py; do
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Rune
|
||||
|
||||
**Current release: v0.99.9**
|
||||
**Current release: v0.99.5**
|
||||
|
||||
**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.99.9"
|
||||
version = "0.99.5"
|
||||
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.99.9"
|
||||
__version__ = "0.99.5"
|
||||
|
||||
# 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
|
||||
|
||||
@@ -233,7 +233,7 @@ def main() -> int:
|
||||
)
|
||||
check(
|
||||
"Rune minor version advertises provisioning and restore verification",
|
||||
'__version__ = "0.99.9"' in rune_version,
|
||||
'__version__ = "0.99.5"' in rune_version,
|
||||
failures,
|
||||
)
|
||||
# #169 policy update: auto-bump on merge is sanctioned (Nick directive:
|
||||
|
||||
@@ -770,6 +770,7 @@ class ConstructionInterface:
|
||||
material_closure = self._auto_material_change(
|
||||
scope_files or [],
|
||||
note=f"contract {contract_id} closed (material change)")
|
||||
self._record_impact_delta(contract_id, scope_files or [])
|
||||
continuation = self._programme_continuation(contract_id)
|
||||
state = {
|
||||
"phase": "closed",
|
||||
@@ -789,6 +790,30 @@ class ConstructionInterface:
|
||||
state=state,
|
||||
)
|
||||
|
||||
def _record_impact_delta(self, contract_id: str, scope_files: list) -> None:
|
||||
"""T5.15 slice 4 (the iteration wire): a material closure records a
|
||||
predicted-vs-observed impact delta (rule 19H — deltas compound into
|
||||
better future prediction). Governance-only closes (no scope files)
|
||||
record nothing. Failure degrades: the ledger never fails a close."""
|
||||
if not scope_files:
|
||||
return
|
||||
try:
|
||||
from rune import iteration as it
|
||||
|
||||
it.record_impact_delta(
|
||||
Path(self.scaffold.current).parent,
|
||||
{
|
||||
"contract_id": contract_id,
|
||||
"scope_files": sorted(scope_files),
|
||||
"predicted_frontier": "from-contract-scope",
|
||||
"recorded_at": ss.now(),
|
||||
},
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001 — delta failure never fails a closure
|
||||
self._record_session(
|
||||
self.scaffold.load(), "impact_delta_degraded",
|
||||
f"{contract_id}: {str(exc)[:120]}")
|
||||
|
||||
def _programme_continuation(self, contract_id: str) -> dict | None:
|
||||
"""T5.15 slice 3 (the programme_continuation wire): after a legal
|
||||
close, any persisted Programme stage bound to this contract is
|
||||
|
||||
@@ -2525,6 +2525,53 @@ I have tools to read, write, edit, search, lint, test, format, and commit code.
|
||||
_operator = None
|
||||
|
||||
|
||||
def _forge_client_for_projection():
|
||||
"""The client seam for the forge projection (test-overridable)."""
|
||||
try:
|
||||
from .forge_client import create_forge_client
|
||||
except ImportError: # TUI top-level deploy (script mode)
|
||||
from forge_client import create_forge_client
|
||||
return create_forge_client()
|
||||
|
||||
|
||||
def _project_failure_to_forge(*, behaviour_id: str, observation: dict,
|
||||
repo: str) -> dict:
|
||||
"""T5.15 slice 5 (the forge projection wire): a recorded failure
|
||||
projects to the human-visible forge (rule 19P — the projection is a
|
||||
VIEW; canonical truth stays in the ledger). Uses the ALREADY-WIRED
|
||||
forge client (create_forge_client → NullAdapter when unconfigured, so
|
||||
the projection is dormant by construction). A thin shim adapts the
|
||||
client's create_issue to the testing-mode projection's
|
||||
create_or_patch_release contract. Forge failure NEVER breaks the
|
||||
failure path — it degrades to a recorded marker."""
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
try:
|
||||
client = _forge_client_for_projection()
|
||||
|
||||
class _Shim:
|
||||
"""Adapts create_issue → the projection's release contract."""
|
||||
|
||||
@staticmethod
|
||||
def create_or_patch_release(repo_: str, tag: str,
|
||||
title: str, body: str) -> dict:
|
||||
return client.create_issue(title, body)
|
||||
|
||||
try:
|
||||
from .testing_mode import project_failure_to_forge
|
||||
except ImportError:
|
||||
from testing_mode import project_failure_to_forge
|
||||
|
||||
return project_failure_to_forge(
|
||||
_Shim(), repo, behaviour_id=behaviour_id,
|
||||
observation=observation)
|
||||
except Exception as exc: # noqa: BLE001 — projection failure never rewrites truth
|
||||
log.debug("forge projection degraded: %s", exc)
|
||||
return {"projected": False,
|
||||
"error": str(exc)[:200]}
|
||||
|
||||
|
||||
def _wire_auto_filer() -> None:
|
||||
"""Composition-root wiring for the failure auto-filer (#122).
|
||||
|
||||
|
||||
@@ -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.99.9
|
||||
Version: v0.99.5
|
||||
Features: 41 (24 original + 17 new native commands)
|
||||
|
||||
Usage:
|
||||
@@ -1948,7 +1948,7 @@ class RuneApp(App):
|
||||
# Show version info
|
||||
self.chat_channel.add_message(
|
||||
"system",
|
||||
f"[dim]Rune v0.99.9 | Textual TUI | {len(loaded)} native modules[/dim]",
|
||||
f"[dim]Rune v0.99.5 | Textual TUI | {len(loaded)} native modules[/dim]",
|
||||
)
|
||||
|
||||
# Auto-show night mode suggestion if evening/night hours
|
||||
|
||||
@@ -624,10 +624,6 @@ fn regex_lite_alternatives(pattern: &str) -> Vec<String> {
|
||||
/// a set of variant strings (from `x?` optional chars — both shapes).
|
||||
fn regex_lite_segments(alt: &str) -> Vec<Vec<String>> {
|
||||
let mut segments: Vec<Vec<String>> = vec![];
|
||||
// Word-boundary marker: \b immediately before a phrase prefixes its
|
||||
// variants with US; \b immediately after suffixes them. The walker
|
||||
// verifies the boundary on the real text and strips the marker.
|
||||
const ANCHOR: char = '\u{1f}';
|
||||
let mut cur: Vec<String> = vec![String::new()]; // variants of current phrase
|
||||
let pc: Vec<char> = alt.chars().collect();
|
||||
let mut j = 0usize;
|
||||
@@ -636,35 +632,11 @@ fn regex_lite_segments(alt: &str) -> Vec<Vec<String>> {
|
||||
if c == '\\' && j + 1 < pc.len() {
|
||||
let n = pc[j + 1];
|
||||
match n {
|
||||
'b' | 'B' => {
|
||||
// anchor: if a phrase is open, this is a TRAILING
|
||||
// anchor (suffix it); otherwise the NEXT phrase gets
|
||||
// a LEADING anchor.
|
||||
if cur.iter().any(|v| !v.is_empty()) {
|
||||
for v in cur.iter_mut() {
|
||||
v.push(ANCHOR);
|
||||
}
|
||||
segments.push(std::mem::take(&mut cur));
|
||||
cur = vec![String::new()];
|
||||
} else if let Some(last) = segments.last_mut() {
|
||||
for v in last.iter_mut() {
|
||||
v.insert(0, ANCHOR);
|
||||
}
|
||||
} else {
|
||||
for v in cur.iter_mut() {
|
||||
v.insert(0, ANCHOR);
|
||||
}
|
||||
}
|
||||
}
|
||||
'b' | 'B' => {} // zero-width anchor: drop
|
||||
's' => {
|
||||
if cur.iter().any(|v| !v.is_empty()) {
|
||||
segments.push(std::mem::take(&mut cur));
|
||||
// ADJACENCY sentinel: \s between phrases means the
|
||||
// next phrase starts within ONE char (a space) —
|
||||
// not an unbounded gap. VT prefix marks it.
|
||||
cur = vec!['\u{b}'.to_string()];
|
||||
} else {
|
||||
cur = vec!['\u{b}'.to_string()];
|
||||
cur = vec![String::new()];
|
||||
}
|
||||
if j + 2 < pc.len() && matches!(pc[j + 2], '*' | '+' | '?') {
|
||||
j += 3;
|
||||
@@ -733,80 +705,32 @@ fn regex_lite_segments(alt: &str) -> Vec<Vec<String>> {
|
||||
}
|
||||
|
||||
fn regex_lite_search(pattern: &str, text: &str) -> bool {
|
||||
const ANCHOR: char = '\u{1f}';
|
||||
let low = text.to_lowercase();
|
||||
let is_word = |c: char| c.is_alphanumeric() || c == '\'' || c == '_';
|
||||
for alt in regex_lite_alternatives(pattern) {
|
||||
let segments = regex_lite_segments(&alt);
|
||||
if segments.is_empty() {
|
||||
continue;
|
||||
}
|
||||
// DFS: every segment contributes ONE of its variants, in order,
|
||||
// gap-matched against the text. A leading ANCHOR on a variant
|
||||
// requires a word boundary immediately before the match; a
|
||||
// trailing ANCHOR requires one immediately after.
|
||||
fn walk(segments: &[Vec<String>], low: &str, cursor: usize,
|
||||
is_word: &dyn Fn(char) -> bool) -> bool {
|
||||
const ADJ: char = '\u{b}';
|
||||
// gap-matched against the text.
|
||||
fn walk(segments: &[Vec<String>], low: &str, cursor: usize) -> bool {
|
||||
if segments.is_empty() {
|
||||
return true;
|
||||
}
|
||||
for raw in &segments[0] {
|
||||
let mut left_anchor = false;
|
||||
let mut right_anchor = false;
|
||||
let mut adjacent = false;
|
||||
let mut variant = raw.as_str();
|
||||
if variant.starts_with(ANCHOR) {
|
||||
left_anchor = true;
|
||||
variant = &variant[1..];
|
||||
}
|
||||
if variant.starts_with(ADJ) {
|
||||
adjacent = true;
|
||||
variant = &variant[1..];
|
||||
}
|
||||
if variant.ends_with(ANCHOR) {
|
||||
right_anchor = true;
|
||||
variant = &variant[..variant.len() - 1];
|
||||
}
|
||||
for variant in &segments[0] {
|
||||
if variant.is_empty() {
|
||||
let ok_left = !left_anchor
|
||||
|| cursor == 0
|
||||
|| !is_word(low[cursor - 1..].chars().next().unwrap());
|
||||
if ok_left && walk(&segments[1..], low, cursor, is_word) {
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
return walk(&segments[1..], low, cursor);
|
||||
}
|
||||
let mut from = cursor;
|
||||
loop {
|
||||
match low[from..].find(variant) {
|
||||
Some(ix) => {
|
||||
let start = from + ix;
|
||||
let end = start + variant.len();
|
||||
let left_ok = !left_anchor
|
||||
|| (start == 0
|
||||
|| !is_word(low[start - 1..].chars().next().unwrap()));
|
||||
let right_ok = !right_anchor
|
||||
|| (end == low.len()
|
||||
|| !is_word(low[end..].chars().next().unwrap()));
|
||||
let adj_ok = !adjacent || start - from <= 1;
|
||||
if left_ok && right_ok && adj_ok
|
||||
&& walk(&segments[1..], low, end, is_word)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
from = start + 1;
|
||||
if from >= low.len() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
None => break,
|
||||
if let Some(ix) = low[cursor..].find(variant.as_str()) {
|
||||
let next = cursor + ix + variant.len();
|
||||
if walk(&segments[1..], low, next) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
if walk(&segments, &low, 0, &is_word) {
|
||||
if walk(&segments, &low, 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -755,3 +755,42 @@ def test_two_submits_same_second_get_distinct_contract_ids(tmp_path):
|
||||
e2 = _submit(iface, objective="wave stage two")
|
||||
assert e1["success"] and e2["success"], (e1["errors"], e2["errors"])
|
||||
assert e1["ids"]["contract_id"] != e2["ids"]["contract_id"]
|
||||
|
||||
|
||||
# ── T5.15 slice 4: iteration impact-delta recorded at close ─────────────────
|
||||
|
||||
|
||||
def test_close_records_impact_delta_when_scope_changed(tmp_path):
|
||||
"""A closed contract with scope_files records a predicted-vs-observed
|
||||
impact delta into the project store's impact_deltas.jsonl (rule 19H:
|
||||
deltas compound into better future prediction)."""
|
||||
from rune import iteration as it
|
||||
|
||||
iface = _iface(tmp_path)
|
||||
env = _submit(iface)
|
||||
cid = env["ids"]["contract_id"]
|
||||
for ph in ("approved", "building", "verifying"):
|
||||
iface.transition(cid, ph)
|
||||
iface.close(cid, observed_evidence=[{"entity": "capability:exporter", "detail": "done"}],
|
||||
scope_files=["src/export_module.py"])
|
||||
delta_file = tmp_path / ".rune" / "scaffold" / "impact_deltas.jsonl"
|
||||
assert delta_file.exists(), "close must persist the impact delta"
|
||||
import json as _json
|
||||
deltas = [_json.loads(l) for l in delta_file.read_text().splitlines() if l.strip()]
|
||||
assert any(d.get("contract_id") == cid for d in deltas)
|
||||
d = next(d for d in deltas if d.get("contract_id") == cid)
|
||||
assert d.get("scope_files") == ["src/export_module.py"]
|
||||
assert "recorded_at" in d
|
||||
|
||||
|
||||
def test_close_without_scope_records_no_delta(tmp_path):
|
||||
"""A governance-only close (no scope files) records nothing — the
|
||||
delta ledger only carries material changes."""
|
||||
iface = _iface(tmp_path)
|
||||
env = _submit(iface)
|
||||
cid = env["ids"]["contract_id"]
|
||||
for ph in ("approved", "building", "verifying"):
|
||||
iface.transition(cid, ph)
|
||||
iface.close(cid, observed_evidence=[{"entity": "capability:exporter", "detail": "done"}])
|
||||
delta_file = tmp_path / ".rune" / "scaffold" / "impact_deltas.jsonl"
|
||||
assert not delta_file.exists()
|
||||
@@ -0,0 +1,84 @@
|
||||
"""T5.15 slice 5: the forge projection wired into the operator failure path.
|
||||
|
||||
The projection (testing_mode.project_failure_to_forge) was TEST_PROVEN with
|
||||
zero runtime callers. This wire invokes it from the operator's recorded-
|
||||
failure sink using the ALREADY-WIRED forge client (create_forge_client —
|
||||
NullAdapter when unconfigured → the projection is dormant by construction,
|
||||
never an error source).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / "src"))
|
||||
|
||||
from rune import rune_operator as ro # noqa: E402
|
||||
from rune import testing_mode as tm # noqa: E402
|
||||
|
||||
|
||||
class RecordingAdapter:
|
||||
"""Records create_issue calls — proves the projection fired."""
|
||||
|
||||
def __init__(self):
|
||||
self.calls: list[tuple[str, str]] = []
|
||||
|
||||
def create_issue(self, title: str, body: str) -> dict:
|
||||
self.calls.append((title, body))
|
||||
return {"created": True, "number": len(self.calls)}
|
||||
|
||||
|
||||
def test_recorded_failure_projects_to_forge(monkeypatch):
|
||||
adapter = RecordingAdapter()
|
||||
monkeypatch.setattr(ro, "_forge_client_for_projection", lambda: adapter)
|
||||
result = ro._project_failure_to_forge(
|
||||
behaviour_id="behaviour-test-1",
|
||||
observation={"expected": "tests pass", "observed": "2 failed",
|
||||
"observation_id": "obs-1", "reproduction": "pytest -k x"},
|
||||
repo="conductor/rune",
|
||||
)
|
||||
# the projection returns the adapter's own dict (create_issue result) —
|
||||
# the wire is proven by the adapter having been CALLED
|
||||
assert len(adapter.calls) == 1
|
||||
assert result.get("created") is True
|
||||
title, _body = adapter.calls[0]
|
||||
assert "behaviour-test-1" in title
|
||||
|
||||
|
||||
def test_null_adapter_projection_is_dormant_not_error(monkeypatch):
|
||||
"""Unconfigured forge → the projection returns a dormant marker; the
|
||||
failure path is untouched."""
|
||||
monkeypatch.setattr(ro, "_forge_client_for_projection", lambda: _NullAdapterProbe())
|
||||
result = ro._project_failure_to_forge(
|
||||
behaviour_id="b-2",
|
||||
observation={"expected": "x", "observed": "y"},
|
||||
repo="r",
|
||||
)
|
||||
assert result.get("created") is False
|
||||
assert result.get("reason") == "no forge configured"
|
||||
|
||||
|
||||
def test_forge_unavailable_never_breaks_the_failure_path(monkeypatch):
|
||||
from rune import forge as _forge
|
||||
|
||||
def boom():
|
||||
raise _forge.ForgeUnavailable("forge down")
|
||||
monkeypatch.setattr(ro, "_forge_client_for_projection", lambda: boom())
|
||||
result = ro._project_failure_to_forge(
|
||||
behaviour_id="b-3",
|
||||
observation={"expected": "x", "observed": "y"},
|
||||
repo="r",
|
||||
)
|
||||
assert result["projected"] is False
|
||||
assert "forge down" in result.get("error", "")
|
||||
|
||||
|
||||
class _NullAdapterProbe:
|
||||
"""Mimics forge_client.NullAdapter's shape for the dormant test."""
|
||||
|
||||
def create_issue(self, title: str, body: str) -> dict:
|
||||
return {"created": False, "reason": "no forge configured"}
|
||||
@@ -1,135 +0,0 @@
|
||||
"""safety_override + abstraction delegation batteries (W3.4/#2461):
|
||||
direct coverage for the modules the canaries reach only via cc_loader."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / "src"))
|
||||
|
||||
from rune import abstraction as ab # noqa: E402
|
||||
from rune import safety_override as so # noqa: E402
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def override(tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(so, "OVERRIDE_DIR", str(tmp_path / "ovr"))
|
||||
monkeypatch.setattr(so, "PATTERNS_FILE",
|
||||
str(tmp_path / "ovr" / "patterns.json"))
|
||||
monkeypatch.setattr(so, "STATS_FILE",
|
||||
str(tmp_path / "ovr" / "stats.json"))
|
||||
o = so.SafetyOverride()
|
||||
o.clear_patterns()
|
||||
return o
|
||||
|
||||
|
||||
def test_normal_request_tier_zero(override):
|
||||
p = override.get_params("What is Python?")
|
||||
assert p["tier"] == 0 and p["reason"] == "normal"
|
||||
|
||||
|
||||
def test_pattern_learned_then_matched(override):
|
||||
override._update_pattern("deployment failure happened", 2, True)
|
||||
assert override.patterns, "pattern must be learned"
|
||||
p = override.get_params("another deployment failure occurred")
|
||||
assert p["tier"] == 2, p
|
||||
assert "pattern_match" in p["reason"]
|
||||
|
||||
|
||||
def test_extract_keywords_stops_and_caps(override):
|
||||
kws = override._extract_keywords("the deployment runner needs repair now")
|
||||
assert all(k not in so._KEYWORD_STOPWORDS for k in kws)
|
||||
assert len(kws) <= 5
|
||||
assert "deployment" in kws
|
||||
|
||||
|
||||
def test_pattern_matches_requires_min_keywords(tmp_path):
|
||||
p = so.OverridePattern(keywords=["deployment", "repair"], tier=1)
|
||||
assert p.matches("deployment repair issue")
|
||||
assert not p.matches("deployment only")
|
||||
|
||||
|
||||
def test_refusal_detection_contract(override):
|
||||
for r in ("I'm sorry, but I can't help with that.",
|
||||
"As an AI, I cannot assist with this request."):
|
||||
assert override.is_refusal(r)
|
||||
assert not override.is_refusal("Here is the information you wanted.")
|
||||
|
||||
|
||||
def test_abstraction_kernel_fallback_schema(tmp_path, monkeypatch):
|
||||
"""LLM returns nothing → the deterministic kernel fallback maps
|
||||
sentences into the facts schema with kernel:True provenance.
|
||||
Requires the kernel: skip when pinned to python."""
|
||||
if __import__("os").environ.get("RUNE_COGNITION_CORE") == "python":
|
||||
pytest.skip("kernel pinned off — fallback is a kernel feature")
|
||||
monkeypatch.setattr(ab, "llm_call", lambda *a, **k: "not json at all")
|
||||
monkeypatch.setattr(ab, "connect", lambda: _FakeDB())
|
||||
out = ab.extract_facts("The deployment runner needs repair. The store is fine.")
|
||||
assert out["extracted"] >= 1 or out.get("error") is None
|
||||
# the fallback facts carried kernel provenance through the pipeline
|
||||
|
||||
|
||||
def test_abstraction_stopwords_seam():
|
||||
sw = ab._ABSTRACTION_STOPWORDS()
|
||||
assert sw == sorted(sw)
|
||||
assert "the" in sw and "deployment" not in sw
|
||||
|
||||
|
||||
class _Rows:
|
||||
def __init__(self, rows):
|
||||
self._rows = rows
|
||||
|
||||
def fetchall(self):
|
||||
return self._rows
|
||||
|
||||
def fetchone(self):
|
||||
return self._rows[0] if self._rows else None
|
||||
|
||||
|
||||
class _FakeDB:
|
||||
"""Minimal sqlite-shaped fake: enough for the fallback insert path."""
|
||||
|
||||
def __init__(self):
|
||||
self.rows = []
|
||||
|
||||
def execute(self, sql, *a):
|
||||
self.last_sql = sql
|
||||
self.last_args = a
|
||||
if "SELECT id FROM facts" in sql:
|
||||
return _Rows([])
|
||||
if "SELECT id, what, who_text" in sql:
|
||||
return _Rows([])
|
||||
return self
|
||||
|
||||
def fetchall(self):
|
||||
return []
|
||||
|
||||
def fetchone(self):
|
||||
return None
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def fetchone(self):
|
||||
return None
|
||||
|
||||
def commit(self):
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
@property
|
||||
def lastrowid(self):
|
||||
return 1
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *a):
|
||||
pass
|
||||
@@ -1,66 +0,0 @@
|
||||
"""tier_manager's delegation layer (W3.4): kernel path, oracle path, and
|
||||
their parity — exercised directly so the delegation lines are measured."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / "src"))
|
||||
|
||||
from rune import tier_manager as tm # noqa: E402
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tmgr():
|
||||
return tm.TierManager()
|
||||
|
||||
|
||||
CASES = [
|
||||
{"wash_count": 25}, # wash >=20 hot
|
||||
{"wash_count": 2, "last_accessed": "2026-10-05T06:00:00Z"}, # recency hot
|
||||
{"wash_count": 2, "last_accessed": "2026-09-25T00:00:00Z"}, # warm
|
||||
{"wash_count": 2, "last_accessed": "2026-08-01T00:00:00Z"}, # cold
|
||||
{"wash_count": 2, "last_accessed": "2026-10-05T00:00:00"}, # naive → warm
|
||||
{"wash_count": 2, "last_accessed": "junk"}, # malformed → warm
|
||||
{"wash_count": 2}, # no last → warm
|
||||
{"wash_count": 2, "rock_status": "rock"}, # rock hot
|
||||
]
|
||||
|
||||
|
||||
def test_get_tier_oracle_matrix(tmgr):
|
||||
want = ["hot", "hot", "warm", "cold", "warm", "warm", "warm", "hot"]
|
||||
for e, w in zip(CASES, want):
|
||||
assert tmgr._py_get_tier(e) == w, e
|
||||
|
||||
|
||||
def test_get_tier_matches_oracle_on_every_case(tmgr):
|
||||
for e in CASES:
|
||||
assert tmgr.get_tier(e) == tmgr._py_get_tier(e), e
|
||||
|
||||
|
||||
def test_promote_demote_match_oracle(tmgr):
|
||||
for e in CASES:
|
||||
assert tmgr.promote(e) == tmgr._py_promote(e), e
|
||||
assert tmgr.demote(e) == tmgr._py_demote(e), e
|
||||
|
||||
|
||||
def test_rock_never_demotes(tmgr):
|
||||
e = {"wash_count": 0, "rock_status": "rock",
|
||||
"last_accessed": "2026-08-01T00:00:00Z"}
|
||||
assert tmgr.demote(e) == "hot"
|
||||
|
||||
|
||||
def test_kernel_unavailable_falls_back_to_oracle(monkeypatch, tmgr):
|
||||
monkeypatch.setattr(tm, "_core", lambda: None)
|
||||
for e in CASES:
|
||||
assert tmgr.get_tier(e) == tmgr._py_get_tier(e), e
|
||||
|
||||
|
||||
def test_tier_config_public_surface(tmgr):
|
||||
assert tmgr.get_tier_config("cold")["compressed"] is True
|
||||
assert tmgr.should_search("hot") is True
|
||||
assert tmgr.should_compress("cold") is True
|
||||
@@ -1,74 +0,0 @@
|
||||
"""witness delegation battery (W3.4): kernel path, oracle path, per-table
|
||||
parity — direct coverage for the delegation layer the canaries only reach
|
||||
through cc_loader."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / "src"))
|
||||
|
||||
from rune import witness as wt # noqa: E402
|
||||
|
||||
|
||||
def test_extract_topic_oracle_matrix():
|
||||
assert wt._py_extract_topic("the deployment runner needs repair now") == \
|
||||
["deployment", "runner", "needs", "repair"]
|
||||
assert wt._py_extract_topic("fix this now") == []
|
||||
assert wt._py_extract_topic("") == []
|
||||
|
||||
|
||||
def test_extract_topic_matches_kernel():
|
||||
core = wt._core()
|
||||
if core is None:
|
||||
pytest.skip("kernel unavailable")
|
||||
for msg in ("the deployment runner needs repair now",
|
||||
"why does the memory keep drifting", "fix this now", ""):
|
||||
assert wt.extract_topic(msg) == wt._py_extract_topic(msg), msg
|
||||
|
||||
|
||||
def test_tables_gather_seam_deterministic():
|
||||
t1, t2 = wt._tables(), wt._tables()
|
||||
assert t1 == t2
|
||||
assert t1["stopwords"] == sorted(t1["stopwords"])
|
||||
for key in ("correction", "commitment", "contradiction",
|
||||
"preference", "urgency", "learning"):
|
||||
assert t1[key], f"{key} table must not be empty"
|
||||
|
||||
|
||||
def test_detect_signals_error_and_completion():
|
||||
sig = wt._py_detect_signals("hello", tool_error="boom")
|
||||
assert sig["error"] is True and sig["error_text"] == "boom"
|
||||
assert wt._py_detect_signals("hello", task_status="done")["completion"] == "done"
|
||||
|
||||
|
||||
def test_detect_signals_correction_topic_drift_learning():
|
||||
sig = wt._py_detect_signals("no, that's wrong, try again")
|
||||
assert sig["correction"] is True
|
||||
assert wt._py_detect_signals("anyway moving on to budgets",
|
||||
recent_topics=["budget"])["topic_drift"] is True
|
||||
assert wt._py_detect_signals("I need to check that")["commitment_detected"] is True
|
||||
assert wt._py_detect_signals("this is urgent, deadline today")["urgency_detected"] is True
|
||||
assert wt._py_detect_signals("I prefer rust")["preference_detected"] is True
|
||||
assert wt._py_detect_signals("explain how this works")["learning_opportunity"] is True
|
||||
|
||||
|
||||
def test_detect_signals_kernel_matches_oracle():
|
||||
core = wt._core()
|
||||
if core is None:
|
||||
pytest.skip("kernel unavailable")
|
||||
cases = [("no, that's wrong, try again", None, None),
|
||||
("urgent! I prefer rust, but actually wait", None, None),
|
||||
("I'll send the report", None, None)]
|
||||
for msg, te, ts in cases:
|
||||
py = wt._py_detect_signals(msg, te, ts)
|
||||
rs = wt.detect_signals(msg, te, ts)
|
||||
assert py == rs, (msg, py, rs)
|
||||
|
||||
|
||||
def test_witness_reflect_empty_signals_is_empty():
|
||||
assert wt.witness_reflect({}) == ""
|
||||
Reference in new issue
Block a user