Owner's top-priority wave: runtime awareness of the user's filesystem
and dominion on tool call usage. Boot-time filesystem probe served on
/v1/status as runtime_env (awareness, never gating); tool-executor
deployed to ~/.local/share/rune/bin via ensure_core.py and
build_paired.sh with one resolution door; deterministic literal-phrase
refusal classifier verifies model capability claims against the actual
suite, corrects false claims with a retry turn naming the real tools,
and journals capability.refusal_correction; model_authority facts on
/v1/status; the dominion check asserts no gate exists between
chat-pane tool calls and filesystem ops.
Contract: ADMIT-wave-d57-runtime-dominion. go test -race -short green;
vet/gofmt clean.
classify + activate + admit of the adversarial re-verification of the
10 remaining general-34 facades at the cache-catch standard; evidence
refs name the serving-path catch (goal fail-loud save) and the three
mutation-gap pins.
The changelog = a generated artifact, not a kept doc: CHANGELOG.md
composes from the MAIN v* tag namespace (one section per version: What is
new / What you could try / What verifies it) byte-deterministically; CI
lint runs "changelog --check" (byte-compare vs regeneration — a release
that skips its sync turns the gate red); auto-version syncs it after
every release with a chore(changelog) commit (the convergence classifier
ranks it "other" = earns no version, no recursion). Tests red-first
(deterministic composition, dedupe); the go battery + the generated
file are current as of v0.346.0.
contract:ADMIT-release-reporting — the version-push reader page now
carries a plain-English body: What is new grouped fixed/new/improved,
What you could try derived from touched surfaces, How it was verified.
latest_release_tag namespaces (main v* vs tui-v*) so the TUI build tag
never blanks a release note; the workflow feeds one namespace-resolved
PREV_TAG to every range consumer; the Go narrative carries the try list
and the workflow rewires its callers. Tests red-first (python namespace
law, go try-section law) + retroactive v0.346.0 body rebuild follows.
- Ollama wire: stream=true /api/chat, NDJSON chunks parsed incrementally
(content/thinking/tool_calls), the final reply carries the assembled
answer — the engine's bounded tool loop runs over streamed replies
(StreamChatClient, loop semantics unchanged)
- Cloud wires: streaming parity — [OI]-compat SSE, anthropic SSE events,
gemini streamGenerateContent (alt=sse); a provider that answers plain
JSON despite stream=true falls back honestly to the assembled decode
- Core /v1/chat/completions: stream=true relays the model's bytes as
NDJSON content chunks (flushed per chunk); a non-streaming leg still
answers the full content in one chunk (the classic framing); errors
before the first byte answer the honest envelope
- Entry proxy: the dispatch client carries NO whole-call timeout (the
core governs streamed deadlines); chunk-relay pass-through proven
incremental; the generic proxy client keeps the whole-turn fallback
- TUI: CompleteStream parses the chunk framing, the lane dispatcher
forwards deltas to the sink, the chatpane renders the answer AS IT
ARRIVES (one progressive bubble the durable final append adopts —
never a duplicate; a failed dispatch retires it)
- DEADLINE REPLACEMENT: the flat whole-turn budgets retire on streamed
paths — the INTER-CHUNK IDLE TIMEOUT (120s default,
RUNE_STREAM_IDLE_TIMEOUT configurable) is the honest dead-model
guardian with a 30-minute backstop; ErrStreamIdle is typed; the
budget constants remain for non-streaming fallback paths
- red-first: ollama/cloud SSE hermetic doubles, slow-model completes /
dead-model trips-idle simulations, live-chunk relay proofs (core,
entry), progressive-render bubble laws, stream dispatch laws
admission: ADMIT-wave-streaming-deadline-root
- the gate sits on the wave render path in activity.go (around
waveOffset/snakeWord): closed = frame-frozen strip, the rest idle
already takes (A5) extended to active states; wired down the face
chain lanes->rail->promote->tasks and driven from the shell's
syncActivity (reduced motion closes it)
- chatpane: the written never-animate law (no motion in the reading
pane; stream append is append-only, never reflows completed blocks)
asserted by TestAppendNeverReflowsCompletedBlocks on the full
renderLines stream, styled and plain
- one-animating-region-per-frame documented at the face chain's
Update loop (Init/heartbeat in cmd/rune-tui/main.go)
- admission door: scripts/admit_wave_d54_nd_presentation.py, contract
ADMIT-wave-d54-nd-presentation minted (Rule-5)
- go test -race -short ./... in tui/ green; D52's rainbow test
updated for the gate's default-open constructor
The work-stream records now link back to the chat demand that spawned
the work: DeriveWorkBlocks keys the origin on the oldest role=user
record the conversation wire carries for the task, using the msg_ids
the core already journals (D51: newMsgID exists core-side — no new
generation; the runecore write-side origin ref is deferred, engine.go
off-limits while the D49 agent works there). The reference renders as
'↩ <msg_id>' on the work block header, inside the clip budget — the
tasks pane's rune-counted width math carries no ANSI, so the dim tone
degrades to the plain marker here (honest degradation, documented).
No demand row on the wire renders nothing — never a fabricated ref.
Admission: ADMIT-wave-d50-residuals (scripts/admit_wave_d50_residuals.py,
EXISTING_PROJECT_CHANGE, building).
The owner's ruling — 'i want that ability to be in every pane' —
supersedes the mode-gated toolset: ToolsetFor now exposes the full
suite (read + write families) in CHAT, PLAN and TASK alike, and
ExecuteGatedTool's write-refusal-by-mode retires; the dispatch-gate
seam stays (future policies hook there) with the typed refusal kept
for genuinely-unavailable tools. The wire tool descriptions state the
write availability — the model asked for shell access because the wire
showed read-only; the wire is fixed, not the model. The consent card
remains the work-promotion mechanism (organization), never a
permission gate; the real safety layer is the scaffolding authority +
contract gates at mutation doors.
Native git family (gittools.go): git_status, git_diff (unstaged+
staged, names and patch forms), git_log, git_branch, git_add,
git_commit — stdlib git subprocess in DefaultRepoRoot() (RUNE_REPO_ROOT
override), porcelain parsed honestly (-z, tab records), typed JSON
results. git_add/git_commit refuse the landing-law banned paths
(check_landing.py rule 4 SWEEP_RACE_BANNED_PATHS + 20MB ceiling);
git_commit runs the repository's own pre-commit hook in a real
checkout.
Contract: ADMIT-wave-d49-capability-everywhere (mode/toolset tests
re-pinned to CHAT=full-suite, red-first evidence against the pre-wave
toolset; go test -race green, vet/gofmt clean).
Stdlib-only Go module deciding each wave's lane so the owner stops
specifying: hot-state registry ~/.rune/hot/dispatch_registry.json
(lock/unlock/show, 4h STALE + --prune), classify with the one
data-driven keyword->surface map, prefix-overlap queue-behind (owner
-> PRIORITY-QUEUE with landing-boundary yield), owner-origin solo
latency, disjoint-surface parallel split suggestion, --apply lock take
atomic and fail-loud on locked/stale/changed; scripts/dispatch_check.sh
wraps the pre-spawn discipline. go test -race green, vet + gofmt clean.
Contract ADMIT-wave-dispatch-classifier.
The build-integrity sweep (the one build law imported by BOTH gates —
unified_check's go_suite/src_go_suite and check_landing's rule 3) ran
'go build -o /dev/null ./...' with VCS stamping enabled. On the landed
sha's git-archive EXPORT — where the landing gate verifies build
health — there is no VCS identity, and a stray host .git (this host
carries an empty /tmp/.git sweep-race artifact) makes every
main-package module die with 'error obtaining VCS status: exit status
128'. Red measured on HEAD's export: 97/100 modules false-RED in 0.9s,
drowning the real build signal — the mass false-RED class behind the
36 backfilled 2026-10-18 manifests' build legs.
Fix 1 — BUILD_CMD gains -buildvcs=false: the sweep measures
compilability, not VCS metadata. Green measured on the same export:
100/100 modules build in 21.5s.
Fix 2 — the paired-Rust freshness law: cgo modules link gitignored
prebuilt staticlibs (src/rust/<crate>/target/release/lib*.a). The
existing retry rebuilt them only when MISSING (linker 'cannot find');
a STALE .a — older than the crate's sources — made the sweep
'successfully' verify the head against yesterday's build. The sweep
now scans each module's cgo LDFLAGS for linked crates and rebuilds
any missing-or-stale staticlib BEFORE the Go build, so a green sweep
is always a green on the current head. Verified: a source touched
newer than the .a flips staticlib_is_stale, cargo rebuild fires, the
.a refreshes, the module builds rc=0, staleness clears.
Real-tree sweep stays green (100/100, 19.9s); no crate rebuilds fire
when artifacts are fresh.
The guard reported '0 orphans' while two hot-looping leaked serves were
alive (the 2026-10-18/19 incident; both died only to direct PID + parent
kills). Three keep-shapes produced that verdict, all fixed:
- living-parent inversion: a parent that is ALIVE but WEDGED
(hot-looping) read as 'the owner's TUI owns this serve' — the guard's
only liveness signal was a non-systemd cmdline. parent_is_dead now
samples the parent's CPU time across a 0.3s window (>~30% of a core
= wedged, never going to reap or tear down) and treats it as dead.
- zombie-parent class: a stat state Z/X parent (zombie leader — the
TUI's main loop gone, threads may still spin) is now explicitly
dead, not just via the empty-cmdline side effect.
- age guard: the 'newest serve is never killed' protection now applies
ONLY while a live session exists (some candidate has a living owner
parent). With no live session every non-owned candidate is a leak,
newest included — the incident's two loopers were both hand-killed
for exactly this. A mid-spawn session is protected by the ownership
check (its TUI parent is alive), not the age guard.
Also the hot/test leak class: leaked /tmp/*/rune-tui test binaries
(exe-named rune-tui under /tmp) whose spawner died (zombie / systemd
--user subreaper) are now a second candidate class — a full TUI
rendering into a dead pty survived every previous run.
Red-first: scripts/test_cleanup_orphan_rune.sh spawns four fake ELF
serves (zombie-leader parent, wedged hot-looping parent, healthy
parent, newest zombie-parented) plus a leaked /tmp rune-tui, and runs
the cleaner dry-run through its scan seams (RUNE_TUI_LIB_GLOB /
RUNE_TUI_TEST_GLOB) so real host serves cannot perturb the verdicts.
Against the old guard it reproduces the incident verbatim ('0 orphans'
with both hot-loopers kept as 'living parent'); against the fix it is
green across three consecutive runs (phase 1: wedged/zombie orphans +
leaked test TUI named, owned + newest kept; phase 2: no live session →
every non-owned candidate an orphan).
The spawn guard was unversioned (no canonical copy, absent from
install_mimo_hook.py MANAGED — the drift-undetected rot that killed
builder-bridge pre-T22) and lexically porous. Canonical copy now lives
in config/mimo-hooks/ and is hash-drift-checked like its siblings;
installed copy synced.
Tightened classification (classifyBrief, exported for direct testing):
- a build brief needs the admission marker AND a target surface (repo
path or project name, judged outside the admission boilerplate) —
marker-only briefs are refused with typed reasons
BUILD_BRIEF_NO_ADMISSION / BUILD_BRIEF_NO_TARGET;
- build markers match on word boundaries (a 'report' mention is no
longer build-shaped via substring 'port');
- the research exemption requires an explicit leading 'research:'
prefix — self-declared 'research artifacts to ...' briefs are no
longer exempt.
rule 5 in check_landing.py --staged-only: a staged MUTATING path
(non-test, non-docs source) must be covered by an OPEN
construction contract or the pre-commit hook refuses, naming the
contract ids checked and the admit_wave_* admission door. The only
escape hatch is scripts/admit_wave_*.py itself.
check_mutation's remediation exit no longer grants remediation to
PATHLESS commands while a scope violation is unresolved — a command
that resolves no repo paths (git add -A && git commit) cannot prove
it targets only the offending paths; it is refused as
SCOPE_VIOLATION_UNRESOLVED with a typed note.
Admission: contract ADMIT-2026-10-22T04:38:23Z-7d445e-wave-
adversarial-escape-highs (phase building, scope covers scripts/,
config/mimo-hooks/, src/rune/construction_authority.py, tests/).
The checkout was missing the dedupe+supersede gate (retirement-map row 24,
parity/memory-ops.json): 'No module named dedupe_policy' on every default
add; only --no-dedupe worked. Module restored with the 2026-08-13 policy
(created/duplicate/supersede + receipts ledger), pinned red-first by
tests/test_dedupe_policy.py (6 tests incl. consumer-contract + one-record
law); live CLI add now returns receipted decisions.
Implement ToolChatClient (SupportsTools + ChatWithTools) on the
[OI]-compat, anthropic, and gemini clients so cloud models act through
the D45 mode-gated tool suite instead of degrading to toolless:
- [OI]-compat: tools array rides the request; tool_calls parse with
JSON-string arguments; assistant tool_calls + role 'tool' replay
- anthropic: tools param with input_schema; tool_use blocks parse to
ToolCalls; continuation as user tool_result blocks with tool_use_id
- gemini: functionDeclarations under tools; functionCall parts parse
to ToolCalls; continuation as user functionResponse parts keyed by
function name
Mode gating stays identical to the local path (ExecuteGatedTool re-reads
the mode per dispatch); the engine's bounded loop and cap-out are
reused untouched. Red-first httptest doubles per wire (request shape +
plain answer / tool call / continuation), hermetic; the live leg stays
RUNE_CLOUD_PARITY_LIVE=1-gated.
- history purge via git-filter-repo (path store/scaffold/legacy-current-migration-snapshot.json, --invert-paths); force-push to gitea under wave-ghost-purge standing approval; branch protection restored after
- rollback: backups/rune-pre-ghost-purge-2026-10-21.bundle (+ /tmp safety bundle of pre-purge state)
- .gitignore entry: the on-disk working copy stays, never committable
- check_landing.py SWEEP_RACE_BANNED_PATHS gains the path — pre-commit hook mechanically refuses staging (proven: force-add → RED refusal)
- merge/rebase/cherry-pick simulation from a wave base: no re-entry
Audit: 981 tui + 75 rune-core tests classified (mutant-proven /
red-first-proven / regression-pinned / invariant-pinned /
candidate-ceremony). Conversions: 2 panic-only tests → mutation-proven
bounded-width pins; 7 synthetic-spelling key sites → real input (the
'down'-types-d class eliminated); 1 vacuous assertion → real
cursor-walk proof. Zero deletions (nothing dead enough to kill).
The sweep EARNED its keep immediately: the converted trust test
caught a real width-overflow bug (56-rune strips in a 1x1 face) —
fixed (floor-clamped strips AND inner lines), re-proven
red/green. Full report: reports/test-vitality-audit-2026-10-19.md
closes#2510
Measured diagnosis: tui/cmd/rune-tui eats 600 of 694 Go-check seconds
— the boot smoke test hangs in an unbounded terminal read past its own
deadlines (pre-existing on HEAD, repro'd on clean checkout). Race
detector measured innocent (1.6x, not 2-10x); Go's built-in test cache
already IS the content-keyed cache; all three sharding options
rejected on the numbers.
Landed instead: boundary_corpus_go scoped from the full tui suite to
its sole corpus consumer (70-84s → 0.1s, verified end-to-end GREEN);
explicit race-command timeout; honest 'not sharded' manifest notes
with measured reasons. The boot-smoke watchdog fix is named for its
own bounded repair wave (fix sketched in the receipt).
closes#2508
At each task-turn boundary the lane takes the OLDEST pending entry and
dispatches it through the normal task-create path (send_task, channel
task); the queue file updates atomically, rows re-number, the pane
follows on its next poll. selectNextQueueEntry is the D36
'Rune intelligently decides' seam (phase 1 = FIFO oldest-first);
failed dispatches re-queue at the FRONT (order-preserving, never
silently dropped) with a typed journal event. Race-proofed (one
critical section, dispatch outside the lock, settle path for
fast tasks) — six consecutive -race suite runs green; live proof:
two queued demands drained FIFO through a real rune serve.
closes#2501
Every mutating tool call (subagent or main session) is scope-checked
against the active construction contract through the installed hook:
in-scope edits allowed, out-of-scope blocked naming the contract; the
durable lock fails closed across fresh processes (ACTIVE/UNREADABLE
both stop, ABSENT allows ordinary work); shell escapes (clobber/tee/
--output/touch) caught; supervisory acts refused; violations recorded
and self-clear on remediation. 67/67 drills + authority tests green;
hook installed hash-verified; activation front door ported (the one
design decision per the map) with the introduce-verb fix for rune's
Rust-core delegation.
closes#2499
CRITICAL: chat-originated tool dispatch (Python local_tool_runtime.py)
had ZERO mode awareness — a chat write_file in CHAT mode executed
ungated (the Go serve surface gated but the chat path never touched
it). Closed: mode authorization at request intake in local_tool_
runtime, authorize seam in tool_protocol dispatch, approve() resume
door gated, hatch-exposure filtered. Typed ModeRefusal feeds the model
as tool-error data; failure breaker trips on repeat.
T1: ONE mode matrix artifact (schemas/mode/v1/mode_matrix.json) read
by BOTH Go (embed, byte-identical, 16-case battery) and Python
(mode_gate.py, per-dispatch read 0.011ms, RUNE_MODE env wins).
T2: model only SEES mode-allowed tools at capability selection.
T3: per-dispatch mode re-check (mid-loop switch pinned).
T4: tool events persist as channel-scoped role=tool conversation
records — results land in the chat lane through the bridge poll.
Main-session verify: mode dispatch gate + tool authority suites green
own runs. 31/31 + 3438/0 new-mode-gating failures.