Compare commits

...
4 Commits
Author SHA1 Message Date
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))
Auto-version and Release / release (push) Successful in 2s
CI Pipeline / unit-tests (push) Successful in 5m3s
CI Pipeline / lint (push) Successful in 34s
CI Pipeline / go-surface (push) Successful in 2s
CI Pipeline / canary (push) Successful in 44s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 1s
CI Pipeline / type-check (push) Successful in 27s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / required-checks (push) Successful in 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 1s
deploy / deploy (push) Successful in 5m9s
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
Auto-version and Release / release (push) Failing after 2s
CI Pipeline / lint (push) Successful in 36s
CI Pipeline / go-surface (push) Successful in 3s
CI Pipeline / best-practices (push) Successful in 1s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / canary (push) Successful in 45s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / unit-tests (push) Successful in 5m0s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
deploy / deploy (push) Successful in 6m2s
2026-10-06 03:11:44 +01:00
conductor 6c997b6af4 Merge branch 'master' into feat/wire-construction-commands-dispatch
deploy / deploy (push) Skipped
CI Pipeline / lint (push) Successful in 34s
CI Pipeline / go-surface (push) Successful in 2s
CI Pipeline / canary (push) Successful in 44s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 1s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / type-check (push) Successful in 29s
CI Pipeline / lint (pull_request) Successful in 36s
CI Pipeline / go-surface (pull_request) Successful in 3s
CI Pipeline / canary (pull_request) Successful in 44s
CI Pipeline / best-practices (pull_request) Successful in 1s
CI Pipeline / documentation (pull_request) Successful in 2s
CI Pipeline / type-check (pull_request) Successful in 27s
CI Pipeline / gitea-offline (pull_request) Successful in 13s
Spec gate (headless) / spec-gate (pull_request) Successful in 4s
CI Pipeline / unit-tests (push) Successful in 4m57s
CI Pipeline / unit-tests (pull_request) Successful in 5m1s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / required-checks (pull_request) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
CI Pipeline / CI Pipeline / notify-event-bridge (pull_request) Successful in 2s
Auto-version and Release / release (pull_request) Successful in 4s
2026-10-06 03:02:52 +01:00
Nick 55f268f978 feat(scaffold): construction_commands dispatch — the serving wire (T5.15 slice 2)
deploy / deploy (push) Skipped
CI Pipeline / lint (push) Successful in 34s
CI Pipeline / go-surface (push) Successful in 3s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Successful in 28s
CI Pipeline / canary (push) Successful in 45s
CI Pipeline / gitea-offline (push) Successful in 13s
CI Pipeline / lint (pull_request) Successful in 34s
CI Pipeline / go-surface (pull_request) Successful in 2s
CI Pipeline / canary (pull_request) Successful in 45s
CI Pipeline / best-practices (pull_request) Successful in 2s
CI Pipeline / documentation (pull_request) Successful in 1s
CI Pipeline / type-check (pull_request) Successful in 26s
CI Pipeline / gitea-offline (pull_request) Successful in 12s
Spec gate (headless) / spec-gate (pull_request) Successful in 4s
CI Pipeline / unit-tests (push) Successful in 4m54s
CI Pipeline / unit-tests (pull_request) Successful in 4m54s
CI Pipeline / required-checks (push) Successful in 0s
CI Pipeline / required-checks (pull_request) Successful in 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Successful in 2s
CI Pipeline / CI Pipeline / notify-event-bridge (pull_request) Successful in 2s
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
7 changed files with 174 additions and 6 deletions

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
# Rune
**Current release: v0.99.3**
**Current release: v0.99.4**
**Rune is a persistent personal agent designed to accumulate experience, develop reusable competence, and remain recognisably the same agent even when its underlying foundation model changes.**
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "rune"
version = "0.99.3"
version = "0.99.4"
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.3"
__version__ = "0.99.4"
# One build identity (handoff 2026-09-10 §4): the semantic version above is
# the repo-level truth (auto-bumped at merge); the DEPLOYED revision (exact
+1 -1
View File
@@ -233,7 +233,7 @@ def main() -> int:
)
check(
"Rune minor version advertises provisioning and restore verification",
'__version__ = "0.99.3"' in rune_version,
'__version__ = "0.99.4"' 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}
+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.99.3
Version: v0.99.4
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.99.3 | Textual TUI | {len(loaded)} native modules[/dim]",
f"[dim]Rune v0.99.4 | Textual TUI | {len(loaded)} native modules[/dim]",
)
# Auto-show night mode suggestion if evening/night hours
+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