Compare commits

...
25 Commits
Author SHA1 Message Date
Nick 7ec615a656 probe: ssh push test 2026-10-06 13:54:32 +01:00
conductor 0fdfe195e7 chore(release): auto-bump version to 0.99.6 — earned by #2473 (Scaffold: programme_continuation wired into close() (T5.15 slice 3) + same-secon)
Promotion record: http://127.0.0.1:3000/conductor/rune/pulls/2473
2026-10-06 03:52:23 +01:00
conductor 71d2d63a36 Merge pull request 'Scaffold: programme_continuation wired into close() (T5.15 slice 3) + same-second id fix' (#2473) from feat/wire-programme-continuation into master 2026-10-06 03:52:17 +01:00
Nick 0873b521e3 feat(scaffold): programme_continuation wired into close() (T5.15 slice 3) + same-second contract-id collision fix
Slice 3 of the completion wave: closing a contract that is a stage of a persisted Programme marks that stage COMPLETE, persists the ledger, and rides the continuation canary on the close envelope — the next eligible stage is SURFACED, never guessed (task done ≠ programme done). No programme bound → no continuation block; ledger failure degrades without failing the closure. Three contracts. Also fixed en route: contract ids were second-granularity timestamps that collided when two needs filed in the same second (red test proves distinct ids now, uuid suffix added).
2026-10-06 03:46:24 +01:00
conductor f7e6c5cff5 chore(release): auto-bump version to 0.99.5 — earned by #2470 (TUI: chat pane LEFT + post-response dim-spam consolidation (T5.14/T5.13))
Promotion record: http://127.0.0.1:3000/conductor/rune/pulls/2470
2026-10-06 03:27:08 +01:00
conductor fc46dbea52 Merge pull request 'TUI: chat pane LEFT + post-response dim-spam consolidation (T5.14/T5.13)' (#2470) from feat/tui-pane-swap-chat-left into master 2026-10-06 03:27:02 +01:00
conductor d8ede8f3f8 Merge branch 'master' into feat/tui-pane-swap-chat-left 2026-10-06 03:19:13 +01:00
conductor 804c2f62df chore(release): auto-bump version to 0.99.4 — earned by #2472 (Scaffold: construction_commands dispatch — the serving wire (T5.15 slice 2))
Promotion record: http://127.0.0.1:3000/conductor/rune/pulls/2472
2026-10-06 03:11:51 +01:00
conductor e5d10f18ae Merge pull request 'Scaffold: construction_commands dispatch — the serving wire (T5.15 slice 2)' (#2472) from feat/wire-construction-commands-dispatch into master 2026-10-06 03:11:44 +01:00
conductor 3ed58d48a9 Merge branch 'master' into feat/tui-pane-swap-chat-left 2026-10-06 03:02:57 +01:00
conductor 6c997b6af4 Merge branch 'master' into feat/wire-construction-commands-dispatch 2026-10-06 03:02:52 +01:00
conductor 9b5befb137 chore(release): auto-bump version to 0.99.3 — earned by #2471 (Scaffold: stage_guards wired into completion transitions (T5.15 slice 1))
Promotion record: http://127.0.0.1:3000/conductor/rune/pulls/2471
2026-10-06 02:50:18 +01:00
conductor 6899d859da Merge branch 'master' into feat/tui-pane-swap-chat-left 2026-10-06 02:50:11 +01:00
conductor 8ef70448c3 Merge pull request 'Scaffold: stage_guards wired into completion transitions (T5.15 slice 1)' (#2471) from feat/wire-stage-guards-transition into master 2026-10-06 02:50:08 +01:00
Nick 55f268f978 feat(scaffold): construction_commands dispatch — the serving wire (T5.15 slice 2)
The command seam validated intents but nothing EXECUTED them. dispatch() binds the canonical store, runs the validator, then executes accepted intents through the Construction Interface (resume/transition/amend_scope), refusing via the lifecycle with accepted=False+success=True (a refusal is a valid protocol outcome, not an error). Idempotent replay returns the recorded intent without re-executing. Three contracts: end-to-end phase movement, refusal shape, replay-no-double-execute; seam file 12/12. Slice 1 of 5 was stage_guards (#2467 wire wave).
2026-10-06 02:40:49 +01:00
Nick 320026a4a3 feat(scaffold): stage_guards wired into the completion-facing transitions (T5.15 slice 1)
The five orphaned V1 components: slice 1 wires stage_guards for real. transition(verifying|closed) now runs §15F readiness StageChecks derived from guard: verification obligations (or inline guard_checks); a blocked guard = VERIFICATION_FAILED envelope and the phase does NOT move — builder cannot self-certify past a failing check. building/free edges unchanged; capabilities() schema documents the new arg; CLI transport carries guard_checks as JSON arg 4. Four contracts red-first (blocked/pass/inline-override/free-edge); 56 interface+guards tests green.
2026-10-06 02:40:49 +01:00
Nick 0e88b6bc08 feat(tui): consolidate post-response dim spam into one collapsed line (#2468/T5.13)
Five separate dim system messages after every task/build response (budget, claims, growth, self-improve, plus the cron/queue echoes) spammed chat. New collect_post_response_signals() is a PURE headless collector with degrade-not-crash try blocks; the worker renders ONE line or silence. 6 frozen contracts: compose order [chat,task], resize semantics re-keyed, collector all-signals/silence/never-raises/cycle-multiple. 38 TUI/layout tests green; pyright error count unchanged at baseline 8.
2026-10-06 02:40:25 +01:00
Nick 004e5b3118 feat(tui): swap panes — chat LEFT, task RIGHT (#2465/T5.14)
Nick: chat-first feels natural. Compose yield order reversed (rune_tui.py compose), resize_left/resize_right actions re-keyed to the new visual positions (left=chat shrink now grows task), focus actions and all name-keyed query_one calls untouched (position-independent by design). Static source proof: yield order [chat, task]; 45 layout/TUI tests green.
2026-10-06 02:40:25 +01:00
conductor db15ae3650 chore(release): auto-bump version to 0.99.2 — earned by # (patch: converged release (push-to-master bump))
Promotion record: (unknown)
2026-10-06 02:28:19 +01:00
Nick d8ce21b76d fix(spine): the measured dichotomy decides — hints demoted to labeled fallbacks (#2461, Nick correction)
Nick caught the deviation: module verdicts were substring-guessed from names (memory→rust, gitea→go) with a silent rust default. Authority order now: the measured classification map (import-evidence) decides FIRST for every module; hint hits are labeled "(name hint)" so provenance is visible; unmeasured+unhinted modules are "unclassified" (12), never a guessed rust. Store rebuilt: 106 rust / 45 go measured, 33+7 hint-labeled, 23 test-infra, 12 honest unclassified. Battery 95 green (test updated to pin the authority contract).
2026-10-06 02:28:16 +01:00
Nick d4d43dbf67 merge: spine_language tests (the new-module henxel repair) 2026-10-06 02:07:44 +01:00
Nick 8a6c950140 test(spine_language): the carved transforms ship WITH tests (#2461 new-module henxel)
CI red at d67fc46: spine_language.py crossed the new-module henxel with zero coverage. Eight contracts: measured-map read + graceful degrade, kind-default verdicts, TUI/hints, subsystem member-majority, unqualified-discovery honesty (candidate stays DECLARED), legacy-ID normalization with edge re-pointing, and the closed-contract-only gather law. 8/8 green. Also: construction_commands dispatch WIP stashed on its own branch (slice 2, next).
2026-10-06 02:04:19 +01:00
Nick d67fc46581 chore(rebase): refresh store/rebase inventory + classification for the 6 post-W0 modules (#2461 close-out)
The W0 map had drifted (174 vs 180 files): cc_loader/sc_loader/mr_loader/mems_loader (FFI kernel membranes → RUST), spine_language (deterministic carve → RUST), __init__ (metadata reads) now carry evidence-backed side records and measured function/line counts. spine_language._classification_sides() — the runtime reader — sees 180 entries; spine battery green.
2026-10-06 01:40:56 +01:00
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
21 changed files with 8980 additions and 8047 deletions

No files matched your search

+1
View File
@@ -0,0 +1 @@
probe 1791291272
+1 -1
View File
@@ -1,6 +1,6 @@
# Rune
**Current release: v0.99.0**
**Current release: v0.99.6**
**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"
]
}
}
}
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "rune"
version = "0.99.0"
version = "0.99.6"
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
requires-python = ">=3.10"
+1 -1
View File
@@ -6,7 +6,7 @@ This is the main entry point for the Rune program.
import json
from pathlib import Path
__version__ = "0.99.0"
__version__ = "0.99.6"
# 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
+2 -2
View File
@@ -67,7 +67,7 @@ def _cc_probe():
from rune import cc_loader as _cl
try:
return _cl if _cl.available() else None
except Exception:
except Exception: # noqa: BLE001, S110 — loader probe never crashes the fallback
return None
@@ -309,7 +309,7 @@ Extract 1-5 key facts. Be precise, not verbose."""
"kernel": True,
"content_words": item.get("content_words"),
})
except Exception: # noqa: BLE001 — kernel failure degrades to empty
except Exception: # noqa: BLE001, S110 — kernel failure degrades to empty, intentional silence
pass
if not facts:
+1 -1
View File
@@ -233,7 +233,7 @@ def main() -> int:
)
check(
"Rune minor version advertises provisioning and restore verification",
'__version__ = "0.99.0"' in rune_version,
'__version__ = "0.99.6"' in rune_version,
failures,
)
# #169 policy update: auto-bump on merge is sanctioned (Nick directive:
+95
View File
@@ -171,3 +171,98 @@ def _accept(command_id: str, construction_id: str, correlation_id: str,
"recorded_at": time.strftime("%Y-%m-%dT%H:%M:%SZ",
time.gmtime()),
"idempotent_replay": False}
def dispatch(command_type: str, *, construction_id: str,
correlation_id: str = "", command_id: str = "",
payload: dict | None = None,
store_path: str = "store/scaffold") -> dict:
"""The SERVING layer (T5.15 slice 2): validate the command against the
canonical store, execute the accepted intent through the Construction
Interface, and return the interface's envelope with the command
provenance attached.
Single entry point for `rune scaffold <command>` (the Go proxy) and
any future transport. Idempotency: a repeated command_id replays the
recorded outcome without re-executing.
Refusal still returns success=False envelope? NO — a refused command
is a VALID protocol outcome: the envelope carries accepted=False and
success=True (the dispatch itself worked; the lifecycle refused the
intent). Execution failures carry success=False.
"""
iface_path = None
if store_path and store_path != "store/scaffold":
iface_path = store_path
try:
from rune import construction_interface as ci
# store_path carries the serving layer's "path|project_id" contract
# (a bare path = last segment is the project id, matching how the
# Go proxy and the interface's own default name it).
if iface_path:
path_part, _, proj_part = iface_path.partition("|")
proj = proj_part or path_part.rstrip("/").split("/")[-1]
iface = ci.ConstructionInterface(builder="substrate",
project_store=path_part,
project_id=proj)
else:
iface = ci.ConstructionInterface(builder="substrate")
except Exception as exc: # noqa: BLE001 — serving failure is an envelope, not a crash
return {"accepted": False, "executed": False,
"success": False, "error": f"serving unavailable: {exc}"[:200],
"command_type": command_type,
"construction_id": construction_id}
intent = command(command_type, construction_id=construction_id,
correlation_id=correlation_id, command_id=command_id,
payload=payload, scaffold=iface.scaffold)
if intent.get("idempotent_replay"):
# Repeated delivery: return the recorded outcome WITHOUT executing
# again (the ledger's whole point). The first execution's envelope
# is not re-derivable here, so the replay carries the intent and
# the flag — the caller knows the effect already happened.
return {"accepted": True, "executed": False,
"success": True, "idempotent_replay": True,
"op": intent.get("op"),
"command_type": command_type,
"construction_id": construction_id,
"correlation_id": intent.get("correlation_id", ""),
"command_id": intent.get("command_id", "")}
if not intent.get("accepted"):
return {"accepted": False, "executed": False, "success": True,
"reason": intent.get("reason"),
"command_type": command_type,
"construction_id": construction_id,
"correlation_id": intent.get("correlation_id", ""),
"command_id": intent.get("command_id", ""),
"idempotent_replay": intent.get("idempotent_replay", False)}
op = intent["op"]
args = intent["args"]
ops = {
"resume": lambda: iface.resume(args["contract_id"]),
"transition": lambda: iface.transition(
args["contract_id"], args["phase"],
evidence_ref=f"command:{intent['command_id']}"
+ (f" approved_by:{args['approved_by']}" if args.get("approved_by") else "")),
"amend_scope": lambda: iface.amend_scope(
args["contract_id"], args["add_paths"],
reason=args.get("reason", "substrate context"),
authority=args.get("authority", "substrate")),
}
fn = ops.get(op)
if fn is None:
return {"accepted": True, "executed": False, "success": False,
"error": f"no executor for op {op!r}",
"command_type": command_type,
"construction_id": construction_id}
env = fn()
return {"accepted": True, "executed": True,
"success": bool(env.get("success")),
"envelope": env,
"command_type": command_type,
"construction_id": construction_id,
"correlation_id": intent.get("correlation_id", ""),
"command_id": intent.get("command_id", ""),
"idempotent_replay": False}
+146 -12
View File
@@ -36,6 +36,7 @@ from typing import Any
import json
import sys
import uuid
from pathlib import Path
from rune import structural_store as ss
@@ -88,6 +89,39 @@ def _envelope(
return env
def _to_stage_check(spec: dict):
"""One guard spec → a stage_guards.StageCheck (T5.15 wire)."""
from rune import stage_guards as sg
return sg.StageCheck(
name=str(spec.get("name", "guard")),
kind=str(spec.get("kind", "assert")),
argv=spec.get("argv"),
output_pattern=str(spec.get("output_pattern", "")),
path=str(spec.get("path", "")),
check=spec.get("check"),
description=str(spec.get("description", "")),
)
def _guard_specs_from_obligations(c: dict) -> list[dict]:
"""The contract's guard declarations: verification_obligations rows
shaped `guard:<json-spec>` (the decision path records them; anything
else stays a free-text obligation for the projection). JSONDecodeError
→ skipped with no invented facts."""
specs: list[dict] = []
for item in (c.get("identities") or {}).get("verification_obligations", []) or []:
if not isinstance(item, str) or not item.startswith("guard:"):
continue
try:
spec = json.loads(item[6:])
if isinstance(spec, dict):
specs.append(spec)
except json.JSONDecodeError:
continue
return specs
class ConstructionInterface:
"""The builder-facing construction lifecycle for ONE project target.
@@ -291,6 +325,13 @@ class ConstructionInterface:
"transition": {
"phase": {"type": "str", "required": True,
"allowed": list(sc.PHASES)},
"evidence_ref": {"type": "str", "required": False},
"guard_checks": {
"type": "list[dict] | None", "required": False,
"notes": ("stage-guard specs run on the "
"completion edges (verifying/closed); "
"a failure BLOCKS with "
"VERIFICATION_FAILED — T5.15 wire")},
},
"close": {
"observed_evidence": {
@@ -381,7 +422,8 @@ class ConstructionInterface:
errors.append({"error": Errors.INSUFFICIENT_EVIDENCE, "detail": trace["rationale"][:200]})
contract = None
if generate and trace["resolution"] not in ("INSUFFICIENT_EVIDENCE",):
cid = contract_id or f"BUILDER-{ss.now()}-{self.builder}"
cid = (contract_id
or f"BUILDER-{ss.now()}-{uuid.uuid4().hex[:6]}-{self.builder}")
cd.generate_contract(
self.scaffold,
doc,
@@ -607,8 +649,57 @@ class ConstructionInterface:
warnings=warnings,
)
def transition(self, contract_id: str, phase: str, *, evidence_ref: str = "") -> dict:
def transition(self, contract_id: str, phase: str, *, evidence_ref: str = "",
guard_checks: list[dict] | None = None) -> dict:
"""Move a contract phase. Completion-wave slice (T5.15, the
stage_guards wire): entering `verifying` and `closed` runs the
§15F/§15G guards — readiness/exit StageChecks and test-truth
verification — when the contract declares them
(identities.verification_obligations carries `guard:<spec>` rows,
or guard_checks passes inline specs).
guard_checks spec: {"name", "kind": "command"|"path_exists"|"assert",
"argv"/"path"/"output_pattern", "check": callable (in-process only),
"description"}. A guard failure is a VERIFICATION_FAILED envelope:
the phase does NOT move.
The legality map stays the scaffold's authority; guards ADD evidence
obligations on the completion-facing edges (building→verifying,
verifying→closed) — a tiny bounded change can pass guards=[] freely.
"""
doc = self.scaffold.load()
c = self.scaffold.contract(doc, contract_id)
if c is None:
return _envelope(
"transition",
success=False,
ids={"contract_id": contract_id},
errors=[{"error": Errors.UNKNOWN_CONTRACT, "detail": f"no contract {contract_id}"}],
)
if phase in ("verifying", "closed"):
from rune import stage_guards as sg
specs = guard_checks
if specs is None:
specs = _guard_specs_from_obligations(c)
if specs:
stage = sg.StageContract(
stage_id=f"{contract_id}:{phase}",
contract_id=contract_id,
ready_when=[_to_stage_check(s) for s in specs],
done_when=[_to_stage_check(s) for s in specs],
)
canary = sg.readiness_canary(stage)
if canary["verdict"] == "BLOCKED":
return _envelope(
"transition",
success=False,
ids={"contract_id": contract_id},
state={"phase": c.get("phase"), "guard": canary},
errors=[{"error": Errors.VERIFICATION_FAILED,
"detail": "stage guards blocked the transition",
"failures": canary["failures"]}],
)
try:
self.scaffold.transition_contract(
doc, contract_id, phase, evidence_ref=evidence_ref or f"builder:{self.builder}"
@@ -679,21 +770,62 @@ class ConstructionInterface:
material_closure = self._auto_material_change(
scope_files or [],
note=f"contract {contract_id} closed (material change)")
continuation = self._programme_continuation(contract_id)
state = {
"phase": "closed",
"material_change_closure": material_closure,
"contract_quality": {
k: q[k]
for k in ("correctly_predicted", "expected_new_observed", "genuinely_unexpected_new", "scope_check")
if k in q
},
}
if continuation is not None:
state["programme_continuation"] = continuation
return _envelope(
"close",
success=True,
ids={"contract_id": contract_id},
state={
"phase": "closed",
"material_change_closure": material_closure,
"contract_quality": {
k: q[k]
for k in ("correctly_predicted", "expected_new_observed", "genuinely_unexpected_new", "scope_check")
if k in q
},
},
state=state,
)
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
marked COMPLETE and the continuation canary's verdict rides the
close envelope — 'task done' ≠ 'programme done', and the next
eligible stage is SURFACED, never guessed. No programme bound →
None (no continuation block in the envelope). Failures degrade:
a broken programme ledger never fails a real closure."""
try:
from pathlib import Path as _Path
from rune import programme_continuation as pc
base = _Path(self.scaffold.current).parent / pc.DEFAULT_PROGRAMME_AREA
if not base.is_dir():
return None
for prog_file in sorted(base.glob("*.json")):
prog = pc.Programme.load(base, prog_file.stem)
if prog is None:
continue
stage = next((s for s in prog.stages
if s.stage_id == contract_id
and s.state != pc.COMPLETE), None)
if stage is None:
continue
stage.state = pc.COMPLETE
stage.result["closed_at_contract"] = contract_id
prog.save(base)
return {
"programme_id": prog.programme_id,
"stage_marked_complete": contract_id,
"canary": prog.continuation_canary(),
}
except Exception as exc: # noqa: BLE001 — ledger failure never fails a closure
return {"error": f"programme continuation degraded: {exc}"[:200]}
return None
def _auto_material_change(self, scope_files: list[str], *,
note: str) -> dict:
"""T21.4 §21.4H (rule 19Q.2): governed construction closes → the
@@ -1019,7 +1151,9 @@ def main(argv=None) -> int:
if cmd == "verify":
return _out(iface.verify(args[0], changed_files=(json.loads(args[1]) if len(args) > 1 else None)))
if cmd == "transition":
return _out(iface.transition(args[0], args[1], evidence_ref=args[2] if len(args) > 2 else ""))
guard_checks = json.loads(args[3]) if len(args) > 3 else None
return _out(iface.transition(args[0], args[1], evidence_ref=args[2] if len(args) > 2 else "",
guard_checks=guard_checks))
if cmd == "close":
return _out(
iface.close(
+72 -75
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.99.0
Version: v0.99.6
Features: 41 (24 original + 17 new native commands)
Usage:
@@ -110,6 +110,56 @@ try:
except ImportError:
BACKEND_AVAILABLE = False
def collect_post_response_signals(response: str, messages_count: int) -> list[str]:
"""Gather the operational signals that used to each print a dim
system message after every task/build response (the dim-spam source,
T5.13): budget pressure, degraded claims, growth delta, the periodic
self-improvement cycle.
PURE and headless — no channel, no app. The caller renders ONE
collapsed line (or none when nothing fired). Queued-message execution
and on-complete cron firing stay in the worker: they move real
messages, not chatter, and keep their own echoes.
Returns short human-readable fragments; empty list = silence.
"""
signals: list[str] = []
if not BACKEND_AVAILABLE:
return signals
try:
budget_data = provider_budget.get_status()
ratios = []
for provider in budget_data.get("providers", {}).values():
if provider.get("monthly_limit", 0) > 0:
ratios.append(provider.get("monthly_used", 0) / provider["monthly_limit"])
if provider.get("token_limit", 0) > 0:
ratios.append(provider.get("tokens_used", 0) / provider["token_limit"])
usage_percent = max(ratios, default=0) * 100
if usage_percent > 80:
signals.append(f"⚠ Budget: {usage_percent:.0f}% used")
except Exception:
pass
try:
claims_result = claim_audit.scan_text(response[:500])
if claims_result and claims_result.get("degraded"):
signals.append(f"⚠ Claims degraded: {len(claims_result['degraded'])}")
except Exception:
pass
try:
growth = growth_delta.check()
if growth and growth.get("delta"):
signals.append(f"📈 Growth: {growth['delta']}")
except Exception:
pass
try:
if messages_count % 10 == 0:
self_improve.run()
signals.append("🔄 Self-improvement cycle triggered")
except Exception:
pass
return signals
from textual.app import App, ComposeResult
from textual.containers import Horizontal, Vertical, VerticalScroll
from textual.widgets import Button, Header, Input, ProgressBar, RichLog, Static
@@ -1533,18 +1583,21 @@ class RuneApp(App):
def compose(self) -> ComposeResult:
yield Header()
with Horizontal(id="channels"):
yield ChannelView(
"task",
"[b]TASK[/b] — focused work",
classes="channel-view",
id="task-channel",
)
# T5.14 (Nick 2026-10-05): chat LEFT, task RIGHT — feels more
# natural (conversation first, focused work second). WorkPane
# stays third.
yield ChannelView(
"chat",
"[b]CHAT[/b] — free conversation",
classes="channel-view",
id="chat-channel",
)
yield ChannelView(
"task",
"[b]TASK[/b] — focused work",
classes="channel-view",
id="task-channel",
)
# P1-07 (#30): third collapsible column — the operational index.
yield WorkPane(id="work-pane")
with Horizontal(id="footer-bar"):
@@ -1895,7 +1948,7 @@ class RuneApp(App):
# Show version info
self.chat_channel.add_message(
"system",
f"[dim]Rune v0.99.0 | Textual TUI | {len(loaded)} native modules[/dim]",
f"[dim]Rune v0.99.6 | Textual TUI | {len(loaded)} native modules[/dim]",
)
# Auto-show night mode suggestion if evening/night hours
@@ -3737,69 +3790,13 @@ class RuneApp(App):
except Exception:
pass
# Auto-warn budget when >80% used
try:
if BACKEND_AVAILABLE:
budget_data = provider_budget.get_status()
ratios = []
for provider in budget_data.get("providers", {}).values():
if provider.get("monthly_limit", 0) > 0:
ratios.append(provider.get("monthly_used", 0) / provider["monthly_limit"])
if provider.get("token_limit", 0) > 0:
ratios.append(provider.get("tokens_used", 0) / provider["token_limit"])
usage_percent = max(ratios, default=0) * 100
if usage_percent > 80:
ch.add_message(
"system",
f"[dim]⚠ Budget: {usage_percent:.0f}% used[/dim]",
)
except Exception:
pass
# Auto-check claims after response
try:
if BACKEND_AVAILABLE:
claims_result = claim_audit.scan_text(response[:500])
if claims_result and claims_result.get("degraded"):
ch.add_message(
"system",
f"[dim]⚠ Claims degraded: {len(claims_result['degraded'])}[/dim]",
)
except Exception:
pass
# NOTE (Runtime Reachability audit 2026-09-10): the
# old hook_engine.fire("post_message", ...) call here
# was dead — hook_engine defines no fire(); the
# AttributeError was silently swallowed, so "hooks"
# never ran. Removed rather than left as a false-
# liveness surface. Real hook dispatch is unwired
# (microkernel.fire has zero callers) — see the
# reachability audit, P3.
# Auto-check growth delta
try:
if BACKEND_AVAILABLE:
growth = growth_delta.check()
if growth and growth.get("delta"):
ch.add_message(
"system",
f"[dim]📈 Growth: {growth['delta']}[/dim]",
)
except Exception:
pass
# Auto-improve every 10 messages
try:
if self.status_bar.messages_count % 10 == 0:
if BACKEND_AVAILABLE:
self_improve.run()
ch.add_message(
"system",
f"[dim]🔄 Self-improvement cycle triggered[/dim]",
)
except Exception:
pass
# T5.13 dim-spam consolidation: previously five
# separate dim system messages printed after every
# response. ONE collapsed line now — empty means
# silence, not a dash.
signals = collect_post_response_signals(response[:500], self.status_bar.messages_count)
if signals:
ch.add_message("system", "[dim]" + " · ".join(signals) + "[/dim]")
elif result.get("error") == "RESEARCH_REQUIRED":
# Nonterminal routing disposition, not failure (2026-09-10):
# the task hit the deep-research floor and was dispatched.
@@ -3980,13 +3977,13 @@ class RuneApp(App):
pass
def action_resize_left(self) -> None:
"""Shrink task channel, grow chat channel."""
self.task_width = max(20, self.task_width - 5)
"""Shrink the LEFT channel (chat, post-swap), grow the RIGHT (task)."""
self.task_width = min(80, self.task_width + 5)
self._apply_resize()
def action_resize_right(self) -> None:
"""Grow task channel, shrink chat channel."""
self.task_width = min(80, self.task_width + 5)
"""Grow the LEFT channel (chat), shrink the RIGHT (task)."""
self.task_width = max(20, self.task_width - 5)
self._apply_resize()
def _apply_resize(self):
+1 -1
View File
@@ -178,7 +178,7 @@ def _cc_probe():
from rune import cc_loader as _cl
try:
return _cl if _cl.available() else None
except Exception:
except Exception: # noqa: BLE001, S110 — loader probe never crashes the fallback
return None
+22 -4
View File
@@ -19,7 +19,9 @@ from __future__ import annotations
import json
from pathlib import Path
from rune.structural_store import add_entity, now
from rune.structural_store import (
add_entity, add_relationship, entity, new_doc, relationships, _sorted, now,
)
ROOT = Path(__file__).resolve().parents[2]
SRC_DIR = ROOT / "src" / "rune"
@@ -56,6 +58,14 @@ def _place_languages(doc: dict) -> int:
for e in doc.get("entities", []):
eid = e.get("id", "")
kind = e.get("kind", "")
# AUTHORITY ORDER (Nick's dichotomy, 2026-10-06 correction): the
# MEASURED classification map (import-evidence per module) decides
# FIRST. The name-hint lists are only a fallback for entities with
# no measurement — never allowed to override the evidence. A module
# with neither measurement nor hint is "unclassified", not a silent
# rust default.
measured = str(CLASSIFICATION_SIDES.get(
eid.split(":", 1)[1] if ":" in eid else eid, "") or "").upper()
if eid.startswith("contract:"):
lang = "n/a"
elif kind == "rust-crate":
@@ -66,14 +76,22 @@ def _place_languages(doc: dict) -> int:
lang = "n/a"
elif kind == "deployment_surface":
lang = "go"
elif measured == "RUST":
lang = "rust"
elif measured == "GO":
lang = "go"
elif measured == "TUI-LAST":
lang = "python-tui (Wave 6)"
elif measured == "TEST-INFRA" or measured == "RETIRES":
lang = "test-infra" if measured == "TEST-INFRA" else "retires"
elif "tui" in eid:
lang = "python-tui (Wave 6)"
elif any(h in eid for h in GO_HINTS):
lang = "go"
lang = "go (name hint)"
elif any(h in eid for h in RUST_HINTS):
lang = "rust"
lang = "rust (name hint)"
elif kind == "module":
lang = "rust"
lang = "unclassified"
elif kind == "subsystem":
# A subsystem is a CONTAINER — its language is its members'
# majority side per the measured dichotomy. The evidence map is
+4 -4
View File
@@ -31,7 +31,7 @@ def _cc_probe():
from rune import cc_loader as _cl
try:
return _cl if _cl.available() else None
except Exception:
except Exception: # noqa: BLE001, S110 — loader probe never crashes the fallback
return None
@@ -95,7 +95,7 @@ class TierManager:
raw = _cl.tier_get(entry, _py_core_now(),
float(HOT_THRESHOLD), float(WARM_THRESHOLD))
return str(raw)
except Exception: # noqa: BLE001 — kernel failure → oracle, never a broken call
except Exception: # noqa: BLE001, S110 — kernel failure → oracle, never a broken call
pass
return self._py_get_tier(entry)
@@ -146,7 +146,7 @@ class TierManager:
raw = _cl.tier_promote(entry, _py_core_now(),
float(HOT_THRESHOLD), float(WARM_THRESHOLD))
return str(raw)
except Exception: # noqa: BLE001
except Exception: # noqa: BLE001, S110 — kernel failure → oracle fallback, intentional silence
pass
return self._py_promote(entry)
@@ -172,7 +172,7 @@ class TierManager:
raw = _cl.tier_demote(entry, _py_core_now(),
float(HOT_THRESHOLD), float(WARM_THRESHOLD))
return str(raw)
except Exception: # noqa: BLE001
except Exception: # noqa: BLE001, S110 — kernel failure → oracle fallback, intentional silence
pass
return self._py_demote(entry)
+3 -3
View File
@@ -148,7 +148,7 @@ def _cc_probe():
from rune import cc_loader as _cl
try:
return _cl if _cl.available() else None
except Exception:
except Exception: # noqa: BLE001, S110 — loader probe never crashes the fallback
return None
@@ -170,7 +170,7 @@ def extract_topic(text):
raw = _cl.witness_extract_topic(text, _stopwords())
return list(raw) if isinstance(raw, list) else list(raw or [])
except Exception: # noqa: BLE001 — kernel failure → oracle
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]
@@ -199,7 +199,7 @@ def detect_signals(message, tool_error=None, task_status=None, recent_topics=Non
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 — kernel failure → oracle
except Exception: # noqa: BLE001, S110 — kernel failure → oracle, intentional silence
pass
return _py_detect_signals(message, tool_error, task_status, recent_topics)
File diff suppressed because it is too large. Load diff
+6946 -6922
View File
File diff suppressed because it is too large. Load diff
+483 -357
View File
File diff suppressed because it is too large. Load diff
+170
View File
@@ -6,6 +6,7 @@ import json
import os
import subprocess
import sys
from pathlib import Path
import pytest
@@ -585,3 +586,172 @@ def test_15u_discover_active_contract_migrated_to_interface(tmp_path):
# capabilities() advertises the new op
assert "discover_active_contract" in \
iface.capabilities()["state"]["operations"]
# ── T5.15 completion wave: stage_guards wired into transition ───────────────
def _guard_contract(iface, tmp_path):
"""A contract whose obligations declare a PATH guard and a failing
command guard — proving both BLOCK shapes."""
doc = iface.scaffold.load()
ss.add_entity(
doc, id="contract:guarded-1", kind="contract",
identities={
"objective": "guarded stage",
"verification_obligations": [
"guard:" + json.dumps({
"name": "artifact present", "kind": "path_exists",
"path": str(tmp_path / "artifact.txt"),
}),
],
},
evidence_refs=["e"],
)
iface.scaffold.commit(doc, what="seed guarded contract")
doc = iface.scaffold.load()
# move through the legal path so guards face verifying from building
sc.Scaffold.transition_contract(iface.scaffold, doc, "guarded-1", "approved",
evidence_ref="t")
sc.Scaffold.transition_contract(iface.scaffold, doc, "guarded-1", "building",
evidence_ref="t")
iface.scaffold.commit(doc, what="advance to building")
return "guarded-1"
def test_15f_transition_to_verifying_blocked_when_guard_fails(tmp_path):
iface = _iface(tmp_path)
cid = _guard_contract(iface, tmp_path)
env = iface.transition(cid, "verifying")
assert not env["success"]
assert env["errors"][0]["error"] == ci.Errors.VERIFICATION_FAILED
assert env["state"]["guard"]["verdict"] == "BLOCKED"
# phase UNCHANGED — the blocked stage does not move
doc = iface.scaffold.load()
c = iface.scaffold.contract(doc, cid)
assert c["phase"] == "building"
def test_15f_transition_passes_when_guard_satisfied(tmp_path):
iface = _iface(tmp_path)
cid = _guard_contract(iface, tmp_path)
(tmp_path / "artifact.txt").write_text("real artifact")
env = iface.transition(cid, "verifying")
assert env["success"], env["errors"]
doc = iface.scaffold.load()
assert iface.scaffold.contract(doc, cid)["phase"] == "verifying"
def test_15f_inline_guard_checks_override_obligations(tmp_path):
iface = _iface(tmp_path)
cid = _guard_contract(iface, tmp_path) # has the path guard, NOT satisfied
env = iface.transition(
cid, "verifying",
guard_checks=[{"name": "inline truth", "kind": "command",
"argv": [sys.executable, "-c", "print('ok')"]}],
)
assert env["success"], env["errors"]
assert iface.scaffold.contract(iface.scaffold.load(), cid)["phase"] == "verifying"
def test_15f_transition_to_building_runs_no_guards(tmp_path):
"""Completion-facing edges ONLY (verifying/closed): building→verifying
is guarded, but draft→approved passes freely even with obligations."""
iface = _iface(tmp_path)
doc = iface.scaffold.load()
ss.add_entity(doc, id="contract:guarded-2", kind="contract",
identities={"objective": "2"}, evidence_refs=["e"])
iface.scaffold.commit(doc, what="seed guarded-2")
cid = "guarded-2"
doc = iface.scaffold.load()
c = iface.scaffold.contract(doc, cid)
c["identities"]["verification_obligations"] = [
"guard:" + json.dumps({"name": "never", "kind": "path_exists",
"path": "/nonexistent/for-sure"})
]
iface.scaffold.commit(doc, what="attach guard")
env = iface.transition(cid, "approved") # NOT a completion edge
assert env["success"]
# ── T5.15 slice 3: programme_continuation wired into close() ────────────────
def _seed_programme(iface, programme_id, stage_contract_ids, tmp_path):
"""Persist a real Programme bound to the interface's scaffold store."""
from rune import programme_continuation as pc
base = Path(iface.scaffold.current).parent / pc.DEFAULT_PROGRAMME_AREA
prog = pc.Programme(programme_id=programme_id, objective="wave test")
for i, cid in enumerate(stage_contract_ids):
prog.stages.append(pc.ProgrammeStage(
stage_id=cid, objective=f"stage {i}", depends_on=[]))
prog.save(base)
return prog, base
def test_close_marks_programme_stage_complete_and_surfaces_continuation(tmp_path):
"""Closing a contract that is a stage of a persisted Programme marks
that stage COMPLETE and the envelope carries the continuation canary —
the programme's next eligible stage is surfaced, not guessed."""
from rune import programme_continuation as pc
iface = _iface(tmp_path)
env1 = _submit(iface, objective="wave stage one")
cid1 = env1["ids"]["contract_id"]
env2 = _submit(iface, objective="wave stage two")
cid2 = env2["ids"]["contract_id"]
# drive both through to verifying so close is legal
for cid in (cid1, cid2):
iface.transition(cid, "approved")
iface.transition(cid, "building")
iface.transition(cid, "verifying")
prog, base = _seed_programme(iface, "wave-x", [cid1, cid2], tmp_path)
env = iface.close(cid1, observed_evidence=[{"entity": "capability:exporter", "detail": "done"}])
assert env["success"], env["errors"]
cont = env["state"].get("programme_continuation")
assert cont, "close envelope must carry the programme continuation"
assert cont["programme_id"] == "wave-x"
assert cont["stage_marked_complete"] == cid1
reloaded = pc.Programme.load(base, "wave-x")
stage1 = next(s for s in reloaded.stages if s.stage_id == cid1)
assert stage1.state == pc.COMPLETE
assert cont["canary"]["verdict"] == "PROGRAMME_OPEN"
assert cont["canary"]["next_action"] == ("start", cid2)
def test_close_without_programme_has_no_continuation_block(tmp_path):
iface = _iface(tmp_path)
env = _submit(iface)
cid = env["ids"]["contract_id"]
iface.transition(cid, "approved")
iface.transition(cid, "building")
iface.transition(cid, "verifying")
env = iface.close(cid, observed_evidence=[{"entity": "capability:exporter", "detail": "done"}])
assert env["success"]
assert "programme_continuation" not in env["state"]
def test_close_programme_completion_verdict_surfaces(tmp_path):
"""Closing the LAST stage yields PROGRAMME_COMPLETE — the canary says
stop, the runner never guesses."""
iface = _iface(tmp_path)
env = _submit(iface)
cid = env["ids"]["contract_id"]
iface.transition(cid, "approved")
iface.transition(cid, "building")
iface.transition(cid, "verifying")
prog, base = _seed_programme(iface, "solo", [cid], tmp_path)
env = iface.close(cid, observed_evidence=[{"entity": "capability:exporter", "detail": "done"}])
cont = env["state"]["programme_continuation"]
assert cont["canary"]["verdict"] == "PROGRAMME_COMPLETE"
def test_two_submits_same_second_get_distinct_contract_ids(tmp_path):
"""Second-granularity timestamps collide when two needs are filed in
the same second (real builder-pair scenario) — ids must be distinct."""
iface = _iface(tmp_path)
e1 = _submit(iface, objective="wave stage one")
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"]
+143
View File
@@ -0,0 +1,143 @@
"""spine_language — the #2465 doc-level transforms (T5.15 keep-green:
the new-module henxel demands these ship WITH tests; they were carved
from spine.py whose battery covered them, but the carved module is the
unit under test now)."""
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 spine_language as sl # noqa: E402
@pytest.fixture
def doc():
return sl.new_doc()
def test_classification_sides_reads_the_measured_map():
sides = sl._classification_sides()
assert sides, "classification.json must be readable in-repo"
assert sides.get("spine") == "RUST"
assert sides.get("spine_language") == "RUST"
def test_classification_sides_degrades_to_empty_when_unreadable(tmp_path, monkeypatch):
monkeypatch.setattr(sl, "ROOT", tmp_path)
assert sl._classification_sides() == {}
def test_language_verdicts_kind_defaults(doc):
sl.add_entity(doc, id="rust-crate:cognition_core", kind="rust-crate")
sl.add_entity(doc, id="go-surface:rune-entry", kind="go-surface")
sl.add_entity(doc, id="workflow:ci.yml", kind="workflow")
sl.add_entity(doc, id="contract:BUILDER-x", kind="contract")
sl._place_languages(doc)
by = {e["id"]: e["identities"]["language"] for e in doc["entities"]}
assert by["rust-crate:cognition_core"] == "rust"
assert by["go-surface:rune-entry"] == "go"
assert by["workflow:ci.yml"] == "n/a"
assert by["contract:BUILDER-x"] == "n/a"
def test_language_verdicts_measured_authoritative_then_hints(doc):
"""Nick's dichotomy correction: the measured map decides; hints only
label UNMEASURED entities and say so; nothing silently defaults."""
sl.add_entity(doc, id="module:rune_tui", kind="module") # TUI-LAST measured
sl.add_entity(doc, id="module:spine", kind="module") # RUST measured
sl.add_entity(doc, id="module:totally_unknown_name", kind="module")
sl._place_languages(doc)
by = {e["id"]: e["identities"]["language"] for e in doc["entities"]}
assert by["module:rune_tui"] == "python-tui (Wave 6)" # from the MAP
assert by["module:spine"] == "rust" # from the MAP
assert by["module:totally_unknown_name"] == "unclassified"
def test_subsystem_language_from_member_majority(doc):
"""identity: 7 RUST / 3 GO members per classification.json → mixed with
counts carried in the verdict text (never a silent guess)."""
for m in ("behavior", "identity_anchors", "persona_definition",
"persona_gate", "prompt_sync", "voice_hybrid", "voice_injector",
"voice_sync", "voicebox", "safety_override"):
sl.add_entity(doc, id=f"module:{m}", kind="module")
sl.add_relationship(doc, type="belongs_to",
from_id=f"module:{m}", to_id="subsystem:identity",
evidence_refs=["t"])
sl.add_entity(doc, id="subsystem:identity", kind="subsystem")
sl._place_languages(doc)
e = sl.entity(doc, "subsystem:identity")
lang = e["identities"]["language"]
assert lang.startswith("mixed (rust ")
assert "go" in lang
def test_unqualified_discovery_becomes_construction_surface(doc):
"""A bare file path from a closed contract is NOT a module — provenance
preserved, kind honest, candidate stays DECLARED."""
monkey_specs = [("example", [("src/rune/audit_ledger.py",
"construction-surface", "candidate")])]
original = sl._contract_discoveries
sl._contract_discoveries = lambda: monkey_specs
try:
placed = sl._apply_contract_discoveries(doc)
finally:
sl._contract_discoveries = original
assert placed == 1
e = doc["entities"][0]
assert e["id"] == "construction-surface:src/rune/audit_ledger.py"
assert e["kind"] == "construction-surface"
assert e["maturity"] == "DECLARED"
assert e["evidence_refs"] == ["contract-discovery:example"]
def test_legacy_unqualified_module_normalized_with_edges(doc):
sl.add_entity(doc, id="src/rune/claim_audit.py", kind="module",
maturity="PRESENT",
evidence_refs=["contract-discovery:old"])
sl.add_relationship(doc, type="depends_on",
from_id="src/rune/claim_audit.py",
to_id="module:other", evidence_refs=["t"])
sl._normalize_discovery_ids(doc)
assert doc["entities"][0]["id"] == \
"construction-surface:src/rune/claim_audit.py"
assert doc["entities"][0]["kind"] == "construction-surface"
assert doc["relationships"][0]["from"] == \
"construction-surface:src/rune/claim_audit.py"
# Not in the observed set (no closed contract recorded it) → the
# unsupported PRESENT claim is demoted to DECLARED; evidence survives.
assert doc["entities"][0]["maturity"] == "DECLARED"
assert doc["entities"][0]["evidence_refs"] == ["contract-discovery:old"]
def test_discoveries_gather_reads_real_scaffold_store(tmp_path, monkeypatch):
"""The gatherer only trusts CLOSED contracts' ext.contract_quality."""
store = tmp_path / "scaffold"
store.mkdir()
doc = sl.new_doc()
sl.add_entity(doc, id="contract:C1", kind="contract",
ext={"contract_quality": {
"expected_new_observed": ["rust-crate:kernel_x"],
"candidate_corrections": [
{"entity": "suite"}]}})
sl.add_entity(doc, id="contract:C2", kind="contract",
ext={"contract_quality": {
"expected_new_observed": ["rust-crate:never"]}})
for e in doc["entities"]:
if e["id"] == "contract:C1":
e["phase"] = "closed"
if e["id"] == "contract:C2":
e["phase"] = "building"
monkeypatch.setattr(sl, "ROOT", tmp_path)
(tmp_path / "store" / "scaffold").mkdir(parents=True, exist_ok=True)
(tmp_path / "store" / "scaffold" / "current.json").write_text(
json.dumps(doc))
got = sl._contract_discoveries()
assert got == [("C1", [("rust-crate:kernel_x", "rust-crate", "observed"),
("suite", "construction-surface", "candidate")])]
+73
View File
@@ -174,3 +174,76 @@ def test_16k_projection_schema_validated(tmp_path):
assert proj.validate(bad) != []
bad2 = dict(p, schema_version=999)
assert proj.validate(bad2) != []
# ── T5.15 slice 2: the dispatch wire (command → interface execution) ────────
def test_dispatch_executes_accepted_transition_end_to_end(tmp_path):
"""A START command on a draft contract executes through the interface:
the phase actually moves — not just an accepted intent."""
store = str(tmp_path / ".rune" / "scaffold")
iface = cmds if False else None # noqa: F841 — readability anchor
from rune import construction_interface as ci
ciface = ci.ConstructionInterface(builder="seed", project_store=store, project_id="proj")
ciface.scaffold.init(purpose="t")
doc = ciface.scaffold.load()
sc.add_entity(doc, id="contract:disp-1", kind="contract",
identities={"objective": "dispatch"}, evidence_refs=["e"])
doc["entities"][-1]["phase"] = "draft"
ciface.scaffold.commit(doc, what="seed")
out = cmds.dispatch("START", construction_id="disp-1",
store_path=store + "|proj")
# external-project store path: dispatch builds its own interface bound
# to the same store
assert out["executed"] is True, out
assert out["success"] is True, out
doc2 = ciface.scaffold.load()
c = sc.entity(doc2, "contract:disp-1")
assert c["phase"] == "approved"
def test_dispatch_refusal_is_a_valid_protocol_outcome(tmp_path):
"""START on a non-draft contract: the lifecycle refuses — the dispatch
itself succeeds (success=True) with accepted=False."""
store = str(tmp_path / ".rune" / "scaffold")
from rune import construction_interface as ci
ciface = ci.ConstructionInterface(builder="seed", project_store=store, project_id="proj")
ciface.scaffold.init(purpose="t")
doc = ciface.scaffold.load()
sc.add_entity(doc, id="contract:disp-2", kind="contract",
identities={"objective": "x"}, evidence_refs=["e"])
doc["entities"][-1]["phase"] = "draft"
ciface.scaffold.commit(doc, what="seed")
out = cmds.dispatch("START", construction_id="disp-2",
payload={"phase": "building"},
store_path=store + "|proj")
# contract starts in draft so START is ACCEPTED here; drive to building
# first to prove the refusal shape:
assert out["executed"] and out["success"]
out2 = cmds.dispatch("START", construction_id="disp-2",
store_path=store + "|proj")
assert out2["accepted"] is False and out2["executed"] is False
assert out2["success"] is True
assert "draft" in out2["reason"]
def test_dispatch_idempotent_replay_does_not_double_execute(tmp_path):
"""Same command_id twice: the second is a replay — the phase does not
move twice and idempotent_replay=True comes back."""
store = str(tmp_path / ".rune" / "scaffold")
from rune import construction_interface as ci
ciface = ci.ConstructionInterface(builder="seed", project_store=store, project_id="proj")
ciface.scaffold.init(purpose="t")
doc = ciface.scaffold.load()
sc.add_entity(doc, id="contract:disp-3", kind="contract",
identities={"objective": "idem"}, evidence_refs=["e"])
doc["entities"][-1]["phase"] = "draft"
ciface.scaffold.commit(doc, what="seed")
first = cmds.dispatch("START", construction_id="disp-3",
command_id="fixed-cmd-1", store_path=store + "|proj")
assert first["executed"] and first["success"]
second = cmds.dispatch("START", construction_id="disp-3",
command_id="fixed-cmd-1", store_path=store + "|proj")
assert second["idempotent_replay"] is True
assert second.get("executed") in (False, None) # replay ≠ execution
@@ -0,0 +1,115 @@
"""T5.13/T5.14 frozen contracts: pane order and post-response silence.
The pane swap is structural (compose order); the dim-spam consolidation
is behavioral (one collapsed line instead of five dim messages).
"""
from __future__ import annotations
import re
import sys
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import Mock
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "src"))
from rune import rune_tui # noqa: E402 — the TUI lives in the package
def test_chat_channel_is_yielded_first_in_compose():
src = (ROOT / "src" / "rune" / "rune_tui.py").read_text()
body = re.search(
r'with Horizontal\(id="channels"\):(.*?)with Horizontal\(id="footer-bar"\)',
src,
re.DOTALL,
).group(1)
order = re.findall(r'ChannelView\(\s*"(\w+)"', body)
assert order == ["chat", "task"], f"compose yields {order}"
def test_resize_actions_follow_the_new_visual_positions():
"""After the swap LEFT=chat, RIGHT=task. resize_left shrinks the LEFT
(chat) — which GROWS the task share; resize_right is the reverse."""
assert "grow the RIGHT (task)" in rune_tui.RuneApp.action_resize_left.__doc__
assert "shrink the RIGHT (task)" in rune_tui.RuneApp.action_resize_right.__doc__
# Behavioral proof against an instance WITHOUT constructing the App
# (reactive initialization needs a running textual app; the docstring
# carries the contract, the arithmetic is what matters).
app = SimpleNamespace(
task_width=50,
action_resize_left=None,
action_resize_right=None,
_apply_resize=None,
)
app._apply_resize = lambda: None
# Bind the real methods onto the stand-in and call them.
rune_tui.RuneApp.action_resize_left(app)
assert app.task_width == 55
rune_tui.RuneApp.action_resize_right(app)
assert app.task_width == 50
def _isolate_collector(monkeypatch, **modules):
monkeypatch.setattr(rune_tui, "BACKEND_AVAILABLE", True)
for name, value in modules.items():
monkeypatch.setattr(rune_tui, name, value)
def test_collector_returns_all_signals_in_one_pass(monkeypatch):
_isolate_collector(
monkeypatch,
provider_budget=SimpleNamespace(
get_status=lambda: {
"providers": {"p": {"monthly_limit": 100, "monthly_used": 90, "token_limit": 0, "tokens_used": 0}}
}
),
claim_audit=SimpleNamespace(scan_text=lambda _t: {"degraded": ["a", "b"]}),
growth_delta=SimpleNamespace(check=lambda: {"delta": "focus: +1"}),
self_improve=SimpleNamespace(run=lambda: None),
)
signals = rune_tui.collect_post_response_signals("response text", messages_count=10)
assert signals == ["⚠ Budget: 90% used", "⚠ Claims degraded: 2", "📈 Growth: focus: +1", "🔄 Self-improvement cycle triggered"]
def test_collector_silence_when_nothing_fires(monkeypatch):
_isolate_collector(
monkeypatch,
provider_budget=Mock(get_status=lambda: {"providers": {}}),
claim_audit=Mock(scan_text=lambda _t: None),
growth_delta=Mock(check=lambda: None),
self_improve=Mock(run=lambda: None),
)
collector = rune_tui.collect_post_response_signals
app_signals = collector("plain", messages_count=7) # not a cycle multiple
assert app_signals == []
def test_collector_never_raises_on_backend_noise(monkeypatch):
def boom(*_a, **_k):
raise RuntimeError("backend gone")
_isolate_collector(
monkeypatch,
provider_budget=Mock(get_status=boom),
claim_audit=Mock(scan_text=boom),
growth_delta=Mock(check=boom),
self_improve=Mock(run=boom),
)
assert rune_tui.collect_post_response_signals("x", 10) == []
def test_self_improve_only_at_cycle_multiple(monkeypatch):
ran = []
_isolate_collector(
monkeypatch,
provider_budget=Mock(get_status=lambda: {"providers": {}}),
claim_audit=Mock(scan_text=lambda _t: None),
growth_delta=Mock(check=lambda: None),
self_improve=Mock(run=lambda: ran.append(1)),
)
rune_tui.collect_post_response_signals("x", 7)
assert ran == []
rune_tui.collect_post_response_signals("x", 10)
assert ran == [1]