Compare commits

...
15 Commits
Author SHA1 Message Date
conductor 84b8d6b215 chore(release): auto-bump version to 0.99.1 — earned by # (patch: converged release (push-to-master bump))
Promotion record: (unknown)
2026-10-06 01:22:12 +01:00
Nick 7f5d57236a fix(ci): 0.99.0 CI repair — silent-handler annotations + spine_language umbrella mapping (#2461)
The 0.99.0 bump ran CI against the delegation code before the annotations landed: BLE001 345->349, S110 75->81 (my new kernel-failure fallbacks), plus spine_language unmapped in the umbrella manifest (test_umbrella_impact) and the resulting spine-rebuild membership diff (rebuild-neutrality). All four new fallbacks carry intentional-silence noqa annotations per the ratchet rule (count now exactly 345/75 — baseline UNCHANGED, ledger honest); spine_language mapped into perception next to spine; store rebuilt so the membership edge and language stamps are in the committed store. 127 tests green incl. the four CI-red surfaces.
2026-10-06 01:22:05 +01:00
conductor bda07c68ee chore(release): auto-bump version to 0.99.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-10-06 00:11:25 +01:00
Nick c5be22be09 feat(cognition): witness/safety/abstraction kernel delegation + regex_lite faithful-subset rewrite (#2461 W3.4, #2465 adjacency)
The W3.4 kernels existed but the Python publics still ran Python. Now: witness extract_topic/detect_signals delegate through the _stopwords()/_tables() gather seams (PATTERN-TABLE GATHER-SEAM LAW) with _py_ oracles; abstraction extract_facts gains the deterministic kernel FALLBACK when the LLM returns nothing; tier_manager was already wired; the RUNE_COGNITION_CORE=python pin is now honored by a SHARED cc_loader.core() probe (the four module-local probes silently ignored it). The canary matrix caught a REAL kernel bug: regex_lite dropped \b anchors as literal b chars so every word-boundary row silently missed (bwrongb never appears in text); rewritten as alternatives-expansion + segment-variant DFS gap-walk; 34-row matrix judged against REAL Python re shows 0 mismatches. canary_growth_pins 27/27 x5 both engine modes; canary_unbounded 14/14; per-row fixtures carry re-truth, not hand labels (a lying fixture is worse than uncovered).
2026-10-06 00:05:54 +01:00
conductor 4d6fec3d17 chore(release): auto-bump version to 0.98.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-10-06 00:01:45 +01:00
conductor 15dbbc562a chore(release): auto-bump version to 0.98.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-10-05 23:56:19 +01:00
Nick 6eb5e06119 refactor(spine): carve the #2465 doc-level transforms into spine_language.py (#2461 keep-green)
spine.py crossed the 1200-line giant-file debt cap (1203) with the language-verdict addition — CI lint red. The three transforms (language placement, discovery-ID normalization, contract-discovery write-back + gatherer) move to spine_language.py intact, re-exported through spine for import-compat (structure unchanged: 256 entities, same verdicts; spine battery 87, canaries 19/19 + 21/21, henxels hold, spine.py back to 1034).
2026-10-05 23:53:33 +01:00
Nick 78e4750a77 feat(tier): tier_manager delegates get/promote/demote to the cognition_core kernels (#2461 W3.4 delegation owed)
The kernels and FFI landed in b0a682b but the Python publics still ran Python — the strangler breach this slice owed. Live probe caught the FFI-arg-shape defect: promote/demote take the FULL ENTRY (kernel recomputes current tier internally), not a tier string; passing a string silently returned threshold-default verdicts (promote cold went warm-to-hot WRONGLY via defaults, demote rock went cold ignoring rock protection). 12-case kernel==oracle sweep green: wash>=20, recency hot/warm/cold, naive-timestamp parity trap, rock protection. PY oracle bodies kept; RUNE_COGNITION_CORE=python pins fallback; canary_growth_pins 26/26 both engines; canary_unbounded 14/14.
2026-10-05 23:50:11 +01:00
conductor c3948bae98 chore(release): auto-bump version to 0.97.0 — earned by # (minor: converged release (push-to-master bump))
Promotion record: (unknown)
2026-10-05 23:48:25 +01:00
Nick b8184a0f01 chore(spine): store rebuild carries subsystem language verdicts (#2465) 2026-10-05 23:47:58 +01:00
Nick 469b91f217 feat(spine): subsystem language verdicts from member majority (#2465 addition 3)
The five unassessed subsystems are containers: their language derives from member modules measured sides (classification.json, the W0 dichotomy map, rust and go counts carried in the verdict text). Unclassified members noted rather than guessed; unreadable classification degrades to unassessed. Spine canaries 19/19 and 21/21, spine battery 87 green.
2026-10-05 23:47:55 +01:00
Nick bb960b1656 test(gitea): canary the numeric-ID stale-label removal (#2467)
Replace the obsolete removeLabel(name) pattern assertion with the actual Gitea /labels/{id} request, check ID lookup under the 404 idempotency guard, and require real P0–P3 managed labels. Local canary 39/39; workflow JS regression passes.
2026-10-05 22:22:53 +01:00
conductor 38c76a0c29 chore(release): auto-bump version to 0.96.1 — earned by # (patch: converged release (push-to-master bump))
Promotion record: (unknown)
2026-10-05 22:15:03 +01:00
Nick 3186566514 fix(gitea): reconcile priority labels by numeric Gitea ID (#2467)
Issue #2467 carried both P0 and P2 because name-based label DELETE returned 404 and the workflow silently ignored it. Use the numeric ID API, enforce exactly one priority, clear stale unprioritized flags, and execute the workflow JavaScript against a Gitea-compatible fake in regression tests.
2026-10-05 22:14:47 +01:00
Nick 24d14ff8e1 fix(spine): preserve candidate discoveries without inventing module IDs (#2465)
Migrate 22 malformed contract-discovery IDs to construction-surface, retain evidence/edges, keep candidates DECLARED, unify cold/warm language projection, and refresh the pinned aiohttp lock. Red regression tests precede the fix; local full suite 2301 passed, spine canaries 19/19 and 21/21.
2026-10-05 22:14:46 +01:00
22 changed files with 2002 additions and 1039 deletions

No files matched your search

+26 -5
View File
@@ -23,12 +23,13 @@ jobs:
const labels = (issue.labels || []).map(l => l.name);
const priorities = ['P0 — critical', 'P1 — high', 'P2 — medium', 'P3 — low'];
const has = labels.filter(l => priorities.includes(l));
if (has.length >= 1) {
if (has.length === 1) {
// Prioritized — clear the flag if it was set.
if (labels.includes('unprioritized')) {
await github.rest.issues.removeLabel({
await github.request('DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels/{id}', {
owner: context.repo.owner, repo: context.repo.repo,
issue_number: issue.number, name: 'unprioritized',
issue_number: issue.number,
id: issue.labels.find(l => l.name === 'unprioritized').id,
}).catch(e => { if (e.status !== 404) throw e; });
}
return;
@@ -170,6 +171,7 @@ jobs:
issue_number: issueNumber,
});
const currentLabels = new Set(current.data.map(label => label.name));
const currentLabelIds = new Map(current.data.map(label => [label.name, label.id]));
const staleManaged = [...currentLabels].filter(
label => managedLabels.has(label) && !desiredLabels.has(label)
);
@@ -179,11 +181,14 @@ jobs:
for (const name of staleManaged) {
try {
await github.rest.issues.removeLabel({
// Gitea's DELETE /labels/{id} requires the numeric label ID.
// Octokit's GitHub-style removeLabel(name) returned 404 and
// left stale priority labels behind (#2467: P0 AND P2).
await github.request('DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels/{id}', {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
name,
id: currentLabelIds.get(name),
});
} catch (error) {
if (error.status !== 404) {
@@ -201,5 +206,21 @@ jobs:
labels: missingDesired,
});
}
// The priority gate can flag a duplicate before this job
// reconciles it. Clear the flag only AFTER verifying exactly one
// surviving P-label against Gitea's current state.
const reconciled = await github.rest.issues.listLabelsOnIssue({
owner: context.repo.owner, repo: context.repo.repo,
issue_number: issueNumber,
});
const actual = reconciled.data;
const activePriority = actual.filter(l => priorityLabelNames.includes(l.name));
const flag = actual.find(l => l.name === 'unprioritized');
if (activePriority.length === 1 && flag) {
await github.request('DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels/{id}', {
owner: context.repo.owner, repo: context.repo.repo,
issue_number: issueNumber, id: flag.id,
});
}
console.log(`Removed managed labels: ${staleManaged.join(', ') || 'none'}`);
console.log(`Added managed labels: ${missingDesired.join(', ') || 'none'}`);
+1 -1
View File
@@ -1,6 +1,6 @@
# Rune
**Current release: v0.96.0**
**Current release: v0.99.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.**
+316 -315
View File
@@ -1,318 +1,319 @@
{
"_comment": "GENERATED by rune.spine \u2014 compatibility view for legacy umbrella consumers. DO NOT EDIT: structural truth lives in store/spine/current.json; edits here are flagged by spine drift.",
"_generated_by_spine": true,
"generated_at": "2026-09-16T18:23:38Z",
"umbrellas": {
"capability": {
"description": "Tool selection and execution authority",
"modules": [
"adaptive_router",
"builder_bridge",
"builder_bridge_cli",
"capability_broker",
"connection_pool",
"core_server",
"cs_loader",
"evidence_probe",
"forge",
"forge_client",
"intent_continuity",
"local_tool_runtime",
"message_queue",
"native_units",
"openai_compat_transport",
"provider_context",
"recall_uplift",
"route_risk_flags",
"tool_executor",
"tool_protocol"
],
"depends_on": [
"memory"
],
"mirrors": [],
"canaries": [
"canary_native",
"canary_p0_05",
"canary_p1_01",
"canary_rune_entry",
"canary_rune_serve"
],
"go_surfaces": [
"rune-entry"
],
"configs": [],
"rust_crates": [
"capability_scoring"
]
},
"governance": {
"description": "Quality control, drift detection, promotion, audit",
"modules": [
"audit_ledger",
"build_pipeline_policy",
"cc_loader",
"claim_audit",
"contracts",
"curator",
"drift_core",
"drift_ledger",
"drift_observability",
"failure_auto_filer",
"programme_gates",
"sc_loader"
],
"depends_on": [],
"mirrors": [],
"canaries": [
"canary_full_system",
"canary_schema_migration",
"canary_audit_ledger",
"canary_spine_meta"
],
"rust_crates": [
"evidence_provenance",
"scaffold_core"
],
"configs": [
".coveragerc",
"go-version.txt",
"lint-baseline.json",
"pyright-baseline.json"
]
},
"growth": {
"description": "Learning and improvement",
"modules": [
"autonomy_loop",
"cognitive_loop",
"dream_cycle",
"elastic_cognition",
"goal",
"growth_delta",
"growth_simulator",
"self_improve",
"subconscious_pass"
],
"depends_on": [
"governance",
"memory"
],
"mirrors": [],
"canaries": [
"canary_full_system"
]
},
"identity": {
"description": "Who the agent is \u2014 persona, voice, anchors",
"modules": [
"behavior",
"identity_anchors",
"persona_definition",
"persona_gate",
"prompt_sync",
"voice_hybrid",
"voice_injector",
"voice_sync",
"voicebox"
],
"depends_on": [
"governance"
],
"mirrors": [],
"canaries": [
"canary_full_system"
]
},
"inference": {
"description": "Model routing and outbound requests",
"modules": [
"inference_trace",
"model_connect",
"model_profiles",
"model_registry",
"model_routing",
"provider_budget",
"provider_registry",
"response_adapter",
"standardized_providers",
"tc_loader",
"token_optimizer"
],
"depends_on": [
"capability"
],
"mirrors": [],
"canaries": [
"canary_p1_02",
"canary_p1_06",
"canary_resource_authority"
],
"rust_crates": [
"token_counting"
]
},
"memory": {
"description": "Storing, retrieving, and managing knowledge",
"modules": [
"abstraction",
"cache",
"drift_cache",
"drift_compression",
"ffi_query_memory_smoke",
"graph_memory",
"graph_shard_bridge",
"memory_consolidation",
"memory_env",
"memory_selector",
"memory_store",
"mems_loader",
"mr_loader",
"ms_loader",
"query_memory",
"recall_router",
"retrieval_benchmark",
"shard_manager",
"tier_manager",
"warm_neuron_store"
],
"depends_on": [
"governance"
],
"mirrors": [],
"canaries": [
"canary_full_system",
"canary_memory_synthesis",
"canary_query_memory"
],
"rust_crates": [
"memory_retrieval",
"memory_scoring",
"memory_storage",
"query_memory"
]
},
"orchestration": {
"description": "Task lifecycle, operator, TUI presentation",
"modules": [
"auto_detect",
"build_mode",
"build_orchestrator",
"chat_bubble",
"cron_engine",
"custodian",
"dependency_tracker",
"event_bus",
"event_envelope",
"gitea_auth",
"gitea_pipeline",
"gitea_provision",
"hook_engine",
"message_widgets",
"microkernel",
"night_mode",
"outcome_ledger",
"parallel_processor",
"pins",
"progressive_escalation",
"rune_graphic",
"rune_operator",
"rune_profiler",
"rune_spinner",
"rune_tui",
"safety_override",
"sleep_time",
"subsystem_health",
"system_registry",
"task_gate",
"task_persist",
"tiered_executor",
"time_awareness",
"tui_transport",
"tui_validator",
"umbrella_update",
"verification_layer",
"warm_router",
"work_manager"
],
"depends_on": [
"capability",
"growth",
"identity",
"inference",
"perception"
],
"mirrors": [],
"canaries": [
"canary_cognitive_strata",
"canary_conversation_state",
"canary_gitea_pipeline",
"canary_orchestration_foundation",
"canary_p1_07",
"canary_surface_parity",
"canary_tui_drift",
"canary_tui_transport"
]
},
"perception": {
"description": "Input processing and context assembly",
"modules": [
"attention_capsule",
"auto_context",
"auto_tasks",
"behaviour_ledger",
"build_basis",
"capability_fit",
"capability_ledger",
"cognitive_budgeter",
"construction_activation",
"construction_commands",
"construction_decision",
"construction_depth",
"construction_intent",
"construction_interface",
"construction_mode",
"construction_pattern",
"construction_patterns_builtin",
"construction_projection",
"context_broker",
"context_pressure",
"drift_resume",
"event_wait",
"failure_recovery",
"graph_projection",
"impact_preview",
"iteration",
"layout_contract",
"programme_continuation",
"project_registry",
"release_notes",
"salience_gate",
"scaffold",
"spine",
"spine_loader",
"stage_guards",
"structural_migration",
"structural_model",
"structural_store",
"testing_mode",
"toolchain",
"tui_backlog",
"wait_guard",
"wait_guard_corpus",
"witness"
],
"depends_on": [
"memory"
],
"mirrors": [],
"canaries": [
"canary_context_broker",
"canary_full_system"
],
"rust_crates": [
"rune_spine"
]
}
"_comment": "GENERATED by rune.spine \u2014 compatibility view for legacy umbrella consumers. DO NOT EDIT: structural truth lives in store/spine/current.json; edits here are flagged by spine drift.",
"_generated_by_spine": true,
"generated_at": "2026-09-16T18:23:38Z",
"umbrellas": {
"capability": {
"canaries": [
"canary_native",
"canary_p0_05",
"canary_p1_01",
"canary_rune_entry",
"canary_rune_serve"
],
"configs": [],
"depends_on": [
"memory"
],
"description": "Tool selection and execution authority",
"go_surfaces": [
"rune-entry"
],
"mirrors": [],
"modules": [
"adaptive_router",
"builder_bridge",
"builder_bridge_cli",
"capability_broker",
"connection_pool",
"core_server",
"cs_loader",
"evidence_probe",
"forge",
"forge_client",
"intent_continuity",
"local_tool_runtime",
"message_queue",
"native_units",
"openai_compat_transport",
"provider_context",
"recall_uplift",
"route_risk_flags",
"tool_executor",
"tool_protocol"
],
"rust_crates": [
"capability_scoring"
]
},
"governance": {
"canaries": [
"canary_full_system",
"canary_schema_migration",
"canary_audit_ledger",
"canary_spine_meta"
],
"configs": [
".coveragerc",
"go-version.txt",
"lint-baseline.json",
"pyright-baseline.json"
],
"depends_on": [],
"description": "Quality control, drift detection, promotion, audit",
"mirrors": [],
"modules": [
"audit_ledger",
"build_pipeline_policy",
"cc_loader",
"claim_audit",
"contracts",
"curator",
"drift_core",
"drift_ledger",
"drift_observability",
"failure_auto_filer",
"programme_gates",
"sc_loader"
],
"rust_crates": [
"evidence_provenance",
"scaffold_core"
]
},
"growth": {
"canaries": [
"canary_full_system"
],
"depends_on": [
"governance",
"memory"
],
"description": "Learning and improvement",
"mirrors": [],
"modules": [
"autonomy_loop",
"cognitive_loop",
"dream_cycle",
"elastic_cognition",
"goal",
"growth_delta",
"growth_simulator",
"self_improve",
"subconscious_pass"
]
},
"identity": {
"canaries": [
"canary_full_system"
],
"depends_on": [
"governance"
],
"description": "Who the agent is \u2014 persona, voice, anchors",
"mirrors": [],
"modules": [
"behavior",
"identity_anchors",
"persona_definition",
"persona_gate",
"prompt_sync",
"voice_hybrid",
"voice_injector",
"voice_sync",
"voicebox"
]
},
"inference": {
"canaries": [
"canary_p1_02",
"canary_p1_06",
"canary_resource_authority"
],
"depends_on": [
"capability"
],
"description": "Model routing and outbound requests",
"mirrors": [],
"modules": [
"inference_trace",
"model_connect",
"model_profiles",
"model_registry",
"model_routing",
"provider_budget",
"provider_registry",
"response_adapter",
"standardized_providers",
"tc_loader",
"token_optimizer"
],
"rust_crates": [
"token_counting"
]
},
"memory": {
"canaries": [
"canary_full_system",
"canary_memory_synthesis",
"canary_query_memory"
],
"depends_on": [
"governance"
],
"description": "Storing, retrieving, and managing knowledge",
"mirrors": [],
"modules": [
"abstraction",
"cache",
"drift_cache",
"drift_compression",
"ffi_query_memory_smoke",
"graph_memory",
"graph_shard_bridge",
"memory_consolidation",
"memory_env",
"memory_selector",
"memory_store",
"mems_loader",
"mr_loader",
"ms_loader",
"query_memory",
"recall_router",
"retrieval_benchmark",
"shard_manager",
"tier_manager",
"warm_neuron_store"
],
"rust_crates": [
"memory_retrieval",
"memory_scoring",
"memory_storage",
"query_memory"
]
},
"orchestration": {
"canaries": [
"canary_cognitive_strata",
"canary_conversation_state",
"canary_gitea_pipeline",
"canary_orchestration_foundation",
"canary_p1_07",
"canary_surface_parity",
"canary_tui_drift",
"canary_tui_transport"
],
"depends_on": [
"capability",
"growth",
"identity",
"inference",
"perception"
],
"description": "Task lifecycle, operator, TUI presentation",
"mirrors": [],
"modules": [
"auto_detect",
"build_mode",
"build_orchestrator",
"chat_bubble",
"cron_engine",
"custodian",
"dependency_tracker",
"event_bus",
"event_envelope",
"gitea_auth",
"gitea_pipeline",
"gitea_provision",
"hook_engine",
"message_widgets",
"microkernel",
"night_mode",
"outcome_ledger",
"parallel_processor",
"pins",
"progressive_escalation",
"rune_graphic",
"rune_operator",
"rune_profiler",
"rune_spinner",
"rune_tui",
"safety_override",
"sleep_time",
"subsystem_health",
"system_registry",
"task_gate",
"task_persist",
"tiered_executor",
"time_awareness",
"tui_transport",
"tui_validator",
"umbrella_update",
"verification_layer",
"warm_router",
"work_manager"
]
},
"perception": {
"canaries": [
"canary_context_broker",
"canary_full_system"
],
"depends_on": [
"memory"
],
"description": "Input processing and context assembly",
"mirrors": [],
"modules": [
"attention_capsule",
"auto_context",
"auto_tasks",
"behaviour_ledger",
"build_basis",
"capability_fit",
"capability_ledger",
"cognitive_budgeter",
"construction_activation",
"construction_commands",
"construction_decision",
"construction_depth",
"construction_intent",
"construction_interface",
"construction_mode",
"construction_pattern",
"construction_patterns_builtin",
"construction_projection",
"context_broker",
"context_pressure",
"drift_resume",
"event_wait",
"failure_recovery",
"graph_projection",
"impact_preview",
"iteration",
"layout_contract",
"programme_continuation",
"project_registry",
"release_notes",
"salience_gate",
"scaffold",
"spine",
"spine_language",
"spine_loader",
"stage_guards",
"structural_migration",
"structural_model",
"structural_store",
"testing_mode",
"toolchain",
"tui_backlog",
"wait_guard",
"wait_guard_corpus",
"witness"
],
"rust_crates": [
"rune_spine"
]
}
}
}
+16 -1
View File
@@ -272,4 +272,19 @@ observations, fresh resume.
Release-note generator: LIVE-PROVEN (real Gitea, 10 releases). Backfill:
0.66.35→0.66.44 carrying historical-backfill provenance. Pre-0.66.35
history: skipped (weak evidence — honest limitation).
history: skipped (weak evidence — honest limitation).
## Contract discovery → spine (2026-10-05, #2465 CI correction)
The closed construction contract is the authority for discoveries. Spine
rebuild reads its observed entities and candidate corrections; it does not
claim a correction has been built. Qualified IDs such as
`rust-crate:cognition_core` keep their declared kind. Bare file paths,
test suites and other unqualified references become
`construction-surface:<reference>`, never `module`. An observed
surface may be PRESENT; a candidate stays DECLARED until independently
observed. Existing bad records are migrated with their evidence and edge
references intact. This matters: the first write-back mapped bare paths to
modules, made 22 false module records and crashed CI on the next rebuild.
The per-contract quick-reference markdown is generated from the scaffold
store at close; it is a view, not another authority.
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "rune"
version = "0.96.0"
version = "0.99.1"
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
requires-python = ">=3.10"
+1 -1
View File
@@ -2,7 +2,7 @@
# uv pip compile pyproject.toml -o requirements-ci.txt --extra dev --extra memory --python-version 3.11
aiohappyeyeballs==2.7.1
# via aiohttp
aiohttp==3.14.3
aiohttp==3.14.4
# via rune (pyproject.toml)
aiosignal==1.4.0
# via aiohttp
+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.96.0"
__version__ = "0.99.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
+62 -2
View File
@@ -43,6 +43,42 @@ LOCAL_MODEL = "qwen2.5:3b"
OLLAMA_URL = os.environ.get("OLLAMA_URL", "http://localhost:11434")
_ABSTRACTION_STOPWORDS_FROZEN = frozenset(
"the a an is are was were be been being have has had do does did will "
"would could should may might shall can to of in for on with at by from "
"as into through during before after this that these those it its what "
"which who how when where why and or but not no".split()
)
def _ABSTRACTION_STOPWORDS() -> list:
"""Gather seam (W3.4): the fact-extraction stopword table as a sorted
list — data received by the kernel; identical filter on both engines."""
return sorted(_ABSTRACTION_STOPWORDS_FROZEN)
def _core():
"""The Rust cognition_core when available (strangler: Python fallback
load-bearing). RUNE_COGNITION_CORE=python pins the fallback."""
return None if cc_loader_pin() else _cc_probe()
def _cc_probe():
from rune import cc_loader as _cl
try:
return _cl if _cl.available() else None
except Exception: # noqa: BLE001, S110 — loader probe never crashes the fallback
return None
def cc_loader_pin() -> bool:
"""The env pin: RUNE_COGNITION_CORE=python → kernels OFF (shared probe).
"""
import os
return os.environ.get("RUNE_COGNITION_CORE", "auto") == "python"
def _now():
return datetime.now(timezone.utc).isoformat()
@@ -241,17 +277,41 @@ Extract 1-5 key facts. Be precise, not verbose."""
response = llm_call(prompt, max_tokens=800)
# Parse response
facts: list = []
try:
# Try to extract JSON from response
start = response.find("[")
end = response.rfind("]") + 1
if start >= 0 and end > start:
facts = json.loads(response[start:end])
else:
facts = []
except json.JSONDecodeError:
facts = []
# LLM unavailable / unparseable → DETERMINISTIC KERNEL FALLBACK
# (W3.4: cc_abstraction_extract_facts — sentence split, stopwords
# filtered, min-length gate; the same facts schema it already emits:
# text/words/content_words mapped onto what= text with empty dims).
if not facts:
core = _core()
if core is not None:
try:
from rune import cc_loader as _cl
raw = _cl.abstraction_extract_facts(
episode_text, _ABSTRACTION_STOPWORDS(), 10)
for item in raw or []:
text_frag = str(item.get("text", "")).strip()
if not text_frag:
continue
facts.append({
"what": text_frag, "when": "", "where": "", "who": "",
"why": "", "type": "experience", "entities": [],
"kernel": True,
"content_words": item.get("content_words"),
})
except Exception: # noqa: BLE001, S110 — kernel failure degrades to empty, intentional silence
pass
if not facts:
return {"extracted": 0, "error": "LLM did not return valid JSON", "raw": response[:200]}
+6 -4
View File
@@ -19,7 +19,8 @@ def has_idempotent_label_removal(workflow: str) -> bool:
return bool(
re.search(
r"for \(const name of staleManaged\) \{\s*"
r"try \{\s*await github\.rest\.issues\.removeLabel\(\{.*?"
r"try \{.*?await github\.request\('DELETE /repos/\{owner\}/\{repo\}/issues/\{issue_number\}/labels/\{id\}', \{.*?"
r"id: currentLabelIds\.get\(name\),\s*"
r"\}\);\s*\} catch \(error\) \{\s*"
r"if \(error\.status !== 404\) \{\s*throw error;\s*\}\s*"
r"console\.log\(`Managed label already absent: \$\{name\}`\);\s*"
@@ -64,7 +65,7 @@ def main() -> int:
"infrastructure",
"life",
"custodian",
"priority: high",
"P0 — critical", "P1 — high", "P2 — medium", "P3 — low",
"bug",
"feature",
)
@@ -80,7 +81,8 @@ def main() -> int:
)
check(
"stale managed labels are removed",
"managedLabels.has" in auto_label and "!desiredLabels.has" in auto_label and "removeLabel" in auto_label,
"managedLabels.has" in auto_label and "!desiredLabels.has" in auto_label
and "currentLabelIds.get(name)" in auto_label,
failures,
)
check(
@@ -231,7 +233,7 @@ def main() -> int:
)
check(
"Rune minor version advertises provisioning and restore verification",
'__version__ = "0.96.0"' in rune_version,
'__version__ = "0.99.1"' in rune_version,
failures,
)
# #169 policy update: auto-bump on merge is sanctioned (Nick directive:
+15
View File
@@ -21,6 +21,21 @@ _LIB_PATH = Path(
_lib = None
def core():
"""The shared kernel probe (strangler law): the delegation entry the
kernel-consuming modules use. RUNE_COGNITION_CORE=python pins the
fallback — THIS is where the pin is honored, so every consumer gets it
(a module-local probe that ignores the pin silently defeats it)."""
import os
if os.environ.get("RUNE_COGNITION_CORE", "auto") == "python":
return None
try:
return _load()
except Exception: # noqa: BLE001 — build/loader failures mean fallback
return None
def available() -> bool:
return _LIB_PATH.is_file()
+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.96.0
Version: v0.99.1
Features: 41 (24 original + 17 new native commands)
Usage:
@@ -1895,7 +1895,7 @@ class RuneApp(App):
# Show version info
self.chat_channel.add_message(
"system",
f"[dim]Rune v0.96.0 | Textual TUI | {len(loaded)} native modules[/dim]",
f"[dim]Rune v0.99.1 | Textual TUI | {len(loaded)} native modules[/dim]",
)
# Auto-show night mode suggestion if evening/night hours
+117 -78
View File
@@ -86,6 +86,110 @@ PATTERN_MAX_PATTERNS = 200
# ═══════════════════════════════════════════════════════════════════════════════
def _keyword_stopwords() -> list:
"""Gather seam (W3.4): the pattern-keyword stopword table as a sorted
list — data, not logic; both engines read this module-owned table."""
return sorted(_KEYWORD_STOPWORDS)
_KEYWORD_STOPWORDS = frozenset({
"the",
"and",
"for",
"are",
"but",
"not",
"you",
"all",
"can",
"had",
"her",
"was",
"one",
"our",
"out",
"day",
"get",
"has",
"him",
"his",
"how",
"its",
"may",
"new",
"now",
"old",
"see",
"way",
"who",
"did",
"let",
"say",
"she",
"too",
"use",
"this",
"that",
"with",
"have",
"from",
"they",
"been",
"said",
"each",
"make",
"like",
"just",
"over",
"such",
"take",
"year",
"them",
"some",
"time",
"very",
"when",
"come",
"could",
"what",
"there",
"their",
"will",
"would",
"about",
"which",
"these",
"other",
"into",
"more",
"than",
"also",
"then",
})
def _core():
"""The Rust cognition_core when available (strangler: Python fallback
load-bearing). RUNE_COGNITION_CORE=python pins the fallback."""
return None if cc_loader_pin() else _cc_probe()
def _cc_probe():
from rune import cc_loader as _cl
try:
return _cl if _cl.available() else None
except Exception: # noqa: BLE001, S110 — loader probe never crashes the fallback
return None
def cc_loader_pin() -> bool:
"""The env pin: RUNE_COGNITION_CORE=python → kernels OFF (shared probe).
"""
import os
return os.environ.get("RUNE_COGNITION_CORE", "auto") == "python"
@dataclass
class OverridePattern:
"""A learned pattern that requires override."""
@@ -105,7 +209,14 @@ class OverridePattern:
return self.successes / total
def matches(self, text: str) -> bool:
"""Check if text matches this pattern's keywords."""
"""Check if text matches this pattern's keywords. Deterministic
keyword-count check (kernel port NOT owed here — the W3.4 safety
kernels cover the params/rows surfaces; this tiny comparison stays
Python-side per the membrane law: gather/IO in Python, kernels for
deterministic row work already ported)."""
return self._py_matches(text)
def _py_matches(self, text: str) -> bool:
text_lower = text.lower()
matches = sum(1 for kw in self.keywords if kw.lower() in text_lower)
return matches >= PATTERN_MIN_KEYWORDS
@@ -293,84 +404,12 @@ class SafetyOverride:
self.stats.patterns_learned += 1
def _extract_keywords(self, text: str) -> list[str]:
"""Extract distinctive keywords from text for pattern matching."""
# Remove common stop words, keep words > 3 chars
stop_words = {
"the",
"and",
"for",
"are",
"but",
"not",
"you",
"all",
"can",
"had",
"her",
"was",
"one",
"our",
"out",
"day",
"get",
"has",
"him",
"his",
"how",
"its",
"may",
"new",
"now",
"old",
"see",
"way",
"who",
"did",
"let",
"say",
"she",
"too",
"use",
"this",
"that",
"with",
"have",
"from",
"they",
"been",
"said",
"each",
"make",
"like",
"just",
"over",
"such",
"take",
"year",
"them",
"some",
"time",
"very",
"when",
"come",
"could",
"what",
"there",
"their",
"will",
"would",
"about",
"which",
"these",
"other",
"into",
"more",
"than",
"also",
"then",
}
"""Extract distinctive keywords from text for pattern matching.
Keyword extraction stays Python-side (W3.4 scope note): the safety
kernels cover the params/rows surfaces; this extractor is a gather
(IO-adjacent list build) per the membrane law."""
words = re.findall(r"[a-z]{4,}", text.lower())
keywords = [w for w in words if w not in stop_words]
keywords = [w for w in words if w not in _KEYWORD_STOPWORDS]
# Return top 5 most distinctive words
return keywords[:5]
+8 -103
View File
@@ -53,6 +53,12 @@ from rune.structural_store import ( # shared generic core (SCAFFOLD-TRANCHE-001
reconcile_authorities, mark_conflict, resolve_status, authority_health,
dedupe_observations as _ss_dedupe_observations,
)
from rune.spine_language import ( # noqa: F401 — re-exported (import-compat): the
# #2465 doc-level transforms live here now (giant-file carve); tests and
# the canary_spine_meta read them THROUGH spine, exactly as before.
_classification_sides, _place_languages, _normalize_discovery_ids,
_apply_contract_discoveries, _contract_discoveries,
)
SCHEMA_VERSION = 1
@@ -337,108 +343,14 @@ def _rust_core():
return None
def _place_languages(doc: dict) -> int:
"""Stamp a language verdict per entity (no-op for entities that carry
one). Deterministic: substring classification over the entity id + the
kind; the exact same rule construction_decision.language_placement
implements (kept inline here to avoid a per-entity FFI crossing for
the spine's own 218 entities; the FFI path is used in contracts)."""
GO_HINTS = ("server", "transport", "dispatch", "entry", "bridge",
"hook_engine", "provider", "watch", "notif", "voice",
"gitea", "event_", "message_", "tool_exec", "work_manager")
RUST_HINTS = ("memory", "drift", "warm", "capab", "token", "spine",
"scaffold", "construct", "structural", "curator",
"consolidat", "elastic", "cognit", "abstract", "contract",
"ledger", "audit", "claim", "evidence", "behavio",
"verif", "pins", "witness", "anchor", "growth", "goal",
"salience", "store", "tui_", "rune_tui")
placed = 0
for e in doc.get("entities", []):
eid = e.get("id", "")
kind = e.get("kind", "")
if eid.startswith("contract:"):
lang = "n/a"
elif kind == "rust-crate":
lang = "rust"
elif kind == "go-surface":
lang = "go"
elif kind == "workflow" or kind == "config":
lang = "n/a"
elif kind == "deployment_surface":
lang = "go"
elif "tui" in eid:
lang = "python-tui (Wave 6)"
elif any(h in eid for h in GO_HINTS):
lang = "go"
elif any(h in eid for h in RUST_HINTS):
lang = "rust"
elif kind == "module":
lang = "rust"
else:
lang = "unassessed"
e.setdefault("identities", {})["language"] = lang
if lang != "unassessed":
placed += 1
return placed
def _apply_contract_discoveries(doc: dict) -> int:
"""Contract-discovery write-back (issue #2465 addition 2): entities
DISCOVERED by closed contracts become modeled spine entities — the
spine learns what construction actually built. Doc-level transform,
applied to BOTH cores' output. The CANDIDATE law holds: only CLOSED
contracts write back."""
placed = 0
for cid, entities in _contract_discoveries():
for eid, kind in entities:
before = len(doc.get("entities", []))
add_entity(doc, id=eid, kind=kind,
tags=["code", "contract-discovery"],
evidence_refs=[f"contract-discovery:{cid}"],
maturity="PRESENT")
if len(doc.get("entities", [])) > before:
placed += 1
else:
placed += 0 # high-water merge — no new entity, still evidence
return placed
def _contract_discoveries():
"""Closed contracts' DISCOVERED entities (issue #2465 addition 2):
expected_new_observed + candidate corrections, gathered from the
scaffold store. Returns [(contract_id, [(entity_id, kind), ...])]."""
try:
doc = json.loads((ROOT / "store" / "scaffold" / "current.json").read_text())
except (OSError, json.JSONDecodeError):
return []
out = []
for e in doc.get("entities", []):
cid = e.get("id", "")
if not cid.startswith("contract:") or e.get("phase") != "closed":
continue
q = e.get("ext", {}).get("contract_quality", {})
found = []
# expected-new surfaces observed at close (REAL delivered entities)
for eid in q.get("expected_new_observed", []):
kind = eid.split(":", 1)[0] if ":" in eid else "module"
if kind in ("rust-crate", "go-surface", "config", "workflow", "module"):
found.append((eid, kind))
# candidate corrections: the closure flagged them for modeling
for cand in q.get("candidate_corrections", []):
eid = cand.get("entity", "")
kind = eid.split(":", 1)[0] if ":" in eid else "module"
if kind in ("rust-crate", "go-surface", "config", "workflow", "module"):
found.append((eid, kind))
if found:
out.append((cid[9:], found))
return out
def _gather_build_inputs(manifest_path: Path | None = None) -> dict:
"""Every I/O read build() needs, in one place (zero-dep split law:
Python scans, the core — Rust or Python — only assembles). Both cores
consume this dict, so parity divergence is logic, never environment."""
existing = load()
if existing:
_normalize_discovery_ids(existing)
seeded = bool(existing) and any(
r["type"] == "belongs_to" for r in existing["relationships"])
# ingest manifest: only consulted when the unseeded path needs it
@@ -615,13 +527,6 @@ def _py_build_from_inputs(inputs: dict) -> dict:
evidence_refs=[f"umbrella-manifest#{name}"])
# 6. language placement (issue #2465 addition 3): every entity carries a
# language verdict — rune units get the GO/RUST dichotomy; other
# projects get a fit assessment. The verdict comes from
# construction_decision.language_placement (the protocol's own
# intelligence — the Rust language.rs kernel when built).
_place_languages(doc)
# reconcile: facts owned by projectors that no longer observe them
# become STALE (marked, never deleted — history survives; open-world
# preservation applies to everything, staleness is the truth marker)
+200
View File
@@ -0,0 +1,200 @@
"""spine_language.py — the #2465 doc-level transforms (carved from spine.py
to honor the giant-file henxel: keep it under 1200 lines).
Three transforms, applied to BOTH cores' output at build time:
1. language placement — every entity gets a language verdict (kind
defaults; subsystems derive from member-majority over the measured
classification map);
2. discovery-ID normalization — migrate legacy unqualified contract
discoveries to construction-surface kind without losing evidence;
3. contract-discovery write-back — closed contracts teach the spine
what construction actually built (observed → PRESENT, candidate →
DECLARED; the CANDIDATE law holds).
Same dependency laws as spine.py: structural_store primitives only, no
I/O beyond the classification/scaffold store reads, deterministic.
"""
from __future__ import annotations
import json
from pathlib import Path
from rune.structural_store import add_entity, now
ROOT = Path(__file__).resolve().parents[2]
SRC_DIR = ROOT / "src" / "rune"
def _classification_sides() -> dict:
"""The measured dichotomy map (W0): module stem → RUST|GO|TUI-LAST|...
Loaded once per _place_languages call, best-effort — a missing or
unreadable classification.json degrades to {}/unassessed verdicts,
never to invented facts."""
try:
data = json.loads((ROOT / "store" / "rebase" / "classification.json").read_text())
return {name: rec.get("side", "") for name, rec in data.items() if isinstance(rec, dict)}
except (OSError, json.JSONDecodeError, AttributeError):
return {}
def _place_languages(doc: dict) -> int:
"""Stamp a language verdict per entity (no-op for entities that carry
one). Deterministic: substring classification over the entity id + the
kind; the exact same rule construction_decision.language_placement
implements (kept inline here to avoid a per-entity FFI crossing for
the spine's own 218 entities; the FFI path is used in contracts)."""
GO_HINTS = ("server", "transport", "dispatch", "entry", "bridge",
"hook_engine", "provider", "watch", "notif", "voice",
"gitea", "event_", "message_", "tool_exec", "work_manager")
RUST_HINTS = ("memory", "drift", "warm", "capab", "token", "spine",
"scaffold", "construct", "structural", "curator",
"consolidat", "elastic", "cognit", "abstract", "contract",
"ledger", "audit", "claim", "evidence", "behavio",
"verif", "pins", "witness", "anchor", "growth", "goal",
"salience", "store", "tui_", "rune_tui")
placed = 0
CLASSIFICATION_SIDES = _classification_sides()
for e in doc.get("entities", []):
eid = e.get("id", "")
kind = e.get("kind", "")
if eid.startswith("contract:"):
lang = "n/a"
elif kind == "rust-crate":
lang = "rust"
elif kind == "go-surface":
lang = "go"
elif kind == "workflow" or kind == "config":
lang = "n/a"
elif kind == "deployment_surface":
lang = "go"
elif "tui" in eid:
lang = "python-tui (Wave 6)"
elif any(h in eid for h in GO_HINTS):
lang = "go"
elif any(h in eid for h in RUST_HINTS):
lang = "rust"
elif kind == "module":
lang = "rust"
elif kind == "subsystem":
# A subsystem is a CONTAINER — its language is its members'
# majority side per the measured dichotomy. The evidence map is
# classification.json (measured, 174 modules; honest note: newer
# files may be unclassified, so the count of classified members
# rides the verdict). Members arrive as belongs_to relationships
# whose from-ids carry module:<name> — map through classification.
members = [
r.get("from", "").split(":", 1)[1]
for r in doc.get("relationships", [])
if r.get("type") == "belongs_to" and r.get("to") == eid
and r.get("from", "").startswith("module:")
]
sides = [
str(CLASSIFICATION_SIDES.get(m) or "").upper()
for m in members
]
rust = sides.count("RUST")
go = sides.count("GO")
if rust and go:
lang = f"mixed (rust {rust} / go {go} members)"
elif rust:
lang = "rust"
elif go:
lang = "go"
else:
lang = "unassessed"
else:
lang = "unassessed"
e.setdefault("identities", {})["language"] = lang
if lang != "unassessed":
placed += 1
return placed
def _normalize_discovery_ids(doc: dict) -> int:
"""Migrate the old unqualified contract references, preserving evidence.
A file path (or 'suite') is not a module ID. Only migrate the malformed
module records produced by the earlier discovery projector; leave all
other open-world entity classes alone.
"""
renamed = {}
observed = {
eid for _, entities in _contract_discoveries()
for eid, _, provenance in entities if provenance == "observed"
}
for e in doc.get("entities", []):
if (e.get("kind") == "module" and ":" not in e.get("id", "")
and any(ref.startswith("contract-discovery:")
for ref in e.get("evidence_refs", []))):
old = e["id"]
e["id"] = f"construction-surface:{old}"
e["kind"] = "construction-surface"
e.setdefault("identities", {})["source_ref"] = old
renamed[old] = e["id"]
if e.get("kind") == "construction-surface" and e.get("id", "").startswith("construction-surface:"):
# Earlier code promoted every *candidate* to PRESENT. Undo only
# that unsupported claim; an actually observed surface retains it.
if e["id"].split(":", 1)[1] not in observed:
e["maturity"] = "DECLARED"
e["tags"] = [tag for tag in e.get("tags", []) if tag != "code"]
for rel in doc.get("relationships", []):
for endpoint in ("from", "to"):
if rel.get(endpoint) in renamed:
rel[endpoint] = renamed[rel[endpoint]]
return len(renamed)
def _apply_contract_discoveries(doc: dict) -> int:
"""Contract-discovery write-back (issue #2465 addition 2): entities
DISCOVERED by closed contracts become modeled spine entities — the
spine learns what construction actually built. Doc-level transform,
applied to BOTH cores' output. The CANDIDATE law holds: only CLOSED
contracts write back."""
placed = 0
for cid, entities in _contract_discoveries():
for eid, kind, provenance in entities:
before = len(doc.get("entities", []))
if kind == "construction-surface":
eid = f"construction-surface:{eid}"
add_entity(doc, id=eid, kind=kind,
tags=["contract-discovery"],
identities={"source_ref": eid.split(":", 1)[1]} if kind == "construction-surface" else None,
evidence_refs=[f"contract-discovery:{cid}"],
maturity="PRESENT" if provenance == "observed" else "DECLARED")
if len(doc.get("entities", [])) > before:
placed += 1
else:
placed += 0 # high-water merge — no new entity, still evidence
return placed
def _contract_discoveries():
"""Closed contracts' DISCOVERED entities (issue #2465 addition 2):
expected_new_observed + candidate corrections, gathered from the
scaffold store. Returns [(contract_id, [(entity_id, kind), ...])]."""
try:
doc = json.loads((ROOT / "store" / "scaffold" / "current.json").read_text())
except (OSError, json.JSONDecodeError):
return []
out = []
for e in doc.get("entities", []):
cid = e.get("id", "")
if not cid.startswith("contract:") or e.get("phase") != "closed":
continue
q = e.get("ext", {}).get("contract_quality", {})
found = []
# expected-new surfaces observed at close (REAL delivered entities)
for eid in q.get("expected_new_observed", []):
kind = eid.split(":", 1)[0] if ":" in eid else "construction-surface"
if kind in ("rust-crate", "go-surface", "config", "workflow", "module"):
found.append((eid, kind, "observed"))
elif kind == "construction-surface":
found.append((eid, kind, "observed"))
# candidate corrections: the closure flagged them for modeling
for cand in q.get("candidate_corrections", []):
eid = cand.get("entity", "")
kind = eid.split(":", 1)[0] if ":" in eid else "construction-surface"
if eid and kind in ("rust-crate", "go-surface", "config", "workflow", "module", "construction-surface"):
found.append((eid, kind, "candidate"))
if found:
out.append((cid[9:], found))
return out
+82 -2
View File
@@ -20,6 +20,33 @@ HOT_THRESHOLD = 7 # accessed within 7 days = hot
WARM_THRESHOLD = 30 # accessed within 30 days = warm
# > 30 days = cold
def _core():
"""The Rust cognition_core when available (strangler: Python fallback is
load-bearing). RUNE_COGNITION_CORE=python pins the fallback."""
return None if cc_loader_pin() else _cc_probe()
def _cc_probe():
from rune import cc_loader as _cl
try:
return _cl if _cl.available() else None
except Exception: # noqa: BLE001, S110 — loader probe never crashes the fallback
return None
def cc_loader_pin() -> bool:
"""The env pin: RUNE_COGNITION_CORE=python → kernels OFF (shared probe).
"""
import os
return os.environ.get("RUNE_COGNITION_CORE", "auto") == "python"
def _py_core_now() -> str:
"""The injected clock (iso Z) — module-level for the kernel boundary."""
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
# Tier storage characteristics
TIER_CONFIG = {
"hot": {
@@ -56,7 +83,24 @@ class TierManager:
"""Determine the tier for an entry based on its access patterns.
Returns 'hot', 'warm', or 'cold'.
Delegates to the Rust kernel when built; the Python body below is
the parity oracle (W3.4: identical thresholds injected, naive
timestamps refuse per the PARITY TRAP law).
"""
core = _core()
if core is not None:
try:
from rune import cc_loader as _cl
raw = _cl.tier_get(entry, _py_core_now(),
float(HOT_THRESHOLD), float(WARM_THRESHOLD))
return str(raw)
except Exception: # noqa: BLE001, S110 — kernel failure → oracle, never a broken call
pass
return self._py_get_tier(entry)
def _py_get_tier(self, entry: dict) -> str:
"""Python parity oracle (W3.4)."""
wash_count = entry.get("wash_count", 0) or 0
last_accessed = entry.get("last_accessed")
rock_status = entry.get("rock_status", "washable")
@@ -88,7 +132,26 @@ class TierManager:
return "warm"
def promote(self, entry: dict) -> str:
"""Promote an entry to a higher tier. Returns new tier."""
"""Promote an entry to a higher tier. Returns new tier.
Delegates to the Rust kernel when built (Python oracle below)."""
core = _core()
if core is not None:
try:
from rune import cc_loader as _cl
# The kernel's promote/demote take the FULL ENTRY (plus
# thresholds + clock) — not a tier string. They recompute
# the current tier internally, so a tier-string call was
# silently landing on defaults (the live probe caught it).
raw = _cl.tier_promote(entry, _py_core_now(),
float(HOT_THRESHOLD), float(WARM_THRESHOLD))
return str(raw)
except Exception: # noqa: BLE001, S110 — kernel failure → oracle fallback, intentional silence
pass
return self._py_promote(entry)
def _py_promote(self, entry: dict) -> str:
"""Python parity oracle."""
current = self.get_tier(entry)
if current == "cold":
return "warm"
@@ -97,7 +160,24 @@ class TierManager:
return "hot" # already hot
def demote(self, entry: dict) -> str:
"""Demote an entry to a lower tier. Returns new tier."""
"""Demote an entry to a lower tier. Returns new tier.
Delegates to the Rust kernel when built (Python oracle below)."""
core = _core()
if core is not None:
try:
from rune import cc_loader as _cl
# Same full-entry contract as promote; the kernel handles
# rock_status internally (rock never demotes).
raw = _cl.tier_demote(entry, _py_core_now(),
float(HOT_THRESHOLD), float(WARM_THRESHOLD))
return str(raw)
except Exception: # noqa: BLE001, S110 — kernel failure → oracle fallback, intentional silence
pass
return self._py_demote(entry)
def _py_demote(self, entry: dict) -> str:
"""Python parity oracle."""
current = self.get_tier(entry)
rock_status = entry.get("rock_status", "washable")
+127 -112
View File
@@ -84,76 +84,129 @@ def is_correction(text):
return False
def extract_topic(text):
"""Extract topic keywords from text. Deterministic."""
stopwords = {
"the",
"a",
"an",
"is",
"are",
"was",
"were",
"be",
"been",
"being",
"have",
"has",
"had",
"do",
"does",
"did",
"will",
"would",
"could",
"should",
"may",
"might",
"shall",
"can",
"to",
"of",
"in",
"for",
"on",
"with",
"at",
"by",
"from",
"as",
"into",
"through",
"during",
"before",
"after",
"this",
"that",
"these",
"those",
"it",
"its",
"what",
"which",
"who",
"how",
"when",
"where",
"why",
"and",
"or",
"but",
"not",
"no",
def _stopwords() -> list:
"""Gather seam (W3.4 delegation): the topic-extraction stopword table
as a SORTED list — sorted so JSON travel and kernel-side matching see
a deterministic order regardless of the set's iteration order."""
return sorted(_STOPWORDS)
_STOPWORDS = {
"the", "a", "an", "is", "are", "was", "were", "be", "been", "being",
"have", "has", "had", "do", "does", "did", "will", "would", "could",
"should", "may", "might", "shall", "can", "to", "of", "in", "for",
"on", "with", "at", "by", "from", "as", "into", "through", "during",
"before", "after", "this", "that", "these", "those", "it", "its",
"what", "which", "who", "how", "when", "where", "why", "and", "or",
"but", "not", "no",
}
def _tables() -> dict:
"""Gather seam: ALL regex pattern tables as data in one dict — the
exact contract the cc_witness_signals kernel consumes (parity-proven
per-table-row; regex_lite_search is the faithful subset for THESE
shapes). Sorted lists keep travel deterministic."""
return {
"stopwords": _stopwords(),
"correction": sorted(CORRECTION_PATTERNS),
"commitment": sorted([
r"\bI'?ll\s+(send|do|fix|build|check|update|create|write)\b",
r"\bI will\s+(send|do|fix|build|check|update|create|write)\b",
r"\blet me\s+(send|do|fix|build|check|update|create|write)\b",
r"\bremind me\b",
r"\bI promise\b",
r"\bI need to\b",
]),
"contradiction": sorted([r"\b(but|however|actually|wait|that contradicts)\b"]),
"preference": sorted([
r"\bI (prefer|like|love|hate|dislike)\b",
r"\bI'?m (not )?(a fan of|into|interested in)\b",
r"\b(don'?t|do not) (like|want|need)\b",
r"\b(always|never) (use|do|say)\b",
]),
"urgency": sorted([
r"\b(urgent|asap|immediately|now|quick|hurry)\b",
r"\b(deadline|due|overdue|late)\b",
r"\b(important|critical|crucial|vital)\b",
]),
"learning": sorted([
r"\b(learn|understand|explain|teach|show me)\b",
r"\b(how (does|do|did)|why (does|do|did)|what (is|are|was|were))\b",
r"\b(I don'?t (understand|know|get))\b",
]),
}
def _core():
"""The Rust cognition_core when available (strangler: Python fallback
load-bearing). RUNE_COGNITION_CORE=python pins the fallback."""
return None if cc_loader_pin() else _cc_probe()
def _cc_probe():
from rune import cc_loader as _cl
try:
return _cl if _cl.available() else None
except Exception: # noqa: BLE001, S110 — loader probe never crashes the fallback
return None
def cc_loader_pin() -> bool:
"""The env pin: RUNE_COGNITION_CORE=python → kernels OFF (shared probe).
"""
import os
return os.environ.get("RUNE_COGNITION_CORE", "auto") == "python"
def extract_topic(text):
"""Extract topic keywords from text. Deterministic.
Delegates to the kernel when built; Python oracle below."""
core = _core()
if core is not None:
try:
from rune import cc_loader as _cl
raw = _cl.witness_extract_topic(text, _stopwords())
return list(raw) if isinstance(raw, list) else list(raw or [])
except Exception: # noqa: BLE001, S110 — kernel failure → oracle, intentional silence
pass
words = re.findall(r"\w+", text.lower())
return [w for w in words if len(w) > 3 and w not in stopwords][:5]
return [w for w in words if len(w) > 3 and w not in _STOPWORDS][:5]
def _py_extract_topic(text):
"""Python parity oracle (W3.4)."""
words = re.findall(r"\w+", text.lower())
return [w for w in words if len(w) > 3 and w not in _STOPWORDS][:5]
def detect_signals(message, tool_error=None, task_status=None, recent_topics=None):
"""Tier 1: Reflex — extract signals from the turn. Pure Python, <1ms.
Returns dict of signals. Empty dict = nothing meaningful happened.
Delegates to the kernel when built (tables travel as data per the
REGEX-TABLE PORT LAW); Python oracle below is the parity authority.
"""
core = _core()
if core is not None:
try:
from rune import cc_loader as _cl
raw = _cl.witness_signals(message, _tables(), tool_error or "",
task_status or "", recent_topics or [])
if isinstance(raw, dict):
return {k: v for k, v in raw.items() if v not in (False, "")}
# non-dict → fall through to the oracle
except Exception: # noqa: BLE001, S110 — kernel failure → oracle, intentional silence
pass
return _py_detect_signals(message, tool_error, task_status, recent_topics)
def _py_detect_signals(message, tool_error=None, task_status=None,
recent_topics=None):
"""Python parity oracle (W3.4). Local tables via the gather seam."""
signals = {}
# Error signal
@@ -179,62 +232,24 @@ def detect_signals(message, tool_error=None, task_status=None, recent_topics=Non
signals["topic_drift"] = True
signals["new_topics"] = list(current_topics - recent_set)[:3]
# Commitment detection
commitment_patterns = [
r"\bI'?ll\s+(send|do|fix|build|check|update|create|write)\b",
r"\bI will\s+(send|do|fix|build|check|update|create|write)\b",
r"\blet me\s+(send|do|fix|build|check|update|create|write)\b",
r"\bremind me\b",
r"\bI promise\b",
r"\bI need to\b",
]
if message:
for pattern in commitment_patterns:
if re.search(pattern, message, re.IGNORECASE):
signals["commitment_detected"] = True
break
# Commitment detection (tables live in _tables() — the gather seam;
# the oracle reads the SAME table data the kernel receives)
for signal_name, table_name in (
("commitment_detected", "commitment"),
("preference_detected", "preference"),
("urgency_detected", "urgency"),
("learning_opportunity", "learning"),
):
if message:
for pattern in _tables().get(table_name, []):
if re.search(pattern, message, re.IGNORECASE):
signals[signal_name] = True
break
# Contradiction signal (simple: message contains "but" or "however" after a statement)
if message and re.search(r"\b(but|however|actually|wait|that contradicts)\b", message.lower()):
signals["possible_contradiction"] = True
# Preference detection
preference_patterns = [
r"\bI (prefer|like|love|hate|dislike)\b",
r"\bI'?m (not )?(a fan of|into|interested in)\b",
r"\b(don'?t|do not) (like|want|need)\b",
r"\b(always|never) (use|do|say)\b",
]
if message:
for pattern in preference_patterns:
if re.search(pattern, message, re.IGNORECASE):
signals["preference_detected"] = True
break
# Urgency detection
urgency_patterns = [
r"\b(urgent|asap|immediately|now|quick|hurry)\b",
r"\b(deadline|due|overdue|late)\b",
r"\b(important|critical|crucial|vital)\b",
]
if message:
for pattern in urgency_patterns:
if re.search(pattern, message, re.IGNORECASE):
signals["urgency_detected"] = True
break
# Learning opportunity detection
learning_patterns = [
r"\b(learn|understand|explain|teach|show me)\b",
r"\b(how (does|do|did)|why (does|do|did)|what (is|are|was|were))\b",
r"\b(I don'?t (understand|know|get))\b",
]
if message:
for pattern in learning_patterns:
if re.search(pattern, message, re.IGNORECASE):
signals["learning_opportunity"] = True
break
return signals
+52 -12
View File
@@ -132,27 +132,67 @@ def main():
check("demote rock=hot (immobile)",
cc_loader.tier_demote({"wash_count": 0, "rock_status": "rock"}, NOW_G) == "hot")
# ── witness + safety + abstraction (W3.4 continued) ──────────────────
# ── witness + safety + abstraction (W3.4, FULL-TABLE parity) ──────────
print("\n--- witness/safety/abstraction kernels ---")
from rune import witness as wt, safety_override as so, abstraction as ab
import re as _re
# the module's REAL tables as data (stopwords/travel tables)
sw = sorted(wt.extract_topic.__doc__ or "") # placeholder — use the real stopwords from a probe call
# extract topics via the PYTHON path first to get the stopwords table
topics_py = wt.extract_topic("the deployment runner needs repair now")
rs_topics = cc_loader.witness_extract_topic("the deployment runner needs repair now", [])
check("witness extract topic", topics_py == rs_topics,
# REAL gather seams (PATTERN-TABLE GATHER-SEAM LAW: canaries call the
# module's actual seams, not hand-typed tables)
sw = wt._stopwords()
topics_py = wt._py_extract_topic("the deployment runner needs repair now")
rs_topics = cc_loader.witness_extract_topic("the deployment runner needs repair now", sw)
check("witness extract topic (real stopwords)",
topics_py == rs_topics and topics_py == ["deployment", "runner", "needs", "repair"],
f"py={topics_py!r} rs={rs_topics!r}"[:200])
signals_py = wt.detect_signals("I'll fix this now, actually that's wrong; I prefer the other way. urgent!")
tables = {"stopwords": [], "correction": [], "commitment": [],
"contradiction": [], "preference": [], "urgency": [], "learning": []}
tables = wt._tables()
rs_sigs = cc_loader.witness_signals(
"I'll fix this now, actually that's wrong; I prefer the other way. urgent!",
tables)
check("witness signals (kernel subset)",
isinstance(rs_sigs, dict),
f"py={signals_py!r} rs={rs_sigs!r}"[:300])
rs_sigs_f = {k: v for k, v in rs_sigs.items() if v not in (False, "")}
check("witness signals (real tables, full parity)",
signals_py == rs_sigs_f,
f"py={signals_py!r} rs={rs_sigs_f!r}"[:300])
# PER-TABLE-ROW parity matrix (REGEX-TABLE PORT LAW: the faithful subset
# is proven PER ROW, not by one lucky combined message; truth = real
# Python re over the SAME table data)
_KEY = {"correction": "correction", "commitment": "commitment_detected",
"contradiction": "possible_contradiction", "preference": "preference_detected",
"urgency": "urgency_detected", "learning": "learning_opportunity"}
_ROWS = {
"correction": [("no, that's wrong", True), ("actually it's not what I said", True),
("fix this please", True), ("wrong answer", True),
("try again tomorrow", True),
("nope, try again", True), ("stop doing that", True),
("the deployment ran fine", False)],
"commitment": [("I'll send the report", True), ("let me fix it", True),
("remind me later", True), ("I promise it", True),
("I need to check", True), ("we should check", False)],
"contradiction": [("but the test passed", True), ("actually, wait", True),
("plain message", False)],
"preference": [("I prefer rust", True), ("I don't like eggs", True),
("always use argv", True), ("he likes it", False)],
"urgency": [("asap please", True), ("deadline tomorrow", True),
("critical bug", True), ("calm message", False)],
"learning": [("explain this", True), ("how does it work", True),
("I don't understand", True), ("it works", False)],
}
row_bad = []
for table, rows in _ROWS.items():
for msg, expect in rows:
exp = any(_re.search(p, msg, _re.IGNORECASE) for p in tables[table])
py = wt._py_detect_signals(msg)
got = bool(py.get(_KEY[table]))
rs = {k: v for k, v in cc_loader.witness_signals(msg, tables).items()
if v not in (False, "")}
rs_got = bool(rs.get(_KEY[table]))
if not (exp == got == rs_got == expect):
row_bad.append((table, msg[:30], expect, got, rs_got))
check(f"witness per-row parity matrix ({sum(len(r) for r in _ROWS.values())} rows)",
not row_bad, f"{row_bad[:3]!r}"[:300])
params = cc_loader.safety_get_params([{"key": "k1", "value": "v1"}])
check("safety get_params", params == {"key": "k1", "value": "v1"}, f"{params!r}")
+176 -19
View File
@@ -553,31 +553,188 @@ pub fn witness_signals(message: &str, tables: &Json,
Json::Obj(sig)
}
fn regex_lite_search(pattern: &str, text: &str) -> bool {
let low = text.to_lowercase();
let low_pat = pattern.to_lowercase();
// faithful subset: word tokens from the pattern (alternation groups
// split on |), every alternative must appear as a phrase
/// Expand top-level alternation of a pattern into full alternative strings
/// (groups become inline cartesian products of their inner alternatives).
fn regex_lite_alternatives(pattern: &str) -> Vec<String> {
let mut alts: Vec<String> = vec![];
let mut prefixes: Vec<String> = vec![String::new()];
let mut cur = String::new();
let mut depth = 0;
for ch in low_pat.chars() {
let mut depth = 0usize;
let chars: Vec<char> = pattern.to_lowercase().chars().collect();
for ch in chars {
match ch {
'(' => { depth += 1; if depth == 1 { alts.push(std::mem::take(&mut cur)); } }
')' => { depth -= 1; if depth == 0 { alts.push(std::mem::take(&mut cur)); } }
'|' if depth == 1 => { alts.push(std::mem::take(&mut cur)); }
_ => { cur.push(ch); }
'(' => {
depth += 1;
if depth == 1 {
if !cur.is_empty() {
prefixes = prefixes
.iter()
.map(|p| format!("{p}{cur}"))
.collect();
cur.clear();
}
} else {
cur.push('(');
}
}
')' => {
depth = depth.saturating_sub(1);
if depth == 0 {
// closing a top-level group: its content is an
// alternation set — expand against the accumulated
// prefixes (cartesian). prefixes.len() may be 1.
let group_alts: Vec<String> = cur.split('|').map(String::from).collect();
let mut next = vec![];
for p in &prefixes {
for g in &group_alts {
next.push(format!("{p}{g}"));
}
}
prefixes = next;
cur.clear();
} else if depth >= 1 {
cur.push(')');
}
}
'|' if depth == 0 => {
for p in &prefixes {
alts.push(format!("{p}{cur}"));
}
cur.clear();
}
_ => cur.push(ch),
}
}
alts.push(cur);
if depth == 0 {
for p in &prefixes {
alts.push(format!("{p}{cur}"));
}
} else {
// unbalanced group: treat literally
alts.push(cur);
}
alts.retain(|a| !a.is_empty());
alts.iter().all(|alt| {
let cleaned: String = alt.chars()
.filter(|c| c.is_alphanumeric() || *c == ' ' || *c == '_')
.collect::<String>()
.split_whitespace().collect::<Vec<_>>().join(" ");
cleaned.is_empty() || low.contains(&cleaned)
})
if alts.is_empty() {
alts.push(String::new());
}
alts
}
/// Split ONE alternative into ordered PHRASE-SEGMENTS, each segment being
/// 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![];
let mut cur: Vec<String> = vec![String::new()]; // variants of current phrase
let pc: Vec<char> = alt.chars().collect();
let mut j = 0usize;
while j < pc.len() {
let c = pc[j];
if c == '\\' && j + 1 < pc.len() {
let n = pc[j + 1];
match n {
'b' | 'B' => {} // zero-width anchor: drop
's' => {
if cur.iter().any(|v| !v.is_empty()) {
segments.push(std::mem::take(&mut cur));
cur = vec![String::new()];
}
if j + 2 < pc.len() && matches!(pc[j + 2], '*' | '+' | '?') {
j += 3;
} else {
j += 2;
}
continue;
}
other => {
for v in cur.iter_mut() {
v.push(other)
}
}
}
j += 2;
continue;
}
match c {
'?' => {
// optional previous char: each variant gains a SHORTER twin
let twinned: Vec<String> = cur
.iter()
.flat_map(|v| {
let mut out = vec![v.clone()];
if !v.is_empty() {
out.push(v[..v.len() - 1].to_string());
}
out
})
.collect();
cur = twinned;
}
'.' | '[' => {
if cur.iter().any(|v| !v.is_empty()) {
segments.push(std::mem::take(&mut cur));
cur = vec![String::new()];
}
if c == '[' {
let mut k = j + 1;
while k < pc.len() && pc[k] != ']' {
k += 1;
}
j = k;
}
}
'*' | '+' => {} // gap quantifier
'^' | '$' => {}
c if c.is_alphanumeric() || c == '\'' || c == ' ' || c == '-' || c == '_' => {
for v in cur.iter_mut() {
v.push(c)
}
}
_ => {
if cur.iter().any(|v| !v.is_empty()) {
segments.push(std::mem::take(&mut cur));
cur = vec![String::new()];
}
}
}
j += 1;
}
if cur.iter().any(|v| !v.is_empty()) {
segments.push(cur);
}
segments
}
fn regex_lite_search(pattern: &str, text: &str) -> bool {
let low = text.to_lowercase();
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.
fn walk(segments: &[Vec<String>], low: &str, cursor: usize) -> bool {
if segments.is_empty() {
return true;
}
for variant in &segments[0] {
if variant.is_empty() {
return walk(&segments[1..], low, cursor);
}
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) {
return true;
}
}
false
}
// ── safety_override kernels (W3.4) ─────────────────────────────────────────
+723 -380
View File
File diff suppressed because it is too large. Load diff
+41
View File
@@ -0,0 +1,41 @@
"""Exercise the Gitea auto-label script, not just its YAML shape."""
import shutil
import subprocess
from pathlib import Path
import pytest
import yaml
def test_duplicate_priority_reconciled_by_numeric_id(tmp_path):
node = shutil.which("node")
if node is None:
pytest.skip("node not installed")
workflow = yaml.safe_load((Path(__file__).parents[1] / ".gitea/workflows/auto-label.yml").read_text())
script = workflow["jobs"]["auto-label"]["steps"][0]["with"]["script"]
fixture = """
const labels = [{name:'P0 — critical',id:23},{name:'P2 — medium',id:25}];
const context = {payload:{action:'labeled',issue:{state:'open',number:2467,
title:'Scaffolding completion',body:'kind: feature\\nspine protocol'}},
repo:{owner:'conductor',repo:'rune'}};
const github = {rest:{issues:{
listLabelsOnIssue: async () => ({data:labels.map(x => ({...x}))}),
addLabels: async ({labels:names}) => {
for (const name of names) labels.push({name,id:100+labels.length});
}
}},request: async (route,{id}) => {
if (!route.includes('/labels/{id}') || typeof id !== 'number')
throw new Error('Gitea requires numeric label ID');
const index=labels.findIndex(x => x.id === id);
if (index < 0) throw new Error('missing label ID '+id);
labels.splice(index,1);
}};
"""
js = (fixture + "\n(async () => {\n" + script + "\n})().then(() => {\n"
+ "const priorities=labels.filter(x => /^P[0-3] — /.test(x.name));\n"
+ "if (priorities.length !== 1 || priorities[0].id !== 23) process.exitCode=1;\n"
+ "}).catch(e => {console.error(e);process.exitCode=1;});\n")
path = tmp_path / "priority.js"
path.write_text(js)
run = subprocess.run([node, str(path)], capture_output=True, text=True, timeout=10)
assert run.returncode == 0, run.stdout + run.stderr
+25
View File
@@ -17,6 +17,31 @@ sys.path.insert(0, str(ROOT / "src"))
from rune import spine
def test_contract_discovery_preserves_unqualified_surface_without_inventing_module(monkeypatch):
monkeypatch.setattr(spine, "_contract_discoveries", lambda: [
("example", [("src/rune/audit_ledger.py", "construction-surface", "candidate"),
("rust-crate:cognition_core", "rust-crate", "observed")])])
doc = spine.new_doc()
spine._apply_contract_discoveries(doc)
found = {e["id"]: e for e in doc["entities"]}
assert found["construction-surface:src/rune/audit_ledger.py"]["kind"] == "construction-surface"
assert found["construction-surface:src/rune/audit_ledger.py"]["maturity"] == "DECLARED"
assert found["rust-crate:cognition_core"]["maturity"] == "PRESENT"
def test_legacy_unqualified_discoveries_are_normalized_without_losing_evidence():
doc = spine.new_doc()
spine.add_entity(doc, id="src/rune/audit_ledger.py", kind="module",
maturity="PRESENT", evidence_refs=["contract-discovery:example"])
spine.add_relationship(doc, type="depends_on", from_id="src/rune/audit_ledger.py",
to_id="module:other", evidence_refs=["test"])
spine._normalize_discovery_ids(doc)
assert doc["entities"][0]["id"] == "construction-surface:src/rune/audit_ledger.py"
assert doc["entities"][0]["maturity"] == "DECLARED"
assert doc["entities"][0]["evidence_refs"] == ["contract-discovery:example"]
assert doc["relationships"][0]["from"] == "construction-surface:src/rune/audit_ledger.py"
# ── helpers ───────────────────────────────────────────────────────────────
@pytest.fixture
+4
View File
@@ -211,4 +211,8 @@ class TestSpineExtractionNeutrality:
"go-surface",
"config",
"workflow",
# Closed contracts may name files or validation surfaces that
# are not Python modules. Keep their provenance without lying
# about their module identity (issue #2465 CI regression).
"construction-surface",
}