Compare commits

..
91 Commits
Author SHA1 Message Date
conductor 79ff61b361 chore(release): auto-bump version to 0.359.0 — earned by # (minor: converged release (push-to-master bump))
CI Pipeline / lint (push) Failing after 45s
CI Pipeline / go-surface (push) Failing after 24s
CI Pipeline / rust-surface (push) Failing after 13s
CI Pipeline / unit-tests (push) Failing after 1m22s
CI Pipeline / best-practices (push) Successful in 6s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / type-check (push) Failing after 39s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / canary (push) Failing after 1m8s
deploy / deploy (push) Failing after 15s
issue-convergence / converge (push) Successful in 1s
CI Pipeline / required-checks (push) Failing after 1s
Auto-version and Release / release (push) Failing after 38s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 5s
Promotion record: (unknown)
2026-10-25 21:46:31 +00:00
Nick 6b3ed6b721 feat(runecore): D57 runtime dominion — runtime-env layer, tool-executor deployment, model-refusal authority (Gitea #2590/#2591)
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 0s
CI Pipeline / lint (push) Canceling
Auto-version and Release / release (push) Canceled after 29s
issue-convergence / converge (push) Successful in 2s
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.
2026-10-25 21:45:37 +00:00
Nick 312592ea13 merge: removal-prep/dead-superseded-pile — minified oracle bodies / dead-pile deletion (owner merge-all ruling, Gitea #2598 item 2)
Auto-version and Release / release (push) Failing after 33s
CI Pipeline / lint (push) Failing after 50s
CI Pipeline / go-surface (push) Failing after 24s
CI Pipeline / rust-surface (push) Failing after 11s
CI Pipeline / unit-tests (push) Failing after 1m12s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Failing after 1m1s
CI Pipeline / type-check (push) Failing after 39s
deploy / deploy (push) Failing after 14s
issue-convergence / converge (push) Successful in 1s
CI Pipeline / gitea-offline (push) Successful in 21s
CI Pipeline / required-checks (push) Failing after 3s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 9s
2026-10-25 19:40:12 +00:00
Nick 4cdafeed37 merge: removal-prep/cache — minified oracle bodies / dead-pile deletion (owner merge-all ruling, Gitea #2598 item 2) 2026-10-25 19:40:12 +00:00
Nick 16721a3366 merge: removal-prep/auto-context — minified oracle bodies / dead-pile deletion (owner merge-all ruling, Gitea #2598 item 2) 2026-10-25 19:40:12 +00:00
Nick 66fb3a8c57 merge: removal-prep/openai-compat-transport — minified oracle bodies / dead-pile deletion (owner merge-all ruling, Gitea #2598 item 2) 2026-10-25 19:40:12 +00:00
Nick 79da78a9f9 merge: removal-prep/message-queue — minified oracle bodies / dead-pile deletion (owner merge-all ruling, Gitea #2598 item 2) 2026-10-25 19:40:12 +00:00
Nick 7e74a627fe merge: removal-prep/forge-client — minified oracle bodies / dead-pile deletion (owner merge-all ruling, Gitea #2598 item 2) 2026-10-25 19:40:12 +00:00
Nick dd40e4fbed merge: removal-prep/reconcile-tools — minified oracle bodies / dead-pile deletion (owner merge-all ruling, Gitea #2598 item 2) 2026-10-25 19:40:12 +00:00
Nick e6d87893b6 merge: removal-prep/paths — minified oracle bodies / dead-pile deletion (owner merge-all ruling, Gitea #2598 item 2) 2026-10-25 19:40:12 +00:00
Nick 936cb1e424 removal-prep/dead-superseded-pile: batch manifest
deploy / deploy (push) Skipped
CI Pipeline / lint (push) Failing after 47s
CI Pipeline / unit-tests (push) Failing after 35s
CI Pipeline / rust-surface (push) Failing after 9s
CI Pipeline / go-surface (push) Failing after 23s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / type-check (push) Failing after 39s
CI Pipeline / canary (push) Failing after 1m7s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / required-checks (push) Failing after 4s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 5s
2026-10-25 19:12:16 +00:00
Nick 4029245630 removal-prep/auto-context: batch manifest 2026-10-25 19:12:16 +00:00
Nick 62afb4a53e removal-prep/reconcile-tools: batch manifest 2026-10-25 19:12:16 +00:00
Nick af94384710 removal-prep/openai-compat-transport: batch manifest 2026-10-25 19:12:15 +00:00
Nick 1effa1f797 removal-prep/message-queue: batch manifest 2026-10-25 19:12:15 +00:00
Nick ebfdac016a removal-prep/forge-client: batch manifest 2026-10-25 19:12:15 +00:00
Nick d9e756f001 removal-prep/cache: batch manifest 2026-10-25 19:12:15 +00:00
Nick 7116409ca4 removal-prep/paths: batch manifest 2026-10-25 19:12:15 +00:00
Nick 17ac2ce203 removal-prep/dead-superseded-pile: delete dead alpha marker stub (Gitea #2598 item 2)
Import-graph over src/tui/scripts/tests: 0 importers anywhere. The 46
superseded modules in triage-2026-10-24 pile2 are HELD: truth-gen,
canary and parity scripts still import them ('0 live importers' = no
runtime consumer), so their removal must be a coordinated script+module
wave, not this tranche.
2026-10-25 18:52:35 +00:00
Nick 14bcd4f489 removal-prep/auto-context: minify Python oracle bodies to escape-hatch refusal (Gitea #2598 item 2)
deploy / deploy (push) Skipped
CI Pipeline / lint (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
RUNE_AUTO_CONTEXT_ENGINE=python switch shape kept; both oracle bodies
raise 'escape hatch retired'. Battery keeps Go-side pins and asserts the
refusal (8 passed); sibling auto-execution-context battery green.
2026-10-25 18:43:08 +00:00
Nick d2e5467d61 removal-prep/reconcile-tools: minify reconcile oracle body to escape-hatch refusal (Gitea #2598 item 2)
deploy / deploy (push) Skipped
CI Pipeline / lint (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
RUNE_MODEL_ROUTING_ENGINE=python switch shape kept; the oracle body raises
'escape hatch retired'. Corpus pins ride the Go engine; refusal pinned;
also fixed pre-existing missing sys.path setup so the battery actually
runs (4 passed).
2026-10-25 18:31:45 +00:00
Nick 1a75b10f43 removal-prep/openai-compat-transport: minify Python oracle bodies to escape-hatch refusal (Gitea #2598 item 2)
deploy / deploy (push) Skipped
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / lint (push) Canceled after 1s
RUNE_OPENAI_COMPAT_TRANSPORT_ENGINE=python switch shape kept; five oracle
bodies raise 'escape hatch retired'. Suite rides the Go engine now;
seam-stub tests converted to refusal pins; semantics (reasoning→thinking,
translation shapes) pinned Go-side in the parity battery (36 passed).
2026-10-25 18:23:33 +00:00
Nick 65d3ffd3e0 chore(scripts): admission dispatch for the second-pass verification wave (Gitea #2596)
Auto-version and Release / release (push) Failing after 34s
CI Pipeline / lint (push) Failing after 45s
CI Pipeline / go-surface (push) Failing after 22s
CI Pipeline / rust-surface (push) Failing after 10s
CI Pipeline / unit-tests (push) Failing after 1m8s
CI Pipeline / best-practices (push) Successful in 6s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Failing after 59s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / type-check (push) Failing after 42s
issue-convergence / converge (push) Successful in 1s
deploy / deploy (push) Failing after 16s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 6s
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.
2026-10-25 18:06:52 +00:00
Nick d18b20ed0b test(forge_client): pin live-path httpx.ConnectError kind projection — mutation spot-check closure (second pass)
The truth rows exercise fake-sender branches only; the live error
classification (classifyTransportErr dial → httpx.ConnectError) is
uncovered, and renaming the kind survived the battery. Pinned through
the eval surface against a closed local port
(TestEvalRequestConnectErrorKind); red under mutation, green clean.
2026-10-25 18:05:55 +00:00
Nick 6b95896c4a test(abstraction): pin list_beliefs eval default limit=20 — mutation spot-check closure (second pass)
The truth rows always carry an explicit limit, so flipping the Go
eval op's default (20 → 19) survived the battery while the eval CLI
(a public serving surface) would silently return one fewer row than
the oracle's list_beliefs(limit=20). Pinned over a 25-belief seeded
store: default == explicit-20 (TestListBeliefsDefaultLimitIs20).
2026-10-25 18:05:55 +00:00
Nick f6338f1143 test(goal): pin eval-surface default verification_method — mutation spot-check closure (second pass)
The frozen truth rows always pass an explicit verification_method, so
flipping the eval op's 'manual' default survived the battery. Pinned
via the eval surface (TestSetGoalDefaultVerificationMethod); red under
mutation, green clean.
2026-10-25 18:05:55 +00:00
Nick 68dd48c035 removal-prep/message-queue: minify Python oracle methods to escape-hatch refusal (Gitea #2598 item 2)
deploy / deploy (push) Skipped
CI Pipeline / lint (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
RUNE_MESSAGE_QUEUE_ENGINE=python switch shape kept; all eight oracle
methods raise 'escape hatch retired'. Battery keeps Go-side flow pins,
replaces oracle comparisons and cross-engine coherence pins with refusal
assertions (13 passed).
2026-10-25 17:47:56 +00:00
Nick 90cc4fa271 removal-prep/forge-client: minify _request_python oracle body to escape-hatch refusal (Gitea #2598 item 2)
deploy / deploy (push) Skipped
CI Pipeline / lint (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
RUNE_FORGE_CLIENT_ENGINE=python switch shape kept; the oracle body raises
'escape hatch retired'. Parity battery keeps all Go-side retry/status
pins and asserts the refusal (14 passed). Pre-existing note:
tests/test_forge_client.py and _resilience.py fail collection on master
(rune import path) — untouched here.
2026-10-25 17:42:34 +00:00
Nick e388abed01 test(forge_client): pin live-path httpx.ConnectError kind projection — mutation spot-check closure (second pass)
The truth rows exercise fake-sender branches only; the live error
classification (classifyTransportErr dial → httpx.ConnectError) is
uncovered, and renaming the kind survived the battery. Pinned through
the eval surface against a closed local port
(TestEvalRequestConnectErrorKind); red under mutation, green clean.
2026-10-25 17:39:11 +00:00
Nick 327308d075 test(abstraction): pin list_beliefs eval default limit=20 — mutation spot-check closure (second pass)
The truth rows always carry an explicit limit, so flipping the Go
eval op's default (20 → 19) survived the battery while the eval CLI
(a public serving surface) would silently return one fewer row than
the oracle's list_beliefs(limit=20). Pinned over a 25-belief seeded
store: default == explicit-20 (TestListBeliefsDefaultLimitIs20).
2026-10-25 17:39:10 +00:00
Nick e6162682cc test(goal): pin eval-surface default verification_method — mutation spot-check closure (second pass)
The frozen truth rows always pass an explicit verification_method, so
flipping the eval op's 'manual' default survived the battery. Pinned
via the eval surface (TestSetGoalDefaultVerificationMethod); red under
mutation, green clean.
2026-10-25 17:39:10 +00:00
Nick dcbec01214 removal-prep/cache: minify Python oracle bodies to escape-hatch refusal stubs (Gitea #2598 item 2)
RUNE_CACHE_ENGINE=python switch shape kept; all seven oracle bodies raise
'escape hatch retired'. Oracle-parity and cross-engine-coherence pins
retired with the bodies; Go surface battery extended with refusal tests
(26 passed).
2026-10-25 17:23:42 +00:00
Nick 1af8a29cc9 research: tooling-pile sort — 104 legit-tooling (85 FFI bridges, zero deletion candidates), 13 stale-watch canaries flagged for consolidation
deploy / deploy (push) Skipped
CI Pipeline / lint (push) Failing after 44s
CI Pipeline / go-surface (push) Failing after 23s
CI Pipeline / rust-surface (push) Failing after 7s
CI Pipeline / unit-tests (push) Failing after 1m17s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / type-check (push) Failing after 40s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / canary (push) Failing after 1m5s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 5s
2026-10-25 17:17:52 +00:00
Nick b08a8af509 removal-prep/paths: minify Python oracle bodies to escape-hatch refusal stubs (Gitea #2598 item 2)
deploy / deploy (push) Skipped
CI Pipeline / lint (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
Go engine is the authority; RUNE_PATHS_ENGINE=python switch shape kept,
each oracle body now raises 'escape hatch retired'. Battery asserts the
refusal; Go-side resolvers unchanged and green (14 passed).
2026-10-25 17:04:30 +00:00
conductor bdbd54dbb8 chore(release): auto-bump version to 0.358.3 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 37s
issue-convergence / converge (push) Successful in 1s
deploy / deploy (push) Failing after 15s
CI Pipeline / lint (push) Failing after 46s
CI Pipeline / go-surface (push) Failing after 24s
CI Pipeline / unit-tests (push) Failing after 1m21s
CI Pipeline / rust-surface (push) Failing after 11s
CI Pipeline / best-practices (push) Successful in 7s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Failing after 37s
CI Pipeline / canary (push) Failing after 59s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / required-checks (push) Failing after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 11s
Promotion record: (unknown)
2026-10-25 16:28:58 +00:00
Nick a35fb1de01 research: wiring-inventory triage — 87 witness live-pure modules into four piles; the honest rebase remainder is 30 modules / 22.6k lines (#2586 inventory)
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 0s
Auto-version and Release / release (push) Canceled after 28s
CI Pipeline / lint (push) Canceled after 25s
issue-convergence / converge (push) Successful in 2s
2026-10-25 16:28:32 +00:00
Nick c9543eb058 fix(goal): fail-loud store save — the serving-path probe caught silent data loss (Gitea #2587 second-pass)
The Python oracle's _save raises on an unwritable store (open()
propagates the OSError); the Go port swallowed the MkdirAll/WriteFile
errors, returning the goal row over a store that was never written —
set_goal reported success while the goal was lost. Save now fails loud
(typed panic, matching the message_queue loadQueue convention) and the
battery pins it (TestSaveFailsLoudOnUnwritableStore, red pre-fix).
2026-10-25 16:14:24 +00:00
conductor b2ddeba622 chore(release): auto-bump version to 0.358.2 — earned by # (patch: converged release (push-to-master bump))
deploy / deploy (push) Failing after 27s
issue-convergence / converge (push) Successful in 4s
Auto-version and Release / release (push) Failing after 32s
CI Pipeline / lint (push) Failing after 57s
CI Pipeline / go-surface (push) Failing after 28s
CI Pipeline / rust-surface (push) Failing after 9s
CI Pipeline / unit-tests (push) Failing after 1m22s
CI Pipeline / best-practices (push) Successful in 4s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Failing after 1m11s
CI Pipeline / type-check (push) Failing after 49s
CI Pipeline / gitea-offline (push) Successful in 22s
CI Pipeline / required-checks (push) Failing after 2s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 5s
Promotion record: (unknown)
2026-10-25 13:02:03 +00:00
Nick 84094f4e19 fix(auto-context): ScriptsDir cwd-independence + venv python resolution + graceful degrade — the parity battery caught three divergences (Gitea #2587 follow-through)
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 0s
Auto-version and Release / release (push) Canceled after 27s
CI Pipeline / lint (push) Canceled after 28s
issue-convergence / converge (push) Successful in 1s
1. ScriptsDir resolved RELATIVE ('src/rune') — broke for any caller whose
   cwd wasn't the repo root; now walks up to the repo root (absolute).
2. defaultPythonBin was 'python3' (system) — the oracle scripts import the
   installed rune package; system python lacks it. Resolves the repo venv
   python first (walk-up), RUNE_PYTHON override still wins.
3. warm_router.py's degrade path crashed under system python (the
   record_failure import inside the except block) — degrade now degrades.
The live-parity battery caught all three; warm_neurons parity restored.
2026-10-25 13:01:37 +00:00
Nick a52624b3ee ci: issue-convergence — keyword closes + mention sweep on master push (#issue-closing automation)
Auto-version and Release / release (push) Failing after 34s
CI Pipeline / lint (push) Failing after 59s
CI Pipeline / go-surface (push) Failing after 28s
CI Pipeline / rust-surface (push) Failing after 9s
CI Pipeline / unit-tests (push) Failing after 1m23s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / canary (push) Failing after 1m10s
CI Pipeline / type-check (push) Failing after 49s
deploy / deploy (push) Failing after 15s
issue-convergence / converge (push) Successful in 1s
CI Pipeline / gitea-offline (push) Successful in 27s
CI Pipeline / required-checks (push) Failing after 2s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 8s
2026-10-25 10:51:46 +00:00
conductor 8bb6ada8b9 chore(release): auto-bump version to 0.358.1 — earned by # (patch: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 30s
CI Pipeline / lint (push) Failing after 43s
CI Pipeline / go-surface (push) Failing after 22s
CI Pipeline / rust-surface (push) Failing after 8s
CI Pipeline / unit-tests (push) Failing after 1m12s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 6s
CI Pipeline / type-check (push) Failing after 36s
CI Pipeline / canary (push) Failing after 57s
CI Pipeline / gitea-offline (push) Successful in 16s
deploy / deploy (push) Failing after 13s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 5s
Promotion record: (unknown)
2026-10-25 06:26:46 +00:00
Nick 53ab710eb6 fix(cache): eval-surface flush + semantic parity battery — the live-parity battery caught deferred-stats loss on the eval surface (Gitea #2587 follow-through)
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 0s
Auto-version and Release / release (push) Canceled after 23s
CI Pipeline / lint (push) Canceled after 19s
ROOT CAUSE (found by the battery, red): cacheEvalOp created a fresh Env per
op and never flushed — the oracle's atexit.register(flush_stats) semantic
was missing from the eval surface, so hit accounting never reached the db
across CLI invocations (hits stayed 0 forever).
FIX: cacheEvalOps tracks every Env used and flushes each at batch end
(atexit semantic restored).
BATTERY: JSON outputs compare semantically (DeepEqual after age
normalization) — Go's alphabetical marshaller vs Python's insertion-order
dumps made byte comparison wrong; hit accounting fields normalized
(sequence-dependent by design under deferred flush, presence compared not
value, divergence documented in-test).
2026-10-25 06:26:28 +00:00
Nick 19664559c5 docs(pins): REBASE-COMPLETION wave 11/11 — the pins boundary verdict (Gitea #2587): VERDICT Python-boundary with Rust-kernel authority, NO Go wiring. Both audit premises were stale: a Go pins port exists (src/go/pins batch 58) but carries only a recall kernel over (state, item_id) — a parity artifact, not an authority; and pins is already delegated — recall/unpin/list/store-path resolve through the Rust cognition_core broker (live per the _cc_core probe), with the retired-Python-fallback RuntimeError law in force. Wiring Go would add a THIRD engine over the same semantics (no-duplicate-authority). The canonical PinRecord store (AtomicStore) is the reactive Python surface justifying the boundary. Verdict battery (4 tests) pins the topology so drift in either direction flips it; receipt verification/integrated/pins-verdict-integrated.json
Auto-version and Release / release (push) Failing after 32s
CI Pipeline / lint (push) Failing after 46s
CI Pipeline / go-surface (push) Failing after 20s
CI Pipeline / rust-surface (push) Failing after 11s
CI Pipeline / unit-tests (push) Failing after 1m10s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / canary (push) Failing after 1m0s
CI Pipeline / type-check (push) Failing after 38s
CI Pipeline / gitea-offline (push) Successful in 19s
deploy / deploy (push) Failing after 16s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 8s
2026-10-25 04:28:11 +00:00
conductor 68e8f6a351 chore(release): auto-bump version to 0.358.0 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 37s
CI Pipeline / lint (push) Failing after 40s
CI Pipeline / go-surface (push) Failing after 22s
CI Pipeline / rust-surface (push) Failing after 8s
CI Pipeline / unit-tests (push) Failing after 1m7s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Failing after 37s
CI Pipeline / canary (push) Failing after 1m1s
deploy / deploy (push) Failing after 13s
CI Pipeline / gitea-offline (push) Successful in 20s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 8s
Promotion record: (unknown)
2026-10-25 04:12:51 +00:00
Nick b4eb27a749 feat(paths): REBASE-COMPLETION wave 10/11 — fail-loud Go delegation wired for all six path resolvers (Gitea #2587): eval.go ops surface (repo_root/crate_dir/deploy_root/rune_home/harness_scripts/wait_resume_dir — the library previously had NO CLI) beside the frozen-truth replay (still green); every resolver in src/rune/paths.py resolves through the Go binary with identical env-override semantics; Python oracle bodies kept as RUNE_PATHS_ENGINE=python escape/oracle, RUNE_PATHS_BIN override, typed RuntimeError on missing binary; the Go side's Law 7 dichotomy verdict is honored by the receipt (delegation wired per the audit's zero-delegation finding, byte-identical resolution pinned both directions); red-first battery 9 tests; oracle default-path mutation flips the battery; receipt verification/integrated/paths-integrated.json
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
Auto-version and Release / release (push) Canceled after 26s
deploy / deploy (push) Canceled after 1s
CI Pipeline / lint (push) Canceled after 26s
2026-10-25 04:12:05 +00:00
conductor 7d96be8f40 chore(release): auto-bump version to 0.357.0 — earned by # (minor: converged release (push-to-master bump))
CI Pipeline / lint (push) Failing after 43s
CI Pipeline / go-surface (push) Failing after 22s
CI Pipeline / rust-surface (push) Failing after 7s
CI Pipeline / canary (push) Failing after 1m49s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / type-check (push) Failing after 37s
CI Pipeline / gitea-offline (push) Successful in 14s
Auto-version and Release / release (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 14m30s
CI Pipeline / unit-tests (push) Canceled after 18m46s
Promotion record: (unknown)
2026-10-25 03:53:23 +00:00
Nick 690c1b4832 feat(voicebox): REBASE-COMPLETION wave 9/11 — fail-loud Go delegation wired for the hook seam (Gitea #2587): eval.go ops surface (handle_hook, manifest) beside the truth replay (still green); handle_hook in src/rune/voicebox.py resolves through the Go binary — the engine's documented empty-event validation improvement (error where the oracle lies ok) surfaces through the delegation, pinned both sides; Python oracle body kept as RUNE_VOICEBOX_ENGINE=python escape/oracle, RUNE_VOICEBOX_BIN override, typed RuntimeError on missing binary; speak/listen/talk stay Python (pynput/venv-bound — blocks_full_switchover recorded); red-first battery 6 tests; oracle handler mutation flips the battery; receipt verification/integrated/voicebox-integrated.json
CI Pipeline / lint (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 0s
Auto-version and Release / release (push) Canceled after 26s
2026-10-25 03:52:54 +00:00
conductor 1a9cfd37d8 chore(release): auto-bump version to 0.356.0 — earned by # (minor: converged release (push-to-master bump))
CI Pipeline / lint (push) Failing after 49s
CI Pipeline / go-surface (push) Failing after 24s
CI Pipeline / rust-surface (push) Failing after 7s
CI Pipeline / canary (push) Failing after 1m38s
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / type-check (push) Failing after 36s
CI Pipeline / gitea-offline (push) Successful in 16s
Auto-version and Release / release (push) Failing after 26s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 14m6s
CI Pipeline / unit-tests (push) Canceled after 18m12s
Promotion record: (unknown)
2026-10-25 03:34:17 +00:00
Nick bee77e00e5 feat(auto-context): REBASE-COMPLETION wave 8/11 — fail-loud Go delegation wired for auto-context build (Gitea #2587): eval.go ops surface (build_auto_context, load_active_tasks; memory_dir-addressed; subprocess seams shell the same warm_router/memory_store scripts) beside the truth replay (still green); both functions in src/rune/auto_context.py resolve through the Go binary; Python oracle bodies kept as RUNE_AUTO_CONTEXT_ENGINE=python escape/oracle, RUNE_AUTO_CONTEXT_BIN override, typed RuntimeError on missing binary; red-first battery 8 tests; oracle task-filter mutation flips the battery; receipt verification/integrated/auto-context-integrated.json
CI Pipeline / lint (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
Auto-version and Release / release (push) Canceled after 23s
deploy / deploy (push) Canceled after 14s
2026-10-25 03:34:00 +00:00
conductor 3ceb3e5084 chore(release): auto-bump version to 0.355.0 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 25s
CI Pipeline / lint (push) Failing after 34s
deploy / deploy (push) Failing after 15m31s
CI Pipeline / go-surface (push) Failing after 20s
CI Pipeline / rust-surface (push) Failing after 8s
CI Pipeline / canary (push) Failing after 1m46s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Failing after 37s
CI Pipeline / gitea-offline (push) Successful in 14s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 22m45s
Promotion record: (unknown)
2026-10-25 03:09:58 +00:00
Nick e09bfeb28d feat(abstraction): REBASE-COMPLETION wave 7/11 — fail-loud Go delegation wired for the abstraction read/report seams (Gitea #2587): eval.go ops surface (list_beliefs/list_rules/list_skills/pipeline_stats/handle_hook/extract_facts over the truth-tested library, memory_dir-addressed) beside the truth replay (still green); five seam functions in src/rune/abstraction.py resolve through the Go binary against the SAME abstraction.db; Python oracle bodies kept as RUNE_ABSTRACTION_ENGINE=python escape/oracle, RUNE_ABSTRACTION_BIN override, typed RuntimeError on missing binary; extract_facts/synthesize_observations/run_lifecycle stay Python this wave (the llm_call judge seam — blocks_full_switchover recorded); red-first battery 9 tests; oracle state-filter mutation flips the battery; receipt verification/integrated/abstraction-integrated.json
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 0s
Auto-version and Release / release (push) Canceled after 25s
CI Pipeline / lint (push) Canceled after 30s
2026-10-25 03:09:32 +00:00
conductor fec44ba321 chore(release): auto-bump version to 0.354.0 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 25s
CI Pipeline / lint (push) Failing after 34s
deploy / deploy (push) Failing after 15m25s
CI Pipeline / go-surface (push) Failing after 21s
CI Pipeline / rust-surface (push) Failing after 7s
CI Pipeline / canary (push) Failing after 1m43s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Failing after 38s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / unit-tests (push) Failing after 26m50s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 5s
Promotion record: (unknown)
2026-10-25 02:28:01 +00:00
Nick 058b37d5a9 feat(message-queue): REBASE-COMPLETION wave 6/11 — fail-loud Go delegation wired into the message queue (Gitea #2587): eval.go ops surface (detect_task_addition/add_message/get_pending/approve/reject/approve_all/clear/get_approved/mark_executed/get_status over the truth-tested MessageQueue library, queue_dir-addressed, snake_case projection) beside the truth replay (still green); eight seam methods in src/rune/message_queue.py resolve through the Go binary against the SAME queue.json — the facade reloads its in-memory mirror after engine mutations, cross-engine coherence pinned both directions; Python oracle bodies kept as RUNE_MESSAGE_QUEUE_ENGINE=python escape/oracle, RUNE_MESSAGE_QUEUE_BIN override, typed RuntimeError on missing binary; red-first battery 14 tests; oracle detect-weight mutation flips the battery; receipt verification/integrated/message-queue-integrated.json
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 0s
Auto-version and Release / release (push) Canceled after 17s
CI Pipeline / lint (push) Canceled after 18s
2026-10-25 02:27:44 +00:00
conductor 1c0e88d6bc chore(release): auto-bump version to 0.353.0 — earned by # (minor: converged release (push-to-master bump))
CI Pipeline / lint (push) Failing after 44s
CI Pipeline / go-surface (push) Failing after 22s
CI Pipeline / rust-surface (push) Failing after 10s
CI Pipeline / canary (push) Failing after 1m44s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Failing after 35s
CI Pipeline / gitea-offline (push) Successful in 14s
Auto-version and Release / release (push) Failing after 26s
deploy / deploy (push) Failing after 15m19s
CI Pipeline / unit-tests (push) Failing after 27m17s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 5s
Promotion record: (unknown)
2026-10-25 01:37:51 +00:00
Nick 543339517b feat(cache): REBASE-COMPLETION wave 5/11 — fail-loud Go delegation wired into the cache seam (Gitea #2587): eval.go ops surface (get_cache/put_cache/invalidate/invalidate_scope/invalidate_all/cache_stats/handle_hook over the truth-tested Env library, ops carry memory_dir) beside the frozen-truth replays (still green); seven seam functions in src/rune/cache.py resolve through the Go binary against the SAME cache.db — cross-engine coherence pinned both directions; miss contract ({hit: false, key}) translated at the seam so both engines expose the identical shape; Python oracle bodies kept as RUNE_CACHE_ENGINE=python escape/oracle, RUNE_CACHE_BIN override, typed RuntimeError on missing binary; red-first battery 13 tests; oracle _hash mutation flips the battery; receipt verification/integrated/cache-integrated.json
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / lint (push) Canceled after 15s
deploy / deploy (push) Canceled after 0s
Auto-version and Release / release (push) Canceled after 23s
2026-10-25 01:37:15 +00:00
conductor 2af134a25f chore(release): auto-bump version to 0.352.0 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 33s
CI Pipeline / lint (push) Failing after 48s
CI Pipeline / go-surface (push) Failing after 27s
CI Pipeline / rust-surface (push) Failing after 9s
CI Pipeline / canary (push) Failing after 1m48s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / type-check (push) Failing after 37s
CI Pipeline / gitea-offline (push) Successful in 17s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 10m21s
deploy / deploy (push) Canceled after 6m21s
Promotion record: (unknown)
2026-10-25 01:26:18 +00:00
Nick 31b9dbee76 research: the witness run — live execution diff vs the source map (three live lists + dead list)
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
Auto-version and Release / release (push) Canceled after 31s
CI Pipeline / lint (push) Canceled after 31s
deploy / deploy (push) Canceled after 0s
2026-10-25 01:25:27 +00:00
Nick 408bbb11a4 feat(governor): WAVE D56 — the provider-aware dispatch governor + the live tok/s readout (ADMIT-wave-d56-governor; Gitea #2580 + #2581, owner doctrine 2026-10-24: cloud-first routing, no consent gates on cloud, routing scope = factory lane assignment): (1) TELEMETRY — runecore.Governor samples live tok/s per provider from the engine's REAL turns (provider output-token accounting wired on every wire: ollama eval_count, [OI] completion_tokens, anthropic output_tokens, gemini candidatesTokenCount; honest token-estimate fallback when the wire carries none; a zero-token turn is never a sample); rolling per-provider samples + (provider,lanes) EMA saturation curve persisted via the hot-state pattern (~/.rune/hot/provider_throughput.json, atomic temp+rename, survives restarts); (2) SATURATION MODEL — lane cap = the highest lanes bucket whose marginal tok/s gain clears the floor (RUNE_GOVERNOR_CAP_FLOOR, default 5); no curve = honest cap 1, never invented headroom; (3) CONNECTION-HEALTH (#2581) — the core polls the registry's enabled providers on a 60s cadence (reachable/authenticated/tok/s/error_rate/last_error); the route refuses unreachable, never-probed-alive and 429-throttled providers (a throttle is worse than not spawning) while day-one pre-probe routing passes through; (4) LANE POOLS + CLOUD-FIRST — Route() orders cloud rows ahead of local (the servedByLocalBinding law), least-loaded within a tier, lane pools tracked via Acquire/ReleaseLane; (5) CLASSIFIER INTEGRATION — dispatch-classifier LoadCapacity reads the same hot file; PARALLEL verdicts become throughput-aware ('governor: N lanes free at current tok/s'), the thin pipe states '0 lanes free ... serialize, no fan-out benefit', unknown capacity stays honest; registry/lock semantics byte-unchanged (locks untouched, asserted); (6) INFO BAR READOUT — /v1/status gains the governor section; both bridge seams (serve + library) carry it; the info bar renders ' · 38 tok/s' after the context readout and NOTHING when nothing was measured (the bar never guesses); the sub-agents section shows the governor's own state row (lanes in use/cap, measured tok/s or '—'); red-first tests: governor laws (samples/persist/cap/cloud-first/health/thin-pipe/lanes), classifier capacity laws, bridge both-seam propagation, hermetic fake-serve info-bar proof '(13%) · 38 tok/s'; go test -race -short green on rune-core, dispatch-classifier, tui; vet/gofmt clean; deployed build_paired → ~/.local/share/rune/bin/rune-tui + rune-core rebuilt, core restarted — /v1/status serving the governor section live 2026-10-25 01:21:55 +00:00
conductor d58ba57f32 chore(release): auto-bump version to 0.351.0 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 48s
CI Pipeline / lint (push) Failing after 54s
CI Pipeline / go-surface (push) Failing after 33s
CI Pipeline / rust-surface (push) Failing after 9s
CI Pipeline / canary (push) Failing after 2m29s
CI Pipeline / best-practices (push) Successful in 5s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / type-check (push) Failing after 48s
CI Pipeline / gitea-offline (push) Successful in 16s
deploy / deploy (push) Failing after 15m30s
CI Pipeline / unit-tests (push) Failing after 33m42s
CI Pipeline / required-checks (push) Failing after 2s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 5s
Promotion record: (unknown)
2026-10-25 01:21:00 +01:00
Nick 790f4cbc17 feat(goal): REBASE-COMPLETION wave 4/11 — fail-loud Go delegation wired for goal state operations (Gitea #2587): eval.go ops surface (set_goal/get_active_goal/achieve_goal/fail_goal/clear_goal/list_goals over the truth-tested GoalManager library, plain-JSON projection) beside the 59-row truth replay (still exact); GoalManager state ops in src/rune/goal.py resolve through the Go binary — the engine mutates the same goals.json the TUI /goal commands read, facade reloads for coherence (cross-read test pins it); Python oracle bodies kept as RUNE_GOAL_ENGINE=python escape/oracle, RUNE_GOAL_BIN override, typed RuntimeError on missing binary; check_goal stays Python this wave (LLM-as-a-Judge seam the Go port lacks — blocks_full_switchover recorded); red-first battery 13 tests against the same storage file both engines; oracle mutation flips the battery; receipt verification/integrated/goal-integrated.json
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
Auto-version and Release / release (push) Canceled after 32s
CI Pipeline / lint (push) Canceled after 32s
deploy / deploy (push) Canceled after 0s
2026-10-25 01:20:31 +01:00
conductor ee25874664 chore(release): auto-bump version to 0.350.0 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 30s
CI Pipeline / lint (push) Failing after 53s
deploy / deploy (push) Failing after 15m42s
CI Pipeline / go-surface (push) Failing after 35s
CI Pipeline / rust-surface (push) Failing after 9s
CI Pipeline / canary (push) Failing after 2m20s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 10s
CI Pipeline / type-check (push) Failing after 46s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / unit-tests (push) Failing after 36m17s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 5s
Promotion record: (unknown)
2026-10-25 00:28:16 +01:00
Nick 3c9888b045 feat(microkernel): REBASE-COMPLETION wave 3/11 — fail-loud Go delegation wired for the pure seams (Gitea #2587): now_iso, _extract_manifest, and resolve in src/rune/microkernel.py resolve through the Go engine's parity stdin-op surface (resolve op added this wave, carrying the manifest-default-100 law explicitly — absent key ranks at 100, explicit 0 stays 0); Python oracle bodies kept as RUNE_MICROKERNEL_ENGINE=python escape/oracle, RUNE_MICROKERNEL_BIN override, typed RuntimeError on missing binary; the battery caught a REAL drift — the pre-port CLI resolve case sorted descending over inactive-inclusive candidates and printed the manifest while the oracle-parity library Resolve sorts ascending/active/name (realigned byte-identical); fire/discover stay Python this wave (Go RunHook/Fire lack the sandbox-grant, digest-verify, and process-group-kill invariants — recorded as blocks_full_switchover); red-first battery 13 tests, both oracle mutations flip it; receipt verification/integrated/microkernel-integrated.json
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 0s
Auto-version and Release / release (push) Canceled after 31s
CI Pipeline / lint (push) Canceled after 31s
2026-10-25 00:27:43 +01:00
conductor 6a9ca48105 chore(release): auto-bump version to 0.349.0 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 27s
CI Pipeline / lint (push) Failing after 37s
deploy / deploy (push) Failing after 15m33s
CI Pipeline / go-surface (push) Failing after 23s
CI Pipeline / rust-surface (push) Failing after 12s
CI Pipeline / canary (push) Failing after 1m47s
CI Pipeline / best-practices (push) Successful in 2s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / type-check (push) Failing after 37s
CI Pipeline / gitea-offline (push) Successful in 18s
CI Pipeline / unit-tests (push) Failing after 27m59s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 4s
Promotion record: (unknown)
2026-10-24 21:01:37 +01:00
Nick 5fe29ddb70 feat(forge-client): REBASE-COMPLETION wave 2/11 — fail-loud Go delegation wired into the forge serving path (Gitea #2587): eval.go ops surface (request: the retry/status decision seam over real net/http; select: the create_forge_client adapter decision) beside the 49-row truth replay (still exact); _request and create_forge_client in src/rune/forge_client.py resolve through the Go binary, Python oracle bodies kept as RUNE_FORGE_CLIENT_ENGINE=python escape/oracle, RUNE_FORGE_CLIENT_BIN override, typed RuntimeError on missing binary; the httpx exception contract preserved through delegation (4xx re-raised as HTTPStatusError, transport Kind strings map 1:1 to httpx exception classes); red-first battery (14 tests) on a live local stub forge shared by httpx AND net/http; oracle mutation flips the battery; injected-sleep documented as a Python-test-only seam; receipt verification/integrated/forge-client-integrated.json
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 0s
Auto-version and Release / release (push) Canceled after 29s
CI Pipeline / lint (push) Canceled after 31s
2026-10-24 21:01:10 +01:00
conductor 454511be53 chore(release): auto-bump version to 0.348.0 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Failing after 32s
CI Pipeline / lint (push) Failing after 46s
CI Pipeline / go-surface (push) Failing after 21s
CI Pipeline / rust-surface (push) Failing after 7s
CI Pipeline / canary (push) Failing after 1m47s
CI Pipeline / best-practices (push) Successful in 3s
CI Pipeline / documentation (push) Successful in 2s
CI Pipeline / type-check (push) Failing after 38s
CI Pipeline / gitea-offline (push) Successful in 17s
deploy / deploy (push) Failing after 15m27s
CI Pipeline / unit-tests (push) Failing after 30m25s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 5s
Promotion record: (unknown)
2026-10-24 19:47:51 +01:00
Nick 4a1e994d43 feat(openai-compat-transport): REBASE-COMPLETION wave 1/11 — fail-loud Go delegation wired into the serving path (Gitea #2587): eval.go ops surface (to_openai_messages/from_openai_response/is_cloud_pinned/chat/cloud_transport_for) added to the Go engine beside its 83-case truth replay (still exact-equal); every seam in src/rune/openai_compat_transport.py now resolves through the Go binary (whole chat turn incl. usage accumulation), Python oracle bodies kept as RUNE_OPENAI_COMPAT_TRANSPORT_ENGINE=python escape/oracle, RUNE_OPENAI_COMPAT_TRANSPORT_BIN override, typed RuntimeError on missing binary (no silent fallback — house shape model_routing.reconcile_tools_support); red-first parity battery (20 tests) against the oracle body caught ONE real drift — Go dropped the DeepSeek reasoning_content→thinking mapping (fixed byte-identical); mutation of the oracle flips the battery; oracle suite pinned to the Python bodies via autouse escape fixture (the dichotomy); integrated receipt verification/integrated/openai-compat-transport-integrated.json; admission contract ADMIT-wave-rebase-completion minted (rule-5 gate)
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
Auto-version and Release / release (push) Canceled after 26s
CI Pipeline / lint (push) Canceling
deploy / deploy (push) Canceled after 1s
2026-10-24 19:47:04 +01:00
Nick a46a0bb02a feat(tui): WAVE D55 — the D49 consent=promotion follow-through + the D53/D54 coherence pass (ADMIT-wave-d55-coherence): (1) the write/edit permission-gate special-casing leaves the approvals layer (IsWriteTool names the pane-wide capability family; a pending WRITE card never face-blocks the composer — the A2 typed-refusal law holds only for genuinely gated tools; the write card reads as WORK promotion, never permission; the consent bridge's wording audited, already D49-correct); (2) every style site resolves through the themes tokens (the confirm gold trio becomes RoleConfirm{Border,BG,Text} shared by approvals+consent; chat body/activity/gauge/border families move into the Tone() table, vivid+calm; the sub-floor ghost literal #78788A finally follows the D54 token lift; gauge-empty lifted off a ~1.5:1 literal); contrast floors assert every role AND tone in BOTH modes; the motion law's three invariants assert in one place; (3) THE STREAMING BACKSTOP SURVIVES THE CMD RETURN — chatDispatch's cancel rode a defer in the tea.Cmd closure that returns immediately, killing the 30-minute backstop and every real streamed dispatch with 'context canceled' (the D55 PTY-proof catch; red-first); (4) the 110-column live-core long-answer PTY proof harness (opt-in RUNE_LIVE_PTY_PROOF=1), the domain-registry full-coverage rows the registry run exposed, and the race build-tag gate for the documented full-race PTY flake (Gitea #2549 follow-through; #2568/#2571 coherence) 2026-10-24 17:39:32 +01:00
conductor fa88a4cf82 chore(release): auto-bump version to 0.347.0 — earned by # (minor: converged release (push-to-master bump))
CI Pipeline / documentation (push) Successful in 13s
CI Pipeline / best-practices (push) Successful in 13s
CI Pipeline / rust-surface (push) Failing after 29s
CI Pipeline / gitea-offline (push) Successful in 37s
CI Pipeline / go-surface (push) Failing after 57s
Auto-version and Release / release (push) Failing after 45s
CI Pipeline / lint (push) Failing after 1m14s
CI Pipeline / type-check (push) Failing after 1m13s
CI Pipeline / canary (push) Failing after 3m10s
deploy / deploy (push) Failing after 15m25s
CI Pipeline / unit-tests (push) Failing after 24m9s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 5s
Promotion record: (unknown)
2026-10-24 16:41:57 +01:00
Nick 0de6f1bba4 fix(release): tag_repair CLI --namespace flag — workflow argv form (#2582)
Auto-label / auto-label (pull_request) Successful in 1s
Local-Model First-Pass Review (#131) / first-pass-review (pull_request) Successful in 20s
Spec gate (headless) / spec-gate (pull_request) Successful in 20s
CI Pipeline / documentation (pull_request) Successful in 10s
CI Pipeline / best-practices (pull_request) Successful in 10s
CI Pipeline / rust-surface (pull_request) Failing after 25s
CI Pipeline / gitea-offline (pull_request) Successful in 28s
CI Pipeline / go-surface (pull_request) Failing after 42s
CI Pipeline / lint (pull_request) Failing after 55s
CI Pipeline / type-check (pull_request) Failing after 57s
CI Pipeline / canary (pull_request) Failing after 3m13s
CI Pipeline / unit-tests (pull_request) Failing after 30m37s
CI Pipeline / required-checks (pull_request) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (pull_request) Failing after 5s
CI Pipeline / lint (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
Auto-version and Release / release (push) Canceled after 17s
deploy / deploy (push) Canceled after 16s
2026-10-24 15:23:08 +01:00
conductor 9c5ea0aeb2 Merge pull request 'Plain-English release bodies: what changed + what to try on every pushed version (#2582 fix branch)' (#2583) from feat/2582-release-reporting into master
Auto-version and Release / release (push) Failing after 20s
CI Pipeline / best-practices (push) Successful in 17s
CI Pipeline / rust-surface (push) Failing after 30s
CI Pipeline / documentation (push) Successful in 14s
CI Pipeline / gitea-offline (push) Successful in 31s
CI Pipeline / go-surface (push) Failing after 53s
CI Pipeline / lint (push) Failing after 1m7s
CI Pipeline / type-check (push) Failing after 1m3s
CI Pipeline / canary (push) Failing after 3m23s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 11m37s
CI Pipeline / unit-tests (push) Canceled after 12m10s
2026-10-24 15:11:36 +01:00
Nick f18b6cc363 feat(changelog): committed CI-checkable changelog — deterministic regen gate (#2582)
CI Pipeline / lint (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Skipped
Local-Model First-Pass Review (#131) / first-pass-review (pull_request) Successful in 28s
CI Pipeline / rust-surface (pull_request) Failing after 25s
Spec gate (headless) / spec-gate (pull_request) Successful in 36s
CI Pipeline / best-practices (pull_request) Successful in 9s
CI Pipeline / documentation (pull_request) Successful in 8s
Auto-label / auto-label (pull_request) Successful in 1s
CI Pipeline / go-surface (pull_request) Failing after 48s
CI Pipeline / gitea-offline (pull_request) Successful in 19s
CI Pipeline / lint (pull_request) Failing after 59s
CI Pipeline / type-check (pull_request) Failing after 46s
CI Pipeline / canary (pull_request) Failing after 3m37s
CI Pipeline / unit-tests (pull_request) Failing after 37m50s
CI Pipeline / required-checks (pull_request) Failing after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (pull_request) Failing after 5s
Auto-version and Release / release (pull_request) Canceled after 0s
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.
2026-10-24 12:44:18 +01:00
Nick a13714364a feat(release): proper-markdown release bodies — rebuild authority + recursion guard (#2582)
CI Pipeline / lint (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 0s
CI Pipeline / go-surface (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 0s
CI Pipeline / best-practices (push) Canceled after 0s
CI Pipeline / documentation (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 0s
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Skipped
Spec gate (headless) / spec-gate (pull_request) Successful in 26s
Local-Model First-Pass Review (#131) / first-pass-review (pull_request) Successful in 29s
CI Pipeline / rust-surface (pull_request) Failing after 26s
CI Pipeline / best-practices (pull_request) Successful in 8s
CI Pipeline / documentation (pull_request) Successful in 8s
CI Pipeline / gitea-offline (pull_request) Successful in 21s
CI Pipeline / go-surface (pull_request) Failing after 47s
CI Pipeline / lint (pull_request) Failing after 1m3s
CI Pipeline / type-check (pull_request) Failing after 49s
CI Pipeline / canary (pull_request) Failing after 3m32s
CI Pipeline / required-checks (pull_request) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (pull_request) Canceled after 0s
CI Pipeline / unit-tests (pull_request) Canceled after 16m41s
The readability ruling applied: every version-push body rebuilds as
proper markdown — exactly ONE H1 (the version title), What is new (H2,
capped readable bullets), What you could try (H2, reader actions), How
it was verified (H2, standard gates), Technical (H2, historical tails
capped). The strip-own-sections recursion guard makes rebuilds
idempotent: earlier rebuild layers (this session produced three) strip
off until the original evidence remains, so no body ever accumulates
stale narratives again. The 49 existing releases (both namespaces)
rebuilt live; audit: zero artifacts, zero contradictions on all.
2026-10-24 12:27:32 +01:00
Nick 0ab8a9485e feat(release-notes): tool-executor try action (#2582)
deploy / deploy (push) Skipped
CI Pipeline / gitea-offline (push) Canceled after 0s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / rust-surface (push) Canceled after 6s
CI Pipeline / documentation (push) Canceled after 5s
CI Pipeline / unit-tests (push) Canceled after 6s
CI Pipeline / type-check (push) Canceled after 5s
CI Pipeline / lint (push) Canceled after 6s
CI Pipeline / canary (push) Canceled after 6s
CI Pipeline / go-surface (push) Canceled after 6s
CI Pipeline / best-practices (push) Canceled after 5s
Auto-label / auto-label (pull_request) Successful in 1s
CI Pipeline / rust-surface (pull_request) Failing after 24s
Local-Model First-Pass Review (#131) / first-pass-review (pull_request) Successful in 28s
CI Pipeline / best-practices (pull_request) Successful in 14s
CI Pipeline / documentation (pull_request) Successful in 13s
Spec gate (headless) / spec-gate (pull_request) Successful in 41s
CI Pipeline / go-surface (pull_request) Failing after 49s
CI Pipeline / gitea-offline (pull_request) Successful in 21s
CI Pipeline / lint (pull_request) Failing after 1m7s
CI Pipeline / type-check (pull_request) Failing after 52s
CI Pipeline / canary (pull_request) Failing after 3m50s
CI Pipeline / required-checks (pull_request) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (pull_request) Canceled after 0s
CI Pipeline / unit-tests (pull_request) Canceled after 27m26s
The v0.346.0 retroactive body rebuild exposed the missing reader action
for tool-executor/local_tool_runtime waves: reading a file through Rune
should behave exactly as before.
2026-10-24 11:59:59 +01:00
Nick 01da5b0999 feat(release): plain-English version reads — namespace-aware tags, what-to-try narrative (#2582)
deploy / deploy (push) Skipped
CI Pipeline / best-practices (push) Successful in 12s
CI Pipeline / documentation (push) Successful in 13s
CI Pipeline / rust-surface (push) Failing after 19s
CI Pipeline / gitea-offline (push) Successful in 20s
CI Pipeline / go-surface (push) Failing after 39s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / canary (push) Canceled after 47s
CI Pipeline / lint (push) Canceled after 51s
CI Pipeline / unit-tests (push) Canceled after 51s
CI Pipeline / type-check (push) Canceled after 52s
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.
2026-10-24 11:59:11 +01:00
conductor df413e8e41 chore(release): auto-bump version to 0.346.0 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 28s
deploy / deploy (push) Skipped
CI Pipeline / best-practices (push) Successful in 12s
CI Pipeline / rust-surface (push) Failing after 17s
CI Pipeline / documentation (push) Successful in 13s
TUI Release / release (push) Successful in 33s
CI Pipeline / gitea-offline (push) Successful in 24s
CI Pipeline / go-surface (push) Failing after 43s
CI Pipeline / lint (push) Failing after 54s
CI Pipeline / type-check (push) Failing after 53s
CI Pipeline / canary (push) Failing after 3m4s
CI Pipeline / unit-tests (push) Failing after 27m21s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 4s
Promotion record: (unknown)
2026-10-24 07:11:25 +01:00
Nick c8b12cc496 feat(tool-executor): the LOCAL TOOL EXECUTION GO DELEGATION — LocalToolRuntime's executor becomes a fail-loud delegation facade over src/go/tool-executor: the four read-only ops (read_file, list_directory, glob_files, grep_files) resolve through the Go binary by default with the JSON envelope preserved exactly; run_command stays behind the approval door (mutating — never silently executed); the Python bodies stay as oracle/escape hatch (RUNE_TOOL_EXECUTOR_ENGINE=python, RUNE_TOOL_EXECUTOR_BIN override, typed RuntimeError on missing binary, no Python deletion). Red-first parity battery caught four real Go↔Python divergences (trailing-newline line numbering, windowing context markers + offset-relative numbering, absolute grep paths, oracle error texts) before landing — all fixed byte-identical; scripted-executor test fixtures pin the python escape hatch (Gitea #2575 wave B, red-first)
CI Pipeline / documentation (push) Successful in 12s
CI Pipeline / best-practices (push) Successful in 13s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / lint (push) Canceled after 16s
CI Pipeline / go-surface (push) Canceled after 16s
CI Pipeline / rust-surface (push) Canceled after 16s
CI Pipeline / gitea-offline (push) Canceled after 2s
CI Pipeline / unit-tests (push) Canceled after 17s
CI Pipeline / canary (push) Canceled after 17s
CI Pipeline / type-check (push) Canceled after 3s
deploy / deploy (push) Canceled after 2s
Auto-version and Release / release (push) Canceled after 18s
2026-10-24 07:11:06 +01:00
conductor ec2ee953c8 chore(release): auto-bump version to 0.345.0 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 21s
deploy / deploy (push) Skipped
CI Pipeline / best-practices (push) Successful in 13s
CI Pipeline / rust-surface (push) Failing after 23s
CI Pipeline / documentation (push) Successful in 11s
TUI Release / release (push) Successful in 31s
CI Pipeline / gitea-offline (push) Successful in 21s
CI Pipeline / go-surface (push) Failing after 44s
CI Pipeline / lint (push) Failing after 56s
CI Pipeline / type-check (push) Failing after 54s
CI Pipeline / canary (push) Failing after 3m4s
CI Pipeline / unit-tests (push) Failing after 27m24s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 5s
Promotion record: (unknown)
2026-10-24 05:26:42 +01:00
Nick e39ea6ac36 feat(reconcile-tools): the CAPABILITY-FLOOR DRIFT RECONCILIATION GO PORT — reconcile_tools_support (the R2 I-R2-022 truth_drift seam) gains a Go engine surface (src/go/model_routing ReconcileToolsSupport + 'eval reconcile'), closing wave 181's full-switchover blocker: a provider supports_tools claim contradicting CAPABILITY_FLOOR surfaces as unverified drift, never silently flipping eligibility, non-floor models are never invented a floor verdict; the Python body stays as oracle/escape hatch (RUNE_MODEL_ROUTING_ENGINE=python, no Python deletion) and the function becomes a fail-loud delegation facade (typed RuntimeError on missing engine binary); red-first battery caught the drift marker's 'true/false' vs oracle 'True/False' before landing — port fixed byte-identical (Gitea #2574 wave A, red-first)
CI Pipeline / documentation (push) Successful in 20s
CI Pipeline / best-practices (push) Successful in 21s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 2s
CI Pipeline / gitea-offline (push) Canceled after 4s
CI Pipeline / lint (push) Canceled after 25s
CI Pipeline / unit-tests (push) Canceled after 25s
CI Pipeline / rust-surface (push) Canceled after 24s
CI Pipeline / go-surface (push) Canceled after 26s
CI Pipeline / canary (push) Canceled after 26s
Auto-version and Release / release (push) Canceled after 28s
deploy / deploy (push) Canceled after 6s
2026-10-24 05:26:14 +01:00
Nick 885332a8ed docs(reports): the D53 wave report — per-item outcomes, the long-answer reproduction, the scrollbar overlay diagnosis, verification and deploy state (Gitea #2568)
Auto-version and Release / release (push) Successful in 21s
CI Pipeline / best-practices (push) Successful in 9s
CI Pipeline / documentation (push) Successful in 10s
CI Pipeline / rust-surface (push) Failing after 21s
CI Pipeline / gitea-offline (push) Successful in 28s
CI Pipeline / go-surface (push) Failing after 42s
CI Pipeline / lint (push) Failing after 57s
CI Pipeline / type-check (push) Failing after 1m0s
CI Pipeline / canary (push) Failing after 3m32s
deploy / deploy (push) Failing after 15m25s
CI Pipeline / unit-tests (push) Failing after 30m22s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 8s
2026-10-24 04:45:52 +01:00
conductor 914910c006 chore(release): auto-bump version to 0.344.0 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 35s
deploy / deploy (push) Skipped
CI Pipeline / best-practices (push) Successful in 8s
CI Pipeline / rust-surface (push) Failing after 28s
CI Pipeline / documentation (push) Successful in 14s
TUI Release / release (push) Successful in 37s
CI Pipeline / go-surface (push) Failing after 46s
CI Pipeline / gitea-offline (push) Successful in 23s
CI Pipeline / lint (push) Failing after 1m0s
CI Pipeline / type-check (push) Failing after 57s
CI Pipeline / canary (push) Failing after 3m35s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
CI Pipeline / unit-tests (push) Canceled after 21m0s
Promotion record: (unknown)
2026-10-24 04:24:23 +01:00
Nick 48d85e169c style(tui): gofmt the D53 touched files
CI Pipeline / documentation (push) Successful in 15s
CI Pipeline / best-practices (push) Successful in 15s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 0s
CI Pipeline / type-check (push) Canceled after 10s
CI Pipeline / rust-surface (push) Canceled after 25s
CI Pipeline / go-surface (push) Canceled after 25s
Auto-version and Release / release (push) Canceled after 25s
CI Pipeline / lint (push) Canceled after 26s
CI Pipeline / canary (push) Canceled after 26s
CI Pipeline / unit-tests (push) Canceled after 26s
CI Pipeline / gitea-offline (push) Canceled after 11s
2026-10-24 04:20:16 +01:00
Nick f869fa407b feat(tui-sub-agents): the SUB-AGENTS surface — the activity pane gains the 'sub agents being processed now' section rendered ONLY while the syncWaves flight observation reports agents in flight (one poller, the section can never disagree with the rail marker or the wave tab), clickable via the mouse.activity.agents region with ctrl+w keyboard parity, opening the CENTERED agents window (the D52 picker pattern) listing all in-flight agents, the selected row's thought process / recent activity (the factory journal + observations detail the shell already derives) rendered beneath; window is modal (esc/up/down, any other key closes), a plain-value AgentView seam keeps lanes un-importable from tasks (Gitea #2568 item 7, red-first) 2026-10-24 04:20:16 +01:00
Nick 71fd16a36b test(tui-idle-token): the D52 frozen idle test follows the D53 owner ruling — idle's rest tone is the RoleStateIdle yellow token (supersession recorded in the test's law comment) 2026-10-24 04:20:16 +01:00
Nick 06302b2472 feat(tui-composer-styling): the composer's enter-send/shift+enter key hints render GHOSTED PINK (a new themes RoleHintPink token — softer than the speaker pink, calm variant included, re-resolved on the calm flip) and the activity pane's idle state renders YELLOW (the RoleStateIdle token replacing the idle dim) (Gitea #2568 item 6, red-first) 2026-10-24 04:20:16 +01:00
Nick 704b4488e6 feat(tui-info-bar): the context readout gains the usage percentage in brackets — 'context 4.2k/32k (13%)' computed from the existing used/limit pair, clamped 0-100, still silent when the engine never reported (Gitea #2568 item 5, red-first) 2026-10-24 04:20:16 +01:00
Nick 6aa271c2d4 test(tui-dynamic-reflow): the DYNAMIC REFLOW squash test — a WindowSizeMsg driven through the face chain re-wraps every bubble and box at the new width: no rendered line exceeds the pane at 44/100 columns in either direction and the scroll offset normalizes to a legal window after the line-count change (frozen law; the render-path rewrap already held — the test pins it against regression) (Gitea #2568 item 4, red-first) 2026-10-24 04:20:16 +01:00
conductor d054ecb1c6 chore(release): auto-bump version to 0.343.0 — earned by # (minor: converged release (push-to-master bump))
Auto-version and Release / release (push) Successful in 26s
deploy / deploy (push) Skipped
CI Pipeline / best-practices (push) Successful in 14s
CI Pipeline / rust-surface (push) Failing after 23s
CI Pipeline / documentation (push) Successful in 8s
TUI Release / release (push) Successful in 39s
CI Pipeline / go-surface (push) Failing after 44s
CI Pipeline / gitea-offline (push) Successful in 23s
CI Pipeline / lint (push) Failing after 1m0s
CI Pipeline / type-check (push) Failing after 50s
CI Pipeline / canary (push) Failing after 3m30s
CI Pipeline / unit-tests (push) Failing after 31m13s
CI Pipeline / required-checks (push) Failing after 1s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Failing after 7s
Promotion record: (unknown)
2026-10-24 03:49:26 +01:00
Nick 1bbd48fbe5 feat(streaming): the STREAMING ROOT-CAUSE WAVE — a turn never sits inside one blocking call again (Gitea #2556, owner verdict: the 330s→480s→6x480s lineage patched the symptom)
CI Pipeline / best-practices (push) Successful in 14s
CI Pipeline / documentation (push) Successful in 13s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 0s
CI Pipeline / lint (push) Canceled after 21s
CI Pipeline / unit-tests (push) Canceled after 21s
CI Pipeline / rust-surface (push) Canceled after 21s
CI Pipeline / gitea-offline (push) Canceled after 7s
Auto-version and Release / release (push) Canceled after 21s
CI Pipeline / go-surface (push) Canceled after 21s
CI Pipeline / canary (push) Canceled after 22s
CI Pipeline / type-check (push) Canceled after 8s
- 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
2026-10-24 03:48:49 +01:00
Nick 7a2a499ce7 feat(tui-quit-warning): the QUIT WARNING — the first ctrl+c of the D46 exit gesture swaps the composer's key hint for the literal soft hint 'press ctrl+c again to quit'; any other key or the 1s timeout restores the live key hint (the D46 timeout disarm was unreachable dead code inside the KeyPressMsg case — the reset now has its own switch case); the second press quits and never shows the warning; the face chain gains the Hint read-back seam (Gitea #2568 item 3, red-first) 2026-10-24 02:40:54 +01:00
Nick 3ea85744e9 feat(tui-scrollbar): the SCROLLBAR wave — the fade layer's overlay splice dropped the thumb glyph on short styled rows (the common transcript shape), so no bar ever rendered; the splice now lands the glyph at the column even past the line's end, the thumb carries a new themes RoleScroll token (scheme-matched yet distinct, calm variant included, floors hold), per-pane rows render styled-or-empty for chat AND tasks, and the wheel step math is pinned (one notch = its exact 3-line step, exact accumulation) (Gitea #2568 item 2, red-first) 2026-10-24 01:31:16 +01:00
Nick 00d55b4383 fix(tui-long-answer-format): the LONG-ANSWER FORMAT BREAK — the table breakout's edge rows overflowed the pane width at narrow widths (edge/body geometry disagreed by the separator columns); rows and edges now share one width, an honest too-narrow drop, and the 20+ block mixed-answer repro asserts no line exceeds the pane at any width, summary-first at any length, no reflow on append (Gitea #2568 item 1, red-first) 2026-10-24 00:44:01 +01:00
217 changed files with 25962 additions and 1407 deletions

No files matched your search

+44 -5
View File
@@ -38,7 +38,7 @@ jobs:
PR_TITLE: ${{ gitea.event.pull_request.title }}
PR_BODY: ${{ gitea.event.pull_request.body }}
PR_NUMBER: ${{ gitea.event.pull_request.number }}
run: "LATEST=$(python3 scripts/tag_repair.py --latest-tag)\nLATEST=\"${LATEST#v}\"\nif [ -z\
run: "LATEST=$(python3 scripts/tag_repair.py --latest-tag --namespace main)\nLATEST=\"${LATEST#v}\"\nif [ -z\
\ \"$LATEST\" ]; then\n echo \"No previous tags \u2014 releasing first version\
\ $RELEASE_VERSION\"\n exit 0\nfi\nif [ \"$LATEST\" != \"$RELEASE_VERSION\"\
\ ]; then\n if [ \"$(printf '%s\\n%s\\n' \"$LATEST\" \"$RELEASE_VERSION\"\
@@ -162,7 +162,7 @@ jobs:
\ summary of\n# what was worked on and submitted \u2014 the version-history\
\ reader's\n# counterpart of the issue-entry template. Appended to the\n#\
\ evidence-derived body so governed facts AND human narrative\n# both appear.\n\
LAST_TAG=$(python3 scripts/tag_repair.py --latest-tag)\nEXIT_NOTE=$(python3 scripts/release_exit_note.py\
LAST_TAG=$(python3 scripts/tag_repair.py --latest-tag --namespace main)\nEXIT_NOTE=$(python3 scripts/release_exit_note.py\
\ --since \"$LAST_TAG\" --to HEAD) || EXIT_NOTE=\"\"\npython3 scripts/release_history_cli.py\
\ init \\\n --version \"$RELEASE_VERSION\" \\\n --server \"$RELEASE_SERVER_URL\"\
\ --repo \"$RELEASE_REPO\" \\\n --store store/scaffold/current.json \\\n\
@@ -180,17 +180,56 @@ jobs:
# Python. Idempotent: a body already carrying the section is left
# alone. Additive and non-blocking: a missing toolchain or a failed
# prepend must never fail a release.
LAST_TAG=$(python3 scripts/tag_repair.py --latest-tag)
LAST_TAG=$(python3 scripts/tag_repair.py --latest-tag --namespace main)
if ! command -v go >/dev/null 2>&1; then
echo "::warning::go toolchain unavailable — release published without the plain-English narrative"
exit 0
fi
(cd src/go/release-notes && go build -o /tmp/rune-release-notes .) || {
echo "::warning::release-notes build failed — narrative skipped"; exit 0; }
/tmp/rune-release-notes prepend-narrative \
# rebuild-body (#2582): the FULL plain-english template — proper
# markdown (one H1 title, sections in reading order, capped bullets,
# the no-changes contradiction scrubbed, evidence tails preserved).
(cd src/go/release-notes && go build -o /tmp/rune-release-notes .) || true
/tmp/rune-release-notes rebuild-body \
--server "$RELEASE_SERVER_URL" --repo "$RELEASE_REPO" \
--tag "v${RELEASE_VERSION}" --since "$LAST_TAG" || {
echo "::warning::narrative prepend failed — release body left unchanged"; exit 0; }
echo "::warning::body rebuild failed — release body left unchanged"; exit 0; }
- name: Sync the committed changelog from the version tags
env:
GITEA_TOKEN: ${{ secrets.ROBOT_TOKEN }}
run: |
# CHANGELOG LAW (#2582): every release syncs the committed
# CHANGELOG.md (deterministic regeneration from the tags). CI's lint
# job byte-compares regeneration against the file — a release that
# skipped its sync turns the gate red on the next PR. The sync
# commit is chore(changelog) (= releases nothing; the convergence
# classifier's "other" bucket), so it earns no version.
(cd src/go/release-notes && go build -o /tmp/rn-changelog .) || {
echo "::warning::changelog sync skipped — go toolchain unavailable"; exit 0; }
/tmp/rn-changelog changelog
if ! git diff --quiet CHANGELOG.md; then
git config user.name "release-bot"
git config user.email "release-bot@rune.dev"
git add CHANGELOG.md
# push auth = tag_repair's tested extraheader mechanism
python3 - <<'PYPUSH'
import tag_repair
import os, subprocess
repo = os.getcwd()
try:
err = tag_repair._configure_http_auth(repo)
except Exception as e:
err = str(e)
if err:
print("::warning::changelog push auth unavailable — next release retries:", str(err)[:120])
else:
r = subprocess.run(["git", "push", "gitea", "master"], capture_output=True, text=True)
print("changelog push:", r.returncode, (r.stdout or r.stderr)[:200])
PYPUSH
else
echo "changelog already current"
fi
- name: Refresh README Highlights from the latest release
env:
GITEA_TOKEN: ${{ secrets.ROBOT_TOKEN }}
+8
View File
@@ -67,6 +67,14 @@ jobs:
if [ "${#ADDED_PYTHON[@]}" -gt 0 ]; then
.ci-venv/bin/ruff format --check "${ADDED_PYTHON[@]}"
fi
- name: Changelog currency (generated == committed, #2582)
run: |
# The committed CHANGELOG.md is generated from the version tags; a
# release that landed without a changelog sync (or a tag added
# without an entry) fails here with the sync command. The tags are
# immutable — regeneration is deterministic, so this gate is exact.
(cd src/go/release-notes && go build -o /tmp/rn-changelog .)
/tmp/rn-changelog changelog --check
- name: Structural contract (henxels)
run: |
.ci-venv/bin/henxels check --all --plain
+88
View File
@@ -0,0 +1,88 @@
name: issue-convergence
# ISSUE-CONVERGENCE (owner directive 2026-10-24): issues auto-close when the
# work that resolves them reaches master. Two roads, both mechanical:
#
# 1. KEYWORD ROAD — commit messages containing "closes #N" / "fixes #N" /
# "resolves #N" (case-insensitive) close N on the master push that
# carries them. Gitea's native auto-close only fires on the default
# branch via PR merge; direct pushes to master bypass it — this job
# replaces that gap.
# 2. MENTION SWEEP — issues referenced in landed commit bodies (#N) get a
# convergence comment ("referenced from <sha> on master") so the ledger
# shows which landed work touched them, without force-closing anything
# that isn't keyword-marked. Closing is the author's explicit act; the
# sweep just makes references visible.
#
# Fail-soft: any API error logs a warning and exits 0 — a failed close must
# never break the push that landed real work.
on:
push:
branches: [master]
jobs:
converge:
runs-on: rune-ci-desktop
steps:
- name: Close keyword-marked issues + comment on mentions
shell: bash
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GITEA_URL: http://127.0.0.1:3000
REPO: conductor/rune
run: |
python3 - <<'PYEOF'
import json, os, re, urllib.request
token = os.environ["GITEA_TOKEN"]
base = os.environ["GITEA_URL"] + "/api/v1/repos/" + os.environ["REPO"]
def api(path, method="GET", body=None):
req = urllib.request.Request(base + path, method=method,
headers={"Authorization": "token " + token,
"Content-Type": "application/json"},
data=json.dumps(body).encode() if body else None)
try:
with urllib.request.urlopen(req, timeout=15) as r:
return json.loads(r.read().decode() or "{}")
except Exception as e:
print(f"warn: {path}: {e}")
return None
# commits in this push: the API diff of the previous master head
# vs the new one is discovered via compare; fall back to scanning
# the last 30 commits for unprocessed keywords (idempotent: closing
# a closed issue is a no-op, commenting twice is tolerated).
compare = api("/compare/fa88a4cf..." + "${{ github.sha }}")
shas = []
if compare and compare.get("commits"):
shas = [c["sha"] for c in compare["commits"]]
else:
commits = api("/commits?limit=30")
shas = [c["sha"] for c in (commits or [])]
kw = re.compile(r"\b(?:closes?|fixed?|resolves?)\s+#(\d+)", re.I)
mention = re.compile(r"(?<![\w>#])#(\d+)\b")
closed, mentioned = [], []
for sha in shas:
c = api(f"/git/commits/{sha}")
if not c: continue
msg = c.get("message", "")
for num in set(kw.findall(msg)):
st = api(f"/issues/{num}")
if st is None: continue
if st.get("state") == "open":
r = api(f"/issues/{num}", "PATCH", {"state": "closed"})
if r: closed.append((num, sha))
for num in set(mention.findall(msg)):
if any(num == n for n, _ in closed): continue
st = api(f"/issues/{num}")
if st is None or st.get("state") != "open": continue
r = api(f"/issues/{num}/comments", "POST",
{"body": f"Referenced from landed commit `{sha[:8]}` on master."})
if r: mentioned.append((num, sha))
print(f"converged: {len(closed)} closed, {len(mentioned)} referenced")
for n, s in closed: print(f" closed #{n} (from {s[:8]})")
PYEOF
+1 -1
View File
@@ -123,7 +123,7 @@ jobs:
run: |
VERSION=$(python3 -c 'import re; text=open("src/rune/__init__.py").read(); print(re.search(r"__version__ = \"([0-9]+\.[0-9]+\.[0-9]+)\"", text).group(1))')
echo "MASTER_VERSION=$VERSION" >> "$GITHUB_ENV"
echo "LATEST_TAG=$(python3 scripts/tag_repair.py --latest-tag)" >> "$GITHUB_ENV"
echo "LATEST_TAG=$(python3 scripts/tag_repair.py --latest-tag --namespace main)" >> "$GITHUB_ENV"
python3 scripts/version_convergence.py > /tmp/convergence.json
echo "Convergence: $(cat /tmp/convergence.json)"
python3 - <<'PYENV'
+7006
View File
File diff suppressed because it is too large. Load diff
+2 -2
View File
@@ -1,9 +1,9 @@
# Rune
**Current release: v0.342.0**
**Current release: v0.359.0**
<!-- highlights:start -->
## Highlights — from the latest release (v0.342.0)
## Highlights — from the latest release (v0.359.0)
- Plain-English jargon dictionary covers the v0.329 vocabulary, plus catch-all sentence simplification pass.
+197 -97
View File
@@ -1,13 +1,13 @@
{
"schema": 1,
"version": "1.0.0",
"updated_at": "2026-10-19",
"law": "Owner directive 2026-10-17 (docs/factory/spawn-queue.md scope law): every factory registry, gate, and map covers the FULL codebase — tui/ packages, src/go/ subsystems/libraries, src/rust/ crates (the dichotomy), and the live src/rune/ Python authority modules (retirement-tracked). Owner critique this registry answers: scaffolding was designed to let the LLM instantly look up a domain and find the relevant area to fix — instead diagnosis had been grep-exploration.",
"updated_at": "2026-10-24",
"law": "Owner directive 2026-10-17 (docs/factory/spawn-queue.md scope law): every factory registry, gate, and map covers the FULL codebase \u2014 tui/ packages, src/go/ subsystems/libraries, src/rust/ crates (the dichotomy), and the live src/rune/ Python authority modules (retirement-tracked). Owner critique this registry answers: scaffolding was designed to let the LLM instantly look up a domain and find the relevant area to fix \u2014 instead diagnosis had been grep-exploration.",
"gate": "scripts/check_domain_registry.py (unified_check member domain_registry)",
"lookup": "scripts/query_domain.py",
"retirement_map": "docs/retirement-map.md + verification/retirement-map.json",
"dichotomy_law": "docs/architecture/boundary-law.md (D30: WASM-standard, cgo exception-only)",
"scaffold_contracts": "store/scaffold — every wave cites its ADMIT contract id",
"scaffold_contracts": "store/scaffold \u2014 every wave cites its ADMIT contract id",
"retirement_pending": [
{
"path": "src/rune/__main__.py",
@@ -59,7 +59,7 @@
"key_files": [
{
"path": "tui/tasks/live.go",
"why": "live task tree face — open here first"
"why": "live task tree face \u2014 open here first"
},
{
"path": "tui/tasks/live_delegation.go",
@@ -67,7 +67,7 @@
},
{
"path": "src/go/auto-tasks/main.go",
"why": "native auto-task engine (parity row 12; oracle retired PROVEN — verification/parity/task-management.json)"
"why": "native auto-task engine (parity row 12; oracle retired PROVEN \u2014 verification/parity/task-management.json)"
},
{
"path": "verification/parity/task-management.json",
@@ -75,7 +75,7 @@
}
],
"test_batteries": [
"go test ./tasks (tui module — unified_check go_suite)",
"go test ./tasks (tui module \u2014 unified_check go_suite)",
"go test (module) src/go/auto-tasks + src/go/auto_tasks",
"cargo test task_gate task_persist (unified_check rust_suite)",
"src/go/parity-harness row-12 end-to-end (unified_check migration_mutants)",
@@ -108,7 +108,7 @@
"key_files": [
{
"path": "tui/threads/README.md",
"why": "what threads render — open first"
"why": "what threads render \u2014 open first"
},
{
"path": "tui/threads/delegation.go",
@@ -116,7 +116,7 @@
}
],
"test_batteries": [
"go test ./threads (tui module — unified_check go_suite)"
"go test ./threads (tui module \u2014 unified_check go_suite)"
],
"serve_endpoints": [],
"symptoms": [
@@ -157,7 +157,7 @@
"key_files": [
{
"path": "tui/stream/stream.go",
"why": "bounded stream adapter — open here first"
"why": "bounded stream adapter \u2014 open here first"
},
{
"path": "src/go/event-bus/main.go",
@@ -169,7 +169,7 @@
}
],
"test_batteries": [
"go test ./stream (tui module — unified_check go_suite)",
"go test ./stream (tui module \u2014 unified_check go_suite)",
"go test (module) src/go/event-bus + src/go/event-envelope + src/go/event-wait",
"tests/test_legacy_event_bus.py",
"tests/test_legacy_event_envelope.py",
@@ -201,7 +201,7 @@
"key_files": [
{
"path": "tui/rail/rail.go",
"why": "activity rail — open here first"
"why": "activity rail \u2014 open here first"
},
{
"path": "tui/receipts/receipts.go",
@@ -209,8 +209,8 @@
}
],
"test_batteries": [
"go test ./rail (tui module — unified_check go_suite)",
"go test ./receipts (tui module — unified_check go_suite)"
"go test ./rail (tui module \u2014 unified_check go_suite)",
"go test ./receipts (tui module \u2014 unified_check go_suite)"
],
"serve_endpoints": [],
"symptoms": [
@@ -237,7 +237,7 @@
"key_files": [
{
"path": "tui/lanes/lanes.go",
"why": "lane layout — open here first"
"why": "lane layout \u2014 open here first"
},
{
"path": "tui/laneconc/laneconc.go",
@@ -249,9 +249,9 @@
}
],
"test_batteries": [
"go test ./lanes (tui module — unified_check go_suite)",
"go test ./laneconc (tui module — unified_check go_suite)",
"go test ./nav (tui module — unified_check go_suite)"
"go test ./lanes (tui module \u2014 unified_check go_suite)",
"go test ./laneconc (tui module \u2014 unified_check go_suite)",
"go test ./nav (tui module \u2014 unified_check go_suite)"
],
"serve_endpoints": [],
"symptoms": [
@@ -277,11 +277,11 @@
"key_files": [
{
"path": "tui/promote/promote.go",
"why": "promotion controls — open here first"
"why": "promotion controls \u2014 open here first"
}
],
"test_batteries": [
"go test ./promote (tui module — unified_check go_suite)"
"go test ./promote (tui module \u2014 unified_check go_suite)"
],
"serve_endpoints": [],
"symptoms": [
@@ -305,11 +305,11 @@
"key_files": [
{
"path": "tui/selection/suite_test.go",
"why": "the law lives in its frozen suite — open here first"
"why": "the law lives in its frozen suite \u2014 open here first"
}
],
"test_batteries": [
"go test ./selection (tui module — unified_check go_suite)"
"go test ./selection (tui module \u2014 unified_check go_suite)"
],
"serve_endpoints": [],
"symptoms": [
@@ -346,7 +346,7 @@
"key_files": [
{
"path": "tui/evidence/evidence.go",
"why": "evidence spine face — open here first"
"why": "evidence spine face \u2014 open here first"
},
{
"path": "src/rune/evidence_probe.py",
@@ -354,7 +354,7 @@
}
],
"test_batteries": [
"go test ./evidence (tui module — unified_check go_suite)",
"go test ./evidence (tui module \u2014 unified_check go_suite)",
"go test (module) src/go/evidence-probe",
"tests/test_evidence_probe.py",
"tests/test_evidence_provenance_shims.py",
@@ -391,7 +391,7 @@
}
],
"test_batteries": [
"go test ./history (tui module — unified_check go_suite)"
"go test ./history (tui module \u2014 unified_check go_suite)"
],
"serve_endpoints": [],
"symptoms": [
@@ -417,7 +417,7 @@
"key_files": [
{
"path": "tui/changes/changes.go",
"why": "changes view — open here first"
"why": "changes view \u2014 open here first"
},
{
"path": "src/go/restore-points/main.go",
@@ -425,7 +425,7 @@
}
],
"test_batteries": [
"go test ./changes (tui module — unified_check go_suite)",
"go test ./changes (tui module \u2014 unified_check go_suite)",
"go test (module) src/go/restore-points"
],
"serve_endpoints": [],
@@ -459,7 +459,7 @@
}
],
"test_batteries": [
"go test ./settings (tui module — unified_check go_suite)"
"go test ./settings (tui module \u2014 unified_check go_suite)"
],
"serve_endpoints": [],
"symptoms": [
@@ -492,7 +492,7 @@
"key_files": [
{
"path": "tui/graphproj/model.go",
"why": "projection model — open here first"
"why": "projection model \u2014 open here first"
},
{
"path": "tui/graphproj/export.go",
@@ -500,7 +500,7 @@
}
],
"test_batteries": [
"go test ./graphproj (tui module — unified_check go_suite)",
"go test ./graphproj (tui module \u2014 unified_check go_suite)",
"go test (module) src/go/graph-memory",
"tests/test_graph_projection.py",
"cargo test graph_projection graph_memory"
@@ -529,7 +529,7 @@
"key_files": [
{
"path": "tui/capsys/gate.go",
"why": "the gate system — open here first"
"why": "the gate system \u2014 open here first"
},
{
"path": "tui/capsys/budget.go",
@@ -537,7 +537,7 @@
}
],
"test_batteries": [
"go test ./capsys (tui module — unified_check go_suite)"
"go test ./capsys (tui module \u2014 unified_check go_suite)"
],
"serve_endpoints": [],
"symptoms": [
@@ -569,7 +569,7 @@
}
],
"test_batteries": [
"go test ./version (tui module — unified_check go_suite)",
"go test ./version (tui module \u2014 unified_check go_suite)",
"go test (module) src/go/release-notes",
"tests/test_check_version_surfaces.py"
],
@@ -602,7 +602,7 @@
}
],
"test_batteries": [
"go test ./update (tui module — unified_check go_suite)"
"go test ./update (tui module \u2014 unified_check go_suite)"
],
"serve_endpoints": [],
"symptoms": [
@@ -630,7 +630,7 @@
"key_files": [
{
"path": "tui/wasm/wasm.go",
"why": "wazero host — open here first"
"why": "wazero host \u2014 open here first"
},
{
"path": "tui/wasm/ipc.go",
@@ -654,7 +654,7 @@
"tui/wasm/modules missing the paired build manifest"
],
"scaffold_contract": "ADMIT-wave-wasm-token-counting",
"notes": "D30 law: Rust crosses to Go as WASM (wazero AOT); cgo is exception-only. Module artifacts are NOT committable — rebuilt reproducibly by scripts/build_paired.sh."
"notes": "D30 law: Rust crosses to Go as WASM (wazero AOT); cgo is exception-only. Module artifacts are NOT committable \u2014 rebuilt reproducibly by scripts/build_paired.sh."
},
{
"domain": "runtime-bridge",
@@ -676,7 +676,7 @@
"key_files": [
{
"path": "tui/bridge/bridge.go",
"why": "the bridge — open here first"
"why": "the bridge \u2014 open here first"
},
{
"path": "tui/bridge/client.go",
@@ -692,8 +692,8 @@
}
],
"test_batteries": [
"go test ./bridge (tui module — unified_check go_suite)",
"go test ./runtime (tui module — unified_check go_suite)",
"go test ./bridge (tui module \u2014 unified_check go_suite)",
"go test ./runtime (tui module \u2014 unified_check go_suite)",
"go test (module) src/go/rune-entry",
"tui/bridge nosim_test.go (no-simulation law)"
],
@@ -726,7 +726,7 @@
"key_files": [
{
"path": "src/go/core-server/main.go",
"why": "every route lives here — open first"
"why": "every route lives here \u2014 open first"
}
],
"test_batteries": [
@@ -767,11 +767,11 @@
},
{
"path": "src/go/alert-gateway/main.go",
"why": "gateway kernel (0 tests — known gap, retirement row 9)"
"why": "gateway kernel (0 tests \u2014 known gap, retirement row 9)"
}
],
"test_batteries": [
"go test ./alerts (tui module — unified_check go_suite)",
"go test ./alerts (tui module \u2014 unified_check go_suite)",
"go test (module) src/go/alert-gateway"
],
"serve_endpoints": [],
@@ -823,7 +823,7 @@
"key_files": [
{
"path": "tui/memory/memory.go",
"why": "pure render over canonical state — open here first"
"why": "pure render over canonical state \u2014 open here first"
},
{
"path": "src/go/context-broker/main.go",
@@ -835,7 +835,7 @@
}
],
"test_batteries": [
"go test ./memory (tui module — unified_check go_suite)",
"go test ./memory (tui module \u2014 unified_check go_suite)",
"go test (module) src/go/context-broker + src/go/memory-store-deep + src/go/memory-consolidation + src/go/memory-ops",
"tests/test_memory_continuity.py",
"tests/test_memory_port.py",
@@ -901,14 +901,14 @@
}
],
"test_batteries": [
"go test ./skills (tui module — unified_check go_suite)",
"go test ./skills (tui module \u2014 unified_check go_suite)",
"go test (module) src/go/curator + src/go/curator-core"
],
"serve_endpoints": [],
"symptoms": [
"skill/pattern proposal shown without the consent gate",
"tested/approved skill activation view wrong",
"episode→skill promotion ladder stuck"
"episode\u2192skill promotion ladder stuck"
],
"scaffold_contract": "ADMIT-wave-k21-skills-engine",
"retirement_row": 16
@@ -940,7 +940,7 @@
}
],
"test_batteries": [
"go test ./extensions (tui module — unified_check go_suite)",
"go test ./extensions (tui module \u2014 unified_check go_suite)",
"go test (module) src/go/extension-host"
],
"serve_endpoints": [],
@@ -976,7 +976,7 @@
}
],
"test_batteries": [
"go test ./devices (tui module — unified_check go_suite)",
"go test ./devices (tui module \u2014 unified_check go_suite)",
"go test (module) src/go/device-pairing"
],
"serve_endpoints": [],
@@ -1003,7 +1003,7 @@
"key_files": [
{
"path": "src/go/model-acquisition/main.go",
"why": "open here first — module main"
"why": "open here first \u2014 module main"
},
{
"path": "src/go/model-connect/main.go",
@@ -1077,7 +1077,7 @@
"key_files": [
{
"path": "src/rune/openai_compat_transport.py",
"why": "THE provider-prefix single authority (is_cloud_pinned) — open here first"
"why": "THE provider-prefix single authority (is_cloud_pinned) \u2014 open here first"
},
{
"path": "src/go/model-routing/main.go",
@@ -1094,7 +1094,7 @@
"serve_endpoints": [],
"symptoms": [
"wrong provider/model selected; route authority flags missed",
"cloud-pinned model misbudgeted (400 invalid model name noise — the proven real change)",
"cloud-pinned model misbudgeted (400 invalid model name noise \u2014 the proven real change)",
"recall expansion drifts; warm/adaptive router pick stale routes"
],
"notes": "The 2026-09-12 scaffolding real-change proof lives here (is_cloud_pinned single authority)."
@@ -1114,7 +1114,7 @@
"key_files": [
{
"path": "src/go/webfeed/main.go",
"why": "open here first — module main"
"why": "open here first \u2014 module main"
}
],
"test_batteries": [
@@ -1156,7 +1156,7 @@
"key_files": [
{
"path": "src/go/workflow-engine/main.go",
"why": "engine kernel — open here first"
"why": "engine kernel \u2014 open here first"
},
{
"path": "tui/workflows/history.go",
@@ -1164,14 +1164,14 @@
}
],
"test_batteries": [
"go test ./workflows (tui module — unified_check go_suite)",
"go test ./workflows (tui module \u2014 unified_check go_suite)",
"go test (module) src/go/workflow-engine + src/go/cron-engine",
"tests/test_deployment_canary.py"
],
"serve_endpoints": [],
"symptoms": [
"unattended workflow stalls, fires twice, or misses its cron",
"sleep→poll regression undetected (wait_guard corpus)",
"sleep\u2192poll regression undetected (wait_guard corpus)",
"workflow-engine module go test RED (known retirement-map finding)",
"night-mode switch flips at the wrong boundary"
],
@@ -1208,7 +1208,7 @@
}
],
"test_batteries": [
"go test ./modes (tui module — unified_check go_suite)",
"go test ./modes (tui module \u2014 unified_check go_suite)",
"go test (module) src/go/build-mode"
],
"serve_endpoints": [],
@@ -1248,13 +1248,13 @@
}
],
"test_batteries": [
"go test ./inputs (tui module — unified_check go_suite)",
"go test ./inputs (tui module \u2014 unified_check go_suite)",
"go test (module) src/go/auto-detect"
],
"serve_endpoints": [],
"symptoms": [
"universal input misclassified; inspect-before-connect skipped",
"auto-detect truth battery unused (0 tests — known gap)"
"auto-detect truth battery unused (0 tests \u2014 known gap)"
],
"scaffold_contract": "ADMIT-wave-universal-input",
"retirement_row": 14
@@ -1276,7 +1276,7 @@
"key_files": [
{
"path": "docs/authority-map.md",
"why": "the map itself — open here first"
"why": "the map itself \u2014 open here first"
},
{
"path": "config/umbrellas.json",
@@ -1316,11 +1316,11 @@
"key_files": [
{
"path": "src/rune/audit_ledger.py",
"why": "deployed authority — open here first"
"why": "deployed authority \u2014 open here first"
},
{
"path": "src/rust/claim_audit/src/lib.rs",
"why": "Rust claim-audit kernel (src/rust/audit_ledger/ holds only build artifacts — no crate there; the Python module is the authority)"
"why": "Rust claim-audit kernel (src/rust/audit_ledger/ holds only build artifacts \u2014 no crate there; the Python module is the authority)"
}
],
"test_batteries": [
@@ -1370,7 +1370,7 @@
},
{
"domain": "critic-gate",
"summary": "The critic gate: repair of the empty critic gate + auto-merge zero approvals (task 0.4) — protected classes require review; drift_core binds.",
"summary": "The critic gate: repair of the empty critic gate + auto-merge zero approvals (task 0.4) \u2014 protected classes require review; drift_core binds.",
"aliases": [],
"packages": {
"tui": [],
@@ -1387,7 +1387,7 @@
"key_files": [
{
"path": "src/rune/drift_core.py",
"why": "task 0.4 binding — open here first"
"why": "task 0.4 binding \u2014 open here first"
},
{
"path": "src/go/drift-core/main.go",
@@ -1404,7 +1404,7 @@
"auto-merge bypasses repository protection",
"always-pass/skipped critic not failing closed"
],
"notes": "Task 0.4 also binds merge_robot.py (not present in src/rune today — the task owns creating/repairing it)."
"notes": "Task 0.4 also binds merge_robot.py (not present in src/rune today \u2014 the task owns creating/repairing it)."
},
{
"domain": "drift-telemetry",
@@ -1519,7 +1519,7 @@
],
"test_batteries": [
"tests/test_owner_envelope.py",
"go test ./trust (tui module — unified_check go_suite)",
"go test ./trust (tui module \u2014 unified_check go_suite)",
"go test (module) src/go/stage-guards + src/go/persona-gate"
],
"serve_endpoints": [],
@@ -1588,7 +1588,7 @@
"key_files": [
{
"path": "schemas/events/corpus/event-checkpoint_committed-001.json",
"why": "the vocabulary — open here first"
"why": "the vocabulary \u2014 open here first"
}
],
"test_batteries": [
@@ -1619,7 +1619,7 @@
"key_files": [
{
"path": "scripts/deploy_promotion.py",
"why": "monotonic ancestry law, atomic symlink swap — open here first"
"why": "monotonic ancestry law, atomic symlink swap \u2014 open here first"
},
{
"path": "docs/release-flow.md",
@@ -1643,7 +1643,7 @@
},
{
"domain": "parent-map",
"summary": "The parent/child delegation ancestry map (child visibility): a missing parent never hides the child — the orphan dock.",
"summary": "The parent/child delegation ancestry map (child visibility): a missing parent never hides the child \u2014 the orphan dock.",
"aliases": [
"child-visibility",
"delegation-ancestry"
@@ -1663,7 +1663,7 @@
"key_files": [
{
"path": "tui/tasks/live_delegation.go",
"why": "orphan dock + nesting law — open here first"
"why": "orphan dock + nesting law \u2014 open here first"
},
{
"path": "tui/threads/delegation.go",
@@ -1675,8 +1675,8 @@
}
],
"test_batteries": [
"go test ./tasks (tui module — unified_check go_suite)",
"go test ./threads (tui module — unified_check go_suite)",
"go test ./tasks (tui module \u2014 unified_check go_suite)",
"go test ./threads (tui module \u2014 unified_check go_suite)",
"tests/test_delegation_pinned_paths.py",
"tests/test_orchestration_delegation.py"
],
@@ -1727,7 +1727,7 @@
"key_files": [
{
"path": "src/rune/witness.py",
"why": "witness authority — open here first"
"why": "witness authority \u2014 open here first"
},
{
"path": "src/go/witness/main.go",
@@ -1743,7 +1743,7 @@
"symptoms": [
"work outcome not recorded or unlearned (outcome_utility)",
"trace lost mid-inference; programme gate skipped",
"operator loop stuck; work-manager/output untested (0 tests — known gap)"
"operator loop stuck; work-manager/output untested (0 tests \u2014 known gap)"
],
"retirement_row": 10
},
@@ -1812,7 +1812,7 @@
"key_files": [
{
"path": "src/rune/microkernel.py",
"why": "kernel — open here first"
"why": "kernel \u2014 open here first"
},
{
"path": "src/rune/orchestration/scheduler.py",
@@ -1884,7 +1884,7 @@
"key_files": [
{
"path": "src/go/cognition/cognition.go",
"why": "Go cognition kernel — open here first"
"why": "Go cognition kernel \u2014 open here first"
},
{
"path": "src/rust/cognition_core/src/lib.rs",
@@ -1939,7 +1939,7 @@
"key_files": [
{
"path": "src/go/tool-executor/main.go",
"why": "executor — open here first"
"why": "executor \u2014 open here first"
},
{
"path": "src/rune/tool_protocol.py",
@@ -2072,7 +2072,7 @@
"key_files": [
{
"path": "src/rune/scaffold.py",
"why": "project scaffold authority — open here first"
"why": "project scaffold authority \u2014 open here first"
},
{
"path": "src/rune/construction_admission.py",
@@ -2118,7 +2118,7 @@
"key_files": [
{
"path": "src/rune/spine.py",
"why": "spine authority — open here first"
"why": "spine authority \u2014 open here first"
},
{
"path": "docs/STRUCTURAL_MAP.md",
@@ -2172,7 +2172,7 @@
"key_files": [
{
"path": "tui/semantic/model.go",
"why": "the behavior/semantic map — open here first"
"why": "the behavior/semantic map \u2014 open here first"
},
{
"path": "src/rune/rune_tui.py",
@@ -2184,7 +2184,7 @@
},
{
"path": "tui/lanes/render.go",
"why": "the composer LAYER SCAFFOLD (D43 layer law): border/content/cursor layers, each with its own tests — locked surface, visual changes owner-routed"
"why": "the composer LAYER SCAFFOLD (D43 layer law): border/content/cursor layers, each with its own tests \u2014 locked surface, visual changes owner-routed"
},
{
"path": "tui/chatpane/chatpane.go",
@@ -2196,7 +2196,7 @@
}
],
"test_batteries": [
"go test ./semantic (tui module — unified_check go_suite)",
"go test ./semantic (tui module \u2014 unified_check go_suite)",
"tests/test_pane_authority_port.py",
"canary_tui_drift.py",
"canary_tui_transport.py",
@@ -2210,7 +2210,7 @@
"tui_validator import-allowlist drift"
],
"retirement_row": 15,
"notes": "D43 layer law: every visual element is an independent layer (border/content/cursor/scroll-indicator/animation) with its own scaffold and tests inside this governed build; layers compose at render time; accidental coupling is a defect. Locked surface — visual changes owner-routed per D43 addendum."
"notes": "D43 layer law: every visual element is an independent layer (border/content/cursor/scroll-indicator/animation) with its own scaffold and tests inside this governed build; layers compose at render time; accidental coupling is a defect. Locked surface \u2014 visual changes owner-routed per D43 addendum."
},
{
"domain": "degradation-ledger",
@@ -2245,19 +2245,19 @@
}
],
"test_batteries": [
"go test ./degrade (tui module — unified_check go_suite)",
"go test ./degrade (tui module \u2014 unified_check go_suite)",
"go test (module) src/go/subsystem-health + src/go/failure-filer"
],
"serve_endpoints": [],
"symptoms": [
"degradation invisible (silent handler — the P0-06 class)",
"degradation invisible (silent handler \u2014 the P0-06 class)",
"subsystem health mis-reported",
"failure filing lost; recovery policy misclassifies"
]
},
{
"domain": "boundary-types",
"summary": "The Go↔Rust boundary message types: schemas/boundary/v1 + generated types on both sides (task 0.12 one-schema-source law).",
"summary": "The Go\u2194Rust boundary message types: schemas/boundary/v1 + generated types on both sides (task 0.12 one-schema-source law).",
"aliases": [],
"packages": {
"tui": [
@@ -2272,7 +2272,7 @@
"key_files": [
{
"path": "schemas/boundary/v1/status_snapshot.schema.json",
"why": "canonical schema set — open here first"
"why": "canonical schema set \u2014 open here first"
}
],
"test_batteries": [
@@ -2302,7 +2302,7 @@
"key_files": [
{
"path": "src/go/parity-harness/harness.go",
"why": "the parity machine — open here first"
"why": "the parity machine \u2014 open here first"
},
{
"path": "store/rebase/classification.json",
@@ -2341,7 +2341,7 @@
"key_files": [
{
"path": "docs/seams/registry.json",
"why": "the registry — open here first"
"why": "the registry \u2014 open here first"
},
{
"path": "scripts/check_seam_registry.py",
@@ -2357,7 +2357,7 @@
"serve_endpoints": [],
"symptoms": [
"seam record incomplete (RED); deletion test missing/failing",
"review date expired (AMBER — paper expiry is a documented warning)",
"review date expired (AMBER \u2014 paper expiry is a documented warning)",
"renewal mechanism detected (automatic renewal laundering = RED)"
],
"scaffold_contract": "ADMIT-wave-seam-registry"
@@ -2379,7 +2379,7 @@
"key_files": [
{
"path": "docs/factory/research-registry.json",
"why": "slug→chain map — open here first"
"why": "slug\u2192chain map \u2014 open here first"
},
{
"path": "scripts/check_research_gate.py",
@@ -2461,7 +2461,7 @@
}
],
"test_batteries": [
"go test ./internal/toolkit (tui module — unified_check go_suite)",
"go test ./internal/toolkit (tui module \u2014 unified_check go_suite)",
"go test (module) src/go/paths + src/go/pins"
],
"serve_endpoints": [],
@@ -2496,7 +2496,7 @@
"key_files": [
{
"path": "src/rune/voice_hybrid.py",
"why": "unified voice — open here first"
"why": "unified voice \u2014 open here first"
}
],
"test_batteries": [
@@ -2512,7 +2512,7 @@
},
{
"domain": "dichotomy-loaders",
"summary": "Mechanical FFI parity facades: one loader per ported Rust crate (src/rune/*_loader.py) — the Python oracle side of the dichotomy.",
"summary": "Mechanical FFI parity facades: one loader per ported Rust crate (src/rune/*_loader.py) \u2014 the Python oracle side of the dichotomy.",
"aliases": [],
"packages": {
"tui": [],
@@ -2610,7 +2610,7 @@
"key_files": [
{
"path": "src/rune/ac_loader.py",
"why": "pattern example — open here first"
"why": "pattern example \u2014 open here first"
}
],
"test_batteries": [
@@ -2668,7 +2668,7 @@
"key_files": [
{
"path": "src/rune/canary_native.py",
"why": "the module registry — open here first"
"why": "the module registry \u2014 open here first"
}
],
"test_batteries": [
@@ -2703,7 +2703,7 @@
"key_files": [
{
"path": "src/go/factory/internal/factory/kernel.go",
"why": "the reconcile kernel: admit, level-triggered tick, phase advance, close — open here first"
"why": "the reconcile kernel: admit, level-triggered tick, phase advance, close \u2014 open here first"
},
{
"path": "src/go/factory/internal/factory/queue.go",
@@ -2723,8 +2723,8 @@
}
],
"test_batteries": [
"go test ./... (module src/go/factory — kernel, queue, ladder batteries)",
"go test ./factory/... (tui module — frozen TUI surface render battery)",
"go test ./... (module src/go/factory \u2014 kernel, queue, ladder batteries)",
"go test ./factory/... (tui module \u2014 frozen TUI surface render battery)",
"pytest tests/test_factory_protocol.py (twin parity vs the frozen oracle)",
"pytest tests/test_seam_deletion.py + scripts/check_seam_registry.py (factory-orchestration socket)"
],
@@ -2736,6 +2736,106 @@
"TUI task tree missing factory waves or receipts stale (projection/fold drift)",
"parity disagreement between Go kernel and Python twin"
]
},
{
"domain": "face-presentation",
"summary": "Face presentation: the colour-token law (themes), the consent card's promotion render, the approval card, gauges, the factory-wave/firstrun/night/planloop/taskledger/taskqueue surfaces \u2014 every raw-hex style site resolves through themes tokens; contrast floors and the motion law assert here.",
"aliases": [],
"packages": {
"tui": [
"tui/approvals",
"tui/consent",
"tui/factorywave",
"tui/firstrun",
"tui/gauges",
"tui/night",
"tui/planloop",
"tui/taskledger",
"tui/taskqueue",
"tui/themes"
],
"src_go": [],
"src_rust": [],
"src_rune": []
},
"key_files": [
{
"path": "tui/themes/roles.go",
"why": "the colour-token law \u2014 RoleHex/Tone resolve every style site; open here first"
},
{
"path": "tui/themes/d55_coherence_test.go",
"why": "contrast floors, every role and tone, vivid AND calm"
},
{
"path": "tui/approvals/approvals.go",
"why": "the approval/WORK card (consent = promotion only, D49)"
},
{
"path": "tui/consent/consent.go",
"why": "the consent bridge's promotion tiers"
},
{
"path": "tui/cmd/rune-tui/d55_motion_law_test.go",
"why": "the motion law's three invariants in one place"
}
],
"test_batteries": [
"go test ./themes ./approvals ./consent ./gauges ./planloop ./night ./taskledger ./taskqueue ./factorywave ./firstrun (tui module \u2014 unified_check go_suite)"
],
"serve_endpoints": [],
"symptoms": [
"a styled row renders in a raw hex colour or breaches the 4.5:1 floor (themes/roles.go is the only home for hex)",
"calm mode does not mute a surface (the mute belongs in the themes tables)",
"the consent/approval card blocks a write that should run (D49: writes never face-block)"
],
"scaffold_contract": "ADMIT-wave-d55-coherence"
},
{
"domain": "construction-authority",
"summary": "The construction admission/activation authority (mode classify, need activation, ADMIT contracts) and the secret store \u2014 the scaffolding doors every wave's admission script drives.",
"aliases": [],
"packages": {
"tui": [],
"src_go": [
"src/go/dispatch-classifier",
"src/go/memory-continuity",
"src/go/rune-core",
"src/go/skills-engine",
"src/go/task-state"
],
"src_rust": [],
"src_rune": [
"src/rune/construction_authority.py",
"src/rune/construction_authority_cli.py",
"src/rune/secret_store.py"
]
},
"key_files": [
{
"path": "src/rune/construction_authority.py",
"why": "the admit/activate/classify doors \u2014 open here first"
},
{
"path": "src/go/rune-core/internal/runecore/server.go",
"why": "the live Go core's serve surface (identity Rune, :8376) \u2014 open here first"
},
{
"path": "src/go/dispatch-classifier/main.go",
"why": "the surface-lock arbitration (SOLO/PARALLEL/QUEUE-BEHIND)"
}
],
"test_batteries": [
"go test (module) src/go/rune-core + src/go/dispatch-classifier + src/go/memory-continuity + src/go/skills-engine + src/go/task-state",
"tests/test_capability_fit.py (admission batteries)"
],
"serve_endpoints": [],
"symptoms": [
"an admission contract fails to mint (STORE_WRITE_FAILED / canonical contract exists)",
"the classify/activate doors reject a lawful wave",
"the core's identity or capability floor drifts"
],
"scaffold_contract": "ADMIT-wave-d55-coherence"
}
]
}
+12
View File
@@ -0,0 +1,12 @@
# removal-prep/auto-context — batch manifest (Gitea #2598 item 2)
- shape: MINIFY (option b default)
- escape-hatch law: RUNE_*_ENGINE=python facade switch KEEN — oracle body refuses with 'escape hatch retired for this row - the Go engine is the authority'
- base: dcbec012 removal-prep/cache: minify Python oracle bodies to escape-hatch refusal stubs (Gitea #2598 item 2)
- verification: pytest tests/test_auto_context_go_parity.py 8 passed + tests/test_auto_execution_context.py 8 passed; ruff clean
- NOT merged to master; push-only for review.
| file | +lines | -lines |
|---|---|---|
| src/rune/auto_context.py | 2 | 113 |
| tests/test_auto_context_go_parity.py | 10 | 16 |
+18
View File
@@ -0,0 +1,18 @@
# removal-prep/cache — batch manifest (Gitea #2598 item 2)
- shape: MINIFY (option b default)
- escape-hatch law: RUNE_*_ENGINE=python facade switch KEEN — oracle body refuses with 'escape hatch retired for this row - the Go engine is the authority'
- base: a35fb1de research: wiring-inventory triage — 87 witness live-pure modules into four piles; the honest rebase remainder is 30 modules / 22.6k lines (#2586 inventory)
- verification: pytest tests/test_cache.py tests/test_cache_go_parity.py 26 passed; ruff clean
- NOT merged to master; push-only for review.
- note: branch carries stranded concurrent-agent commits (tooling-sort-2026-10-24 wave, README/pyproject bumps) — cherry-pick to master separately, do not lose
| file | +lines | -lines |
|---|---|---|
| README.md | 2 | 2 |
| pyproject.toml | 1 | 1 |
| research/rebase-proof/tooling-sort-2026-10-24.json | 983 | 0 |
| research/rebase-proof/tooling-sort-2026-10-24.md | 46 | 0 |
| src/rune/__init__.py | 1 | 1 |
| src/rune/canary_gitea_automation.py | 1 | 1 |
| src/rune/rune_tui.py | 2 | 2 |
@@ -0,0 +1,19 @@
# removal-prep/dead-superseded-pile — batch manifest (Gitea #2598 item 2)
- shape: DELETE (dead pile only)
- escape-hatch law: RUNE_*_ENGINE=python facade switch KEEN — oracle body refuses with 'escape hatch retired for this row - the Go engine is the authority'
- base: dcbec012 removal-prep/cache: minify Python oracle bodies to escape-hatch refusal stubs (Gitea #2598 item 2)
- verification: import-graph (AST) over src/tui/scripts/tests: alpha 0 importers
- NOT merged to master; push-only for review.
- note: 46 superseded pile2 modules HELD: truth-gen/canary/parity scripts still import them; tooling-sort-2026-10-24 confirms 0 dead/0 superseded in tooling pile
| file | +lines | -lines |
|---|---|---|
| manifests/removal-prep/auto-context.md | 12 | 0 |
| manifests/removal-prep/cache.md | 18 | 0 |
| manifests/removal-prep/forge-client.md | 16 | 0 |
| manifests/removal-prep/message-queue.md | 12 | 0 |
| manifests/removal-prep/openai-compat-transport.md | 13 | 0 |
| manifests/removal-prep/paths.md | 12 | 0 |
| manifests/removal-prep/reconcile-tools.md | 12 | 0 |
| src/rune/alpha.py | 0 | 1 |
+16
View File
@@ -0,0 +1,16 @@
# removal-prep/forge-client — batch manifest (Gitea #2598 item 2)
- shape: MINIFY (option b default)
- escape-hatch law: RUNE_*_ENGINE=python facade switch KEEN — oracle body refuses with 'escape hatch retired for this row - the Go engine is the authority'
- base: dcbec012 removal-prep/cache: minify Python oracle bodies to escape-hatch refusal stubs (Gitea #2598 item 2)
- verification: pytest tests/test_forge_client_go_parity.py 14 passed; ruff clean
- NOT merged to master; push-only for review.
- note: branch carries 3 stranded second-pass test commits (e6162682 test(goal), 327308d0 test(abstraction), e388abed test(forge_client)) — cherry-pick to master separately, do not lose; tests/test_forge_client.py + _resilience.py fail collection on master pre-existing
| file | +lines | -lines |
|---|---|---|
| src/go/abstraction/default_limit_test.go | 53 | 0 |
| src/go/forge_client/truth_test.go | 27 | 0 |
| src/go/goal/truth_test.go | 18 | 0 |
| src/rune/forge_client.py | 1 | 44 |
| tests/test_forge_client_go_parity.py | 16 | 28 |
+12
View File
@@ -0,0 +1,12 @@
# removal-prep/message-queue — batch manifest (Gitea #2598 item 2)
- shape: MINIFY (option b default)
- escape-hatch law: RUNE_*_ENGINE=python facade switch KEEN — oracle body refuses with 'escape hatch retired for this row - the Go engine is the authority'
- base: dcbec012 removal-prep/cache: minify Python oracle bodies to escape-hatch refusal stubs (Gitea #2598 item 2)
- verification: pytest tests/test_message_queue_go_parity.py 13 passed; ruff clean
- NOT merged to master; push-only for review.
| file | +lines | -lines |
|---|---|---|
| src/rune/message_queue.py | 8 | 109 |
| tests/test_message_queue_go_parity.py | 13 | 24 |
@@ -0,0 +1,13 @@
# removal-prep/openai-compat-transport — batch manifest (Gitea #2598 item 2)
- shape: MINIFY (option b default)
- escape-hatch law: RUNE_*_ENGINE=python facade switch KEEN — oracle body refuses with 'escape hatch retired for this row - the Go engine is the authority'
- base: dcbec012 removal-prep/cache: minify Python oracle bodies to escape-hatch refusal stubs (Gitea #2598 item 2)
- verification: pytest tests/test_openai_compat_transport.py + _go_parity.py 36 passed; ruff clean
- NOT merged to master; push-only for review.
| file | +lines | -lines |
|---|---|---|
| src/rune/openai_compat_transport.py | 5 | 135 |
| tests/test_openai_compat_transport.py | 20 | 34 |
| tests/test_openai_compat_transport_go_parity.py | 12 | 8 |
+12
View File
@@ -0,0 +1,12 @@
# removal-prep/paths — batch manifest (Gitea #2598 item 2)
- shape: MINIFY (option b default)
- escape-hatch law: RUNE_*_ENGINE=python facade switch KEEN — oracle body refuses with 'escape hatch retired for this row - the Go engine is the authority'
- base: a35fb1de research: wiring-inventory triage — 87 witness live-pure modules into four piles; the honest rebase remainder is 30 modules / 22.6k lines (#2586 inventory)
- verification: pytest tests/test_paths_go_parity.py 14 passed; ruff clean
- NOT merged to master; push-only for review.
| file | +lines | -lines |
|---|---|---|
| src/rune/paths.py | 6 | 17 |
| tests/test_paths_go_parity.py | 33 | 31 |
+12
View File
@@ -0,0 +1,12 @@
# removal-prep/reconcile-tools — batch manifest (Gitea #2598 item 2)
- shape: MINIFY (option b default)
- escape-hatch law: RUNE_*_ENGINE=python facade switch KEEN — oracle body refuses with 'escape hatch retired for this row - the Go engine is the authority'
- base: dcbec012 removal-prep/cache: minify Python oracle bodies to escape-hatch refusal stubs (Gitea #2598 item 2)
- verification: pytest tests/test_reconcile_tools_support_port.py 4 passed; ruff clean; also fixed pre-existing missing sys.path
- NOT merged to master; push-only for review.
| file | +lines | -lines |
|---|---|---|
| src/rune/model_routing.py | 1 | 29 |
| tests/test_reconcile_tools_support_port.py | 14 | 12 |
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "rune"
version = "0.342.0"
version = "0.359.0"
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
requires-python = ">=3.10"
@@ -0,0 +1,82 @@
# D53 wave report — pane polish + the long-answer format break (Gitea #2568)
Admission: `scripts/admit_wave_d53_pane_polish.py` (classified
EXISTING_PROJECT_CHANGE, activated, contract
ADMIT-wave-d53-pane-polish minted — note: the classify core treats
"listing" as a reading verb; the objective says "showing").
## Per-item outcome
1. **LONG-ANSWER FORMAT BREAK** — reproduced: a 24-block mixed answer
at width 24 rendered a table edge row 30 columns wide. Root cause:
`tableLines` (tui/chatpane/blocks.go) built edge rows wider than
body rows by the separator columns, and the width budget ignored
them. Fixed: rows and edges share one geometry, honest
too-narrow drop (`total < cols*3` → nil). Summary-first asserted
at any length; append-never-reflows asserted on the long answer.
Commit 00d55b43 (rode to gitea/master with #2556's push).
2. **SCROLLBAR** — root cause found: `overlayDisplayCol`
(tui/lanes/render.go) wrote the thumb glyph into the left half
ONLY when a content rune sat at column x — a short styled row (the
common transcript shape) lost the glyph entirely, so no bar ever
rendered. Fixed: the glyph lands at the column even past the
line's end; thumb styled with a new `RoleScroll` token (vivid
#B87C99 / calm #C790AC, floors ≥4.5:1 asserted); chat AND tasks
rows render styled-or-empty; wheel step math pinned (one notch =
exactly its 3-line step, exact accumulation). Commit 3ea85744.
3. **QUIT WARNING** — first ctrl+c swaps the composer key hint for
the literal `press ctrl+c again to quit`; any other key or the 1s
timeout restores the live key hint. Found + fixed a D46 latent
defect: the arm-reset check was unreachable dead code inside the
KeyPressMsg case — the reset now has its own top-level switch
case. The second press quits, never shows the warning. Face chain
gained a `Hint()` read-back seam. Commit 7a2a499c.
4. **DYNAMIC REFLOW** — squash test frozen: a WindowSizeMsg through
the face chain re-wraps every block at 44 and 100 columns, no line
exceeds the pane, the scroll offset normalizes after the line-count
change. The render-path rewrap already held; the test pins it.
Commit 6aa271c2.
5. **INFO BAR** — `context 4.2k/32k (13%)` via `ctxPercent`
(clamped 0-100, still silent when the engine never reported).
Commit 704b4488.
6. **COMPOSER STYLING** — key hints render in the new `RoleHintPink`
token (vivid #C9739D / calm #D083AC); the activity pane's idle
state renders in the new `RoleStateIdle` yellow token (vivid
#D9B45B / calm #C9B06E) — the D52 frozen idle test superseded with
the ruling recorded. Commits 06302b24 + 71fd16a3.
7. **SUB-AGENTS** — the activity pane's "sub agents being processed
now" section renders ONLY while the syncWaves flight observation
reports agents in flight (one poller — the section can never
disagree with the rail marker or the wave tab); clickable
(mouse.activity.agents region) with ctrl+w keyboard parity,
opening the CENTERED agents window (the D52 picker pattern)
listing all in-flight agents; the selected row's thought process /
recent activity (the factory journal + observations detail
`waveRows` already derives) renders beneath; the window is modal
(esc/up/down, any other key closes). Plain-value `AgentView` seam
keeps lanes un-importable from tasks. Commit f869fa40.
## Verification
- `go test -race -short ./...` green (tui module; the documented
pre-existing boot-smoke PTY family aside did not fire this run).
- gofmt clean; `go vet ./...` clean.
- PTY proof: the deployed binary boots to CHAT/TASKS panes with the
live "enter send" hint (bubbletea coords 0-based-normalized per the
D52 finding; click tests drive the region rects directly).
- Rebased onto gitea/master d054ecb1 (#2556 streaming wave landed
mid-flight); items 1-3 rode up with that push; items 4-7 pushed as
48d85e16.
- Deploy: `scripts/build_paired.sh` attested
`48d85e16...+20261024T032845Z`; binary installed to
`~/.local/share/rune/bin/rune-tui`; `ensure_core` reports real and
healthy (identity 'Rune', 13 models).
## Concurrency note
A streaming wave (#2556) edited tui/ concurrently mid-wave. One
commit initially swept its WIP; it was split back out (the commit was
mine, unpushed) and item 3 was re-landed from HEAD blobs. The sweep
race guard caught nothing (staged-tree check passed) — the guard
checks contract coverage, not authorship. Consider a per-hunk
authorship check for shared surfaces.
+346
View File
@@ -0,0 +1,346 @@
{
"generated": "2026-10-25",
"source": "src/rune static surface + research/witness-run/witness-2026-10-24.json",
"total": 296,
"go_delegated": {
"count": 15,
"modules": [
"abstraction",
"auto_context",
"cache",
"event_bus",
"forge_client",
"goal",
"local_tool_runtime",
"message_queue",
"microkernel",
"model_routing",
"openai_compat_transport",
"paths",
"persona_gate",
"stage_guards",
"voicebox"
]
},
"rust_kernel_wired": {
"count": 101,
"criterion": "module name matches a src/rust crate or imports cognition_core/cc_loader",
"modules": [
"abstraction_ladder",
"attention_capsule",
"behavior",
"behaviour_ledger",
"build_basis",
"capability_broker",
"capability_fit",
"capability_ledger",
"cc_loader",
"claim_audit",
"cognitive_budgeter",
"concept_topology",
"connection_pool",
"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",
"contracts",
"dependency_tracker",
"drift_cache",
"drift_core",
"drift_ledger",
"drift_observability",
"drift_resume",
"evidence_capsule",
"execution_trace",
"experience_encoding",
"failure_recovery",
"grading",
"graph_memory",
"graph_projection",
"growth_delta",
"growth_pipeline",
"identity_anchors",
"impact_preview",
"inference_trace",
"intent_continuity",
"iteration",
"layout_contract",
"lexicon_evolution",
"meaningful_events",
"memory_consolidation",
"memory_selector",
"memory_types",
"message_graph",
"mutation_guards",
"outcome_ledger",
"outcome_utility",
"pane_authority",
"pins",
"programme_continuation",
"programme_gates",
"progressive_escalation",
"project_registry",
"prompt_sync",
"recall_uplift",
"research_budget",
"response_adapter",
"route_risk_flags",
"rune_profiler",
"rune_spinner",
"safety_override",
"salience_gate",
"self_improve",
"self_model",
"self_modify",
"shard_manager",
"skill_compiler",
"spine",
"spine_language",
"spine_loader",
"state_checkpoint",
"structural_migration",
"structural_model",
"structural_store",
"subconscious_pass",
"subsystem_health",
"system_registry",
"task_gate",
"task_persist",
"testing_mode",
"tier_manager",
"time_awareness",
"token_optimizer",
"toolchain",
"verification_layer",
"voice_hybrid",
"voice_sync",
"wait_guard",
"wait_guard_corpus",
"warm_neuron_store",
"witness",
"worthiness"
]
},
"pure_python": {
"count": 180,
"load_bearing": {
"count": 59,
"modules": [
"adaptive_router",
"alpha",
"audit_ledger",
"build_attestation",
"build_mode",
"builder_bridge",
"chat_bubble",
"construction_admission",
"construction_authority",
"core_server",
"cron_engine",
"drift-ledger",
"drift_compression",
"effect_mediation",
"elastic_cognition",
"event_envelope",
"event_wait",
"factory_protocol",
"failure_auto_filer",
"ffi_query_memory_smoke",
"hook_engine",
"inference_paths",
"live_proof",
"memory_continuity",
"memory_env",
"memory_store",
"message_widgets",
"mode_gate",
"model_connect",
"model_profiles",
"model_registry",
"night_mode",
"owner_envelope",
"parallel_processor",
"persona_definition",
"provider_budget",
"provider_context",
"provider_registry",
"recall_router",
"release_notes",
"rune_graphic",
"rune_operator",
"rune_tui",
"runtime_attestation",
"secret_store",
"self_change_qualification",
"self_transform_pipeline",
"skills_engine",
"sleep_time",
"standardized_providers",
"tiered_executor",
"tool_executor",
"tool_protocol",
"tui_backlog",
"tui_transport",
"tui_validator",
"voice_injector",
"warm_router",
"work_manager"
]
},
"tooling_test": {
"count": 116,
"modules": [
"ac_loader",
"al_loader",
"bb_loader",
"bh_loader",
"bl_loader",
"broker_loader",
"builder_bridge_cli",
"ca_loader",
"canary_audit_ledger",
"canary_cognitive_strata",
"canary_context_broker",
"canary_conversation_state",
"canary_full_system",
"canary_gitea_automation",
"canary_gitea_delegation",
"canary_gitea_pipeline",
"canary_memory_synthesis",
"canary_native",
"canary_orchestration_foundation",
"canary_p0_04",
"canary_p0_05",
"canary_p0_07",
"canary_p1_01",
"canary_p1_02",
"canary_p1_03",
"canary_p1_06",
"canary_p1_07",
"canary_query_memory",
"canary_resource_authority",
"canary_rune_entry",
"canary_rune_serve",
"canary_schema_migration",
"canary_spine_meta",
"canary_surface_parity",
"canary_tui_drift",
"canary_tui_transport",
"canary_unbounded",
"cb_loader",
"ccmd_loader",
"cd_loader",
"cdec_loader",
"cf_loader",
"ci_loader",
"cl_loader",
"cm_loader",
"construction_authority_cli",
"contracts_loader",
"cp_loader",
"cpb_loader",
"cpool_loader",
"cproj_loader",
"cps_loader",
"cs_loader",
"ct_loader",
"dc_loader",
"dcore_loader",
"dl_loader",
"do_loader",
"dr_loader",
"dt_loader",
"ec_loader",
"ee_loader",
"et_loader",
"evidence_probe",
"gm_loader",
"gp2_loader",
"gp_loader",
"gr_loader",
"ic_loader",
"ip_loader",
"it_loader",
"itr_loader",
"lc_loader",
"le_loader",
"me_loader",
"mems_loader",
"mg_loader",
"mr_loader",
"ms_loader",
"mse_loader",
"mt_loader",
"mu_loader",
"ol_loader",
"ou_loader",
"pa_loader",
"pc_loader",
"pr_loader",
"progressive_escalation_loader",
"ps_loader",
"ra_loader",
"rb_loader",
"rp_loader",
"ru_loader",
"sc_loader",
"sck_loader",
"sg_loader",
"sh_loader",
"si_loader",
"sk_loader",
"sl_loader",
"sm_loader",
"smm_loader",
"smod_loader",
"sp_loader",
"sr_loader",
"ss_loader",
"stm_loader",
"ta_loader",
"tc_loader",
"tg_loader",
"tl_loader",
"tm_loader",
"to_loader",
"vh_loader",
"vs_loader",
"wr_loader"
]
},
"dead": {
"count": 1,
"modules": [
"__main__"
]
},
"unclassified": {
"count": 4,
"modules": [
"__init__",
"curator",
"forge",
"scaffold"
]
}
},
"reconciliation_with_witness": {
"witness": {
"go": 11,
"rust": 79,
"py": 204,
"tooling": 117,
"load_bearing": 87,
"dead": 1
},
"note": "static name/delegation surface finds 33 go-delegated facades (witness counted 11 served live in the boot chain \u2014 the rest execute on dispatch, not boot); rust-kernel-wired reconstructed structurally (witness stored the count only)"
}
}
+40
View File
@@ -0,0 +1,40 @@
# REBASE-PROOF wiring inventory — Gitea #2586 (2026-10-25)
"'Wired in' also means nothing Go-able left unexamined": every one of the 296 `src/rune` modules is
classified three-way, and pure-python modules further split load-bearing (executed in the witness
run) vs test/tooling vs dead.
## Honest counts
| class | count | criterion |
|---|---|---|
| **total src/rune modules** | **296** | `*.py` in src/rune |
| go-delegated | 15 | module carries a Go delegation seam (`_go_engine_bin` / `_go_bus_bin` / `_lte_go_engine_bin`) |
| rust-kernel-wired | 101 | module name matches a `src/rust` crate, or imports `cognition_core`/`cc_loader` |
| pure-python | 180 | neither of the above |
| — load-bearing (witness-executed) | 59 | in `research/witness-run/witness-2026-10-24.json` `live_pure_core` |
| — tooling/test-pattern | 116 | in witness `live_pure_tooling` |
| — dead (never executed) | 1 | witness `dead`: `__main__` |
| — unclassified | 4 | `__init__`, `curator`, `forge`, `scaffold` (glue/facade aggregators — no engine seam, no crate match, not in witness lists) |
## Reconciliation with the witness scoreboard (deltas are findings)
Witness (runtime, 2026-10-24): 11 go-facade-served-live · 79 rust-kernel-wired · 87 load-bearing ·
117 tooling · 1 dead. This inventory (static structural surface, 2026-10-25): 15 / 101 / 59 / 116 / 1.
- **go-delegated 15 vs 11:** the witness counted only facades served in the *boot chain*; the static
sweep finds 4 more delegation seams that execute on dispatch, not boot
(persona_gate, stage_guards, +2 dispatch-time surfaces). Nothing Go-able unexamined — they are
bar-2-probed in the verdict table where applicable.
- **rust-kernel-wired 101 vs 79:** the witness file stores the count only, not the list; this
inventory reconstructs the list structurally (crate-name match ∪ cognition_core import). The +22
are crate-name-matched modules whose runtime broker hop the witness's serving chain didn't touch.
- **load-bearing 59 vs 87:** the witness's 87 included modules classified load-bearing by runtime
evidence beyond name matching; the static intersection with `live_pure_core` yields 59. The
witness file remains authoritative for *runtime* load-bearing status; this file is authoritative
for *structural* classification.
- **4 unclassified:** `__init__` (package glue), `curator`, `forge`, `scaffold` (aggregator
facades). These need a disposition decision in the removal tranche (#2592) — none is Go-able
(no engine seam), none is dead (imported by live surfaces).
Full module lists: `inventory.json` (same directory).
@@ -0,0 +1,983 @@
{
"method": "import-graph grep (src/tui/scripts/tests/shared/bridge/interop + Makefile/docs) -> git recency -> AST import-resolution check; lens: tooling-pattern modules from witness-2026-10-24.json",
"date": "2026-10-25",
"counts": {
"legit_tooling": 104,
"superseded": 0,
"dead": 0,
"broken_but_needed": 0,
"of_which_stale_watch": 13
},
"legit_tooling": [
{
"module": "ac_loader",
"last_commit": "2026-10-11",
"importers": [
"src/rune/attention_capsule.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "al_loader",
"last_commit": "2026-10-12",
"importers": [
"scripts/gen_abstraction_ladder_truth.py",
"src/rune/abstraction_ladder.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "bb_loader",
"last_commit": "2026-10-11",
"importers": [
"scripts/check_build_basis_gap_parity.py",
"scripts/check_build_basis_parity.py",
"scripts/gen_build_basis_gap_truth.py",
"src/rune/build_basis.py",
"src/rust/build_basis/src/gap.rs"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "bh_loader",
"last_commit": "2026-10-12",
"importers": [
"scripts/check_behavior_parity.py",
"src/rune/behavior.py",
"tests/test_behavior_port.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "bl_loader",
"last_commit": "2026-10-12",
"importers": [
"src/rune/behaviour_ledger.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "broker_loader",
"last_commit": "2026-10-14",
"importers": [
"scripts/check_capability_broker_gap_parity.py",
"src/rune/capability_broker.py",
"tests/test_capability_broker_delegation.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "ca_loader",
"last_commit": "2026-10-14",
"importers": [
"scripts/check_construction_activation_parity.py",
"src/rune/construction_activation.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "cb_loader",
"last_commit": "2026-10-14",
"importers": [
"scripts/check_cognitive_budgeter_parity.py",
"src/rune/cognitive_budgeter.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "cc_loader",
"last_commit": "2026-10-15",
"importers": [
"scripts/check_growth_delta_parity.py",
"scripts/check_identity_anchors_parity.py",
"scripts/check_pins_parity.py",
"scripts/check_safety_override_parity.py",
"scripts/check_tier_parity.py",
"scripts/gen_abstraction_truth.py",
"scripts/gen_context_broker_truth.py",
"src/rune/abstraction.py",
"src/rune/context_broker.py",
"src/rune/growth_delta.py",
"src/rune/identity_anchors.py",
"src/rune/memory_consolidation.py",
"src/rune/persona_gate.py",
"src/rune/safety_override.py",
"src/rune/stage_guards.py",
"src/rune/tier_manager.py",
"src/rune/witness.py",
"src/rust/cognition_core/canary_cognition_core.py",
"src/rust/cognition_core/canary_growth_pins.py",
"src/rust/cognition_core/canary_persona.py",
"tests/test_cc_loader_seam.py",
"tests/test_rust_core_shims.py",
"tests/test_stage_guards_delegation.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "ccmd_loader",
"last_commit": "2026-10-14",
"importers": [
"scripts/gen_construction_commands_truth.py",
"src/rune/construction_commands.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "cd_loader",
"last_commit": "2026-10-12",
"importers": [
"src/rune/construction_depth.py",
"src/rust/construction_depth/canary_construction_depth.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "cdec_loader",
"last_commit": "2026-10-13",
"importers": [
"src/rune/construction_decision.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "cf_loader",
"last_commit": "2026-10-15",
"importers": [
"scripts/check_capability_fit_gap_parity.py",
"src/rune/capability_fit.py",
"tests/test_capability_fit_port.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "ci_loader",
"last_commit": "2026-10-11",
"importers": [
"src/rune/construction_intent.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "cl_loader",
"last_commit": "2026-10-11",
"importers": [
"scripts/fuzz_capability_ledger_parity.py",
"src/rune/capability_ledger.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "cm_loader",
"last_commit": "2026-10-14",
"importers": [
"scripts/check_construction_mode_parity.py",
"src/rune/construction_mode.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "contracts_loader",
"last_commit": "2026-10-14",
"importers": [
"scripts/gen_contracts_gap_truth.py",
"src/rune/contracts.py",
"tests/test_contracts_port.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "cp_loader",
"last_commit": "2026-10-11",
"importers": [
"scripts/check_construction_pattern_parity.py",
"src/rune/construction_pattern.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "cpb_loader",
"last_commit": "2026-10-12",
"importers": [
"scripts/check_cpb_parity.py",
"src/rune/construction_patterns_builtin.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "cpool_loader",
"last_commit": "2026-10-12",
"importers": [
"src/rune/connection_pool.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "cproj_loader",
"last_commit": "2026-10-14",
"importers": [
"scripts/check_construction_projection_parity.py",
"src/rune/construction_projection.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "cps_loader",
"last_commit": "2026-10-14",
"importers": [
"scripts/check_context_pressure_parity.py",
"src/rune/context_pressure.py",
"tests/test_context_pressure.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "cs_loader",
"last_commit": "2026-10-04",
"importers": [
"scripts/check_capability_broker_gap_parity.py",
"src/rune/capability_broker.py",
"src/rune/capability_fit.py",
"src/rust/capability_scoring/canary_capability_scoring.py",
"tests/test_capability_broker_delegation.py",
"tests/test_rust_core_shims.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "ct_loader",
"last_commit": "2026-10-12",
"importers": [
"src/rune/concept_topology.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "dc_loader",
"last_commit": "2026-10-12",
"importers": [
"scripts/gen_drift_cache_truth.py",
"src/rune/drift_cache.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "dcore_loader",
"last_commit": "2026-10-14",
"importers": [
"scripts/check_drift_core_parity.py",
"src/rune/drift_core.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "dl_loader",
"last_commit": "2026-10-12",
"importers": [
"src/rune/drift_ledger.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "do_loader",
"last_commit": "2026-10-11",
"importers": [
"scripts/gen_drift_observability_truth.py",
"src/rune/drift_observability.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "dr_loader",
"last_commit": "2026-10-13",
"importers": [
"scripts/gen_drift_resume_truth.py",
"src/rune/drift_resume.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "dt_loader",
"last_commit": "2026-10-14",
"importers": [
"scripts/check_dependency_tracker_parity.py",
"src/rune/dependency_tracker.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "ec_loader",
"last_commit": "2026-10-13",
"importers": [
"src/rune/construction_decision.py",
"src/rune/evidence_capsule.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "ee_loader",
"last_commit": "2026-10-12",
"importers": [
"scripts/gen_experience_encoding_truth.py",
"src/rune/experience_encoding.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "et_loader",
"last_commit": "2026-10-13",
"importers": [
"scripts/check_execution_trace_parity.py",
"src/rune/execution_trace.py",
"tests/test_execution_trace_port.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "gm_loader",
"last_commit": "2026-10-14",
"importers": [
"scripts/check_graph_memory_parity.py",
"src/rune/graph_memory.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "gp2_loader",
"last_commit": "2026-10-13",
"importers": [
"src/rune/growth_pipeline.py",
"tests/test_growth_pipeline_port.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "gp_loader",
"last_commit": "2026-10-15",
"importers": [
"scripts/check_graph_projection_parity.py",
"src/rune/graph_projection.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "gr_loader",
"last_commit": "2026-10-12",
"importers": [
"scripts/check_grading_gap_parity.py",
"src/rune/grading.py",
"src/rust/grading/src/gap_battery.rs"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "ic_loader",
"last_commit": "2026-10-12",
"importers": [
"src/rune/intent_continuity.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "ip_loader",
"last_commit": "2026-10-10",
"importers": [
"src/rune/impact_preview.py",
"src/rust/impact_preview/canary_impact_preview.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "it_loader",
"last_commit": "2026-10-14",
"importers": [
"scripts/check_inference_trace_parity.py",
"src/rune/inference_trace.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "itr_loader",
"last_commit": "2026-10-15",
"importers": [
"scripts/check_iteration_parity.py",
"src/rune/iteration.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "lc_loader",
"last_commit": "2026-10-14",
"importers": [
"scripts/check_layout_contract_parity.py",
"src/rune/layout_contract.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "le_loader",
"last_commit": "2026-10-12",
"importers": [
"scripts/gen_lexicon_evolution_truth.py",
"src/rune/lexicon_evolution.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "me_loader",
"last_commit": "2026-10-13",
"importers": [
"scripts/check_meaningful_events_parity.py",
"src/rune/meaningful_events.py",
"tests/test_meaningful_events_port.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "mems_loader",
"last_commit": "2026-10-04",
"importers": [
"src/rune/memory_store.py",
"src/rust/memory_storage/canary_memory_storage.py",
"tests/test_rust_core_shims.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "mg_loader",
"last_commit": "2026-10-12",
"importers": [
"src/rune/message_graph.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "mr_loader",
"last_commit": "2026-10-10",
"importers": [
"src/rune/memory_selector.py",
"src/rune/memory_store.py",
"src/rust/memory_retrieval/canary_memory_retrieval.py",
"tests/test_mr_branch_battery.py",
"tests/test_rust_core_shims.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "ms_loader",
"last_commit": "2026-10-14",
"importers": [
"henxels_checks.py",
"scripts/check_drift_compression_parity.py",
"scripts/dc_freeze_seams/sitecustomize.py",
"src/rune/drift_core.py",
"src/rune/memory_store.py",
"src/rust/memory_scoring/canary_memory_scoring.py",
"src/rust/memory_storage/canary_memory_storage.py",
"tests/test_constitutional_adversarial.py",
"tests/test_ms_loader_seam.py",
"tests/test_rust_core_shims.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "mse_loader",
"last_commit": "2026-10-13",
"importers": [
"scripts/check_memory_selector_parity.py",
"src/rune/memory_selector.py",
"tests/test_p1_03_memory_selection.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "mt_loader",
"last_commit": "2026-10-13",
"importers": [
"src/rune/memory_types.py",
"tests/test_memory_types_port.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "mu_loader",
"last_commit": "2026-10-12",
"importers": [
"scripts/gen_mutation_guards_truth.py",
"src/rune/mutation_guards.py",
"src/rust/mutation_guards/src/lib.rs"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "ol_loader",
"last_commit": "2026-10-12",
"importers": [
"scripts/check_outcome_ledger_gap_parity.py",
"src/rune/connection_pool.py",
"src/rune/outcome_ledger.py",
"src/rust/outcome_ledger/src/gap_battery.rs",
"tests/test_outcome_ledger_port.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "ou_loader",
"last_commit": "2026-10-13",
"importers": [
"src/rune/outcome_utility.py",
"tests/test_outcome_utility_port.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "pa_loader",
"last_commit": "2026-10-13",
"importers": [
"scripts/check_pane_authority_parity.py",
"src/rune/pane_authority.py",
"tests/test_pane_authority_port.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "pc_loader",
"last_commit": "2026-10-15",
"importers": [
"scripts/check_pc_residue_parity.py",
"scripts/gen_programme_continuation_residue_truth.py",
"src/rune/programme_continuation.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "pr_loader",
"last_commit": "2026-10-13",
"importers": [
"scripts/gen_project_registry_truth.py",
"src/rune/project_registry.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "progressive_escalation_loader",
"last_commit": "2026-10-14",
"importers": [
"scripts/check_progressive_escalation_parity.py",
"scripts/gen_progressive_escalation_truth.py",
"src/rune/progressive_escalation.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "ps_loader",
"last_commit": "2026-10-11",
"importers": [
"scripts/check_context_pressure_parity.py",
"src/rune/context_pressure.py",
"src/rune/prompt_sync.py",
"tests/test_context_pressure.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "ra_loader",
"last_commit": "2026-10-14",
"importers": [
"scripts/check_response_adapter_parity.py",
"src/rune/response_adapter.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "rb_loader",
"last_commit": "2026-10-13",
"importers": [
"src/rune/research_budget.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "rp_loader",
"last_commit": "2026-10-12",
"importers": [
"scripts/check_rune_profiler_parity.py",
"src/rune/rune_profiler.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "ru_loader",
"last_commit": "2026-10-12",
"importers": [
"src/rune/recall_uplift.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "sc_loader",
"last_commit": "2026-10-04",
"importers": [
"src/rune/construction_decision.py",
"src/rust/scaffold_core/canary_scaffold_core.py",
"tests/test_rust_core_shims.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "sck_loader",
"last_commit": "2026-10-15",
"importers": [
"scripts/gen_state_checkpoint_truth.py",
"src/rune/state_checkpoint.py",
"tests/test_state_checkpoint_delegation.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "sg_loader",
"last_commit": "2026-10-10",
"importers": [
"scripts/gen_salience_gate_truth.py",
"src/rune/salience_gate.py",
"src/rust/salience_gate/canary_salience_gate.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "sh_loader",
"last_commit": "2026-10-12",
"importers": [
"scripts/check_subsystem_health_parity.py",
"src/rune/subsystem_health.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "si_loader",
"last_commit": "2026-10-14",
"importers": [
"scripts/gen_self_improve_truth.py",
"src/rune/self_improve.py",
"tests/test_self_improve_port.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "sk_loader",
"last_commit": "2026-10-13",
"importers": [
"scripts/check_skill_compiler_parity.py",
"src/rune/skill_compiler.py",
"tests/test_skill_compiler_port.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "sl_loader",
"last_commit": "2026-10-12",
"importers": [
"src/rune/spine_language.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "sm_loader",
"last_commit": "2026-10-12",
"importers": [
"src/rune/shard_manager.py",
"src/rune/stm_loader.py",
"src/rust/iteration/src/lib.rs"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "smm_loader",
"last_commit": "2026-10-13",
"importers": [
"src/rune/self_model.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "smod_loader",
"last_commit": "2026-10-12",
"importers": [
"scripts/check_self_modify_gap_parity.py",
"scripts/gen_self_modify_truth.py",
"src/rune/self_modify.py",
"src/rust/self_modify/src/gap_battery.rs"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "sp_loader",
"last_commit": "2026-10-12",
"importers": [
"scripts/check_subconscious_pass_parity.py",
"src/rune/subconscious_pass.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "sr_loader",
"last_commit": "2026-10-11",
"importers": [
"src/rune/system_registry.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "ss_loader",
"last_commit": "2026-10-15",
"importers": [
"scripts/check_structural_store_parity.py",
"src/rune/structural_store.py",
"src/rust/structural_store/canary_structural_store.py",
"tests/test_structural_store_delegation.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "stm_loader",
"last_commit": "2026-10-14",
"importers": [
"scripts/check_structural_model_parity.py",
"src/rune/structural_model.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "ta_loader",
"last_commit": "2026-10-12",
"importers": [
"scripts/check_time_awareness_gap_parity.py",
"scripts/check_time_awareness_parity.py",
"src/rune/time_awareness.py",
"src/rust/time_awareness/src/gap_battery.rs",
"tests/test_time_awareness_port.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "tc_loader",
"last_commit": "2026-10-04",
"importers": [
"src/rune/token_optimizer.py",
"src/rust/token_counting/canary_token_counting.py",
"tests/test_rust_core_shims.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "tg_loader",
"last_commit": "2026-10-12",
"importers": [
"scripts/gen_task_gate_truth.py",
"src/rune/task_gate.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "tl_loader",
"last_commit": "2026-10-12",
"importers": [
"scripts/check_toolchain_parity.py",
"src/rune/toolchain.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "tm_loader",
"last_commit": "2026-10-15",
"importers": [
"scripts/check_structural_model_parity.py",
"scripts/gen_testing_mode_truth.py",
"src/rune/structural_model.py",
"src/rune/testing_mode.py",
"tests/test_testing_mode_delegation.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "to_loader",
"last_commit": "2026-10-11",
"importers": [
"scripts/check_token_optimizer_parity.py",
"src/rune/token_optimizer.py",
"tests/test_token_optimizer_port.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "vh_loader",
"last_commit": "2026-10-14",
"importers": [
"scripts/check_voice_hybrid_parity.py",
"src/rune/voice_hybrid.py",
"tests/test_voice_hybrid_port.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "vs_loader",
"last_commit": "2026-10-14",
"importers": [
"scripts/gen_voice_sync_truth.py",
"src/rune/voice_sync.py",
"src/rust/voice_sync/canary_voice_sync.py",
"tests/test_voice_sync_port.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "wr_loader",
"last_commit": "2026-10-15",
"importers": [
"scripts/gen_worthiness_truth.py",
"src/rune/worthiness.py"
],
"verdict": "FFI ctypes bridge to src/rust crate; imported by live core module/test/parity script; committed Oct 2026"
},
{
"module": "evidence_probe",
"last_commit": "2026-10-08",
"importers": [
"scripts/gen_evidence_probe_truth.py",
"scripts/gen_local_tool_runtime_truth.py",
"scripts/research_stage_executor.py",
"src/rune/canary_audit_ledger.py",
"src/rune/rune_operator.py",
"tests/test_evidence_probe.py",
"tests/test_evidence_provenance_shims.py",
"tests/test_operator_probe_isolation.py"
],
"verdict": "8 importers incl. live scripts/research_stage_executor.py; truth generators depend on it"
},
{
"module": "builder_bridge_cli",
"last_commit": "2026-10-21",
"importers": [],
"verdict": "referenced by scripts/gen_builder_bridge_cli_truth.py + structural_coverage_audit; committed 2026-10-21"
},
{
"module": "construction_authority_cli",
"last_commit": "2026-10-19",
"importers": [
"tests/test_construction_authority_drills.py"
],
"verdict": "imported by tests/test_construction_authority_drills.py; committed 2026-10-19"
},
{
"module": "canary_audit_ledger",
"last_commit": "2026-10-08",
"importers": [],
"verdict": "canary battery entry, fresh commit Oct 2026, doc-tracked (docs/dichotomy-rebase-honest-audit.md); no importers expected for canaries"
},
{
"module": "canary_full_system",
"last_commit": "2026-10-08",
"importers": [],
"verdict": "canary battery entry, fresh commit Oct 2026, doc-tracked (docs/dichotomy-rebase-honest-audit.md); no importers expected for canaries"
},
{
"module": "canary_gitea_automation",
"last_commit": "2026-10-25",
"importers": [],
"verdict": "canary battery entry, fresh commit Oct 2026, doc-tracked (docs/dichotomy-rebase-honest-audit.md); no importers expected for canaries"
},
{
"module": "canary_native",
"last_commit": "2026-10-18",
"importers": [
"docs/structural-contract.md",
"scripts/scaffold_projection.py",
"src/rune/spine.py",
"src/rust/spine/src/gap.rs"
],
"verdict": "canary battery entry, fresh commit Oct 2026, doc-tracked (docs/dichotomy-rebase-honest-audit.md); no importers expected for canaries"
},
{
"module": "canary_p0_04",
"last_commit": "2026-10-18",
"importers": [],
"verdict": "canary battery entry, fresh commit Oct 2026, doc-tracked (docs/dichotomy-rebase-honest-audit.md); no importers expected for canaries"
},
{
"module": "canary_p1_01",
"last_commit": "2026-10-18",
"importers": [],
"verdict": "canary battery entry, fresh commit Oct 2026, doc-tracked (docs/dichotomy-rebase-honest-audit.md); no importers expected for canaries"
},
{
"module": "canary_p1_02",
"last_commit": "2026-10-10",
"importers": [],
"verdict": "canary battery entry, fresh commit Oct 2026, doc-tracked (docs/dichotomy-rebase-honest-audit.md); no importers expected for canaries"
},
{
"module": "canary_p1_06",
"last_commit": "2026-10-10",
"importers": [],
"verdict": "canary battery entry, fresh commit Oct 2026, doc-tracked (docs/dichotomy-rebase-honest-audit.md); no importers expected for canaries"
},
{
"module": "canary_p1_07",
"last_commit": "2026-10-10",
"importers": [],
"verdict": "canary battery entry, fresh commit Oct 2026, doc-tracked (docs/dichotomy-rebase-honest-audit.md); no importers expected for canaries"
},
{
"module": "canary_query_memory",
"last_commit": "2026-10-08",
"importers": [],
"verdict": "canary battery entry, fresh commit Oct 2026, doc-tracked (docs/dichotomy-rebase-honest-audit.md); no importers expected for canaries"
},
{
"module": "canary_rune_entry",
"last_commit": "2026-10-04",
"importers": [],
"verdict": "canary battery entry, fresh commit Oct 2026, doc-tracked (docs/dichotomy-rebase-honest-audit.md); no importers expected for canaries"
},
{
"module": "canary_rune_serve",
"last_commit": "2026-10-17",
"importers": [],
"verdict": "canary battery entry, fresh commit Oct 2026, doc-tracked (docs/dichotomy-rebase-honest-audit.md); no importers expected for canaries"
},
{
"module": "canary_spine_meta",
"last_commit": "2026-10-04",
"importers": [],
"verdict": "canary battery entry, fresh commit Oct 2026, doc-tracked (docs/dichotomy-rebase-honest-audit.md); no importers expected for canaries"
},
{
"module": "canary_tui_drift",
"last_commit": "2026-10-08",
"importers": [],
"verdict": "canary battery entry, fresh commit Oct 2026, doc-tracked (docs/dichotomy-rebase-honest-audit.md); no importers expected for canaries"
},
{
"module": "canary_tui_transport",
"last_commit": "2026-10-04",
"importers": [],
"verdict": "canary battery entry, fresh commit Oct 2026, doc-tracked (docs/dichotomy-rebase-honest-audit.md); no importers expected for canaries"
},
{
"module": "canary_unbounded",
"last_commit": "2026-10-08",
"importers": [],
"verdict": "canary battery entry, fresh commit Oct 2026, doc-tracked (docs/dichotomy-rebase-honest-audit.md); no importers expected for canaries"
}
],
"superseded": [],
"dead": [],
"broken_but_needed": [],
"notes": [
"All 85 *_loader modules are ctypes FFI bridges to src/rust crates \u2014 they ARE the Python->Rust successor mechanism, not Python legacy; every one has live importers and was touched within 30 days.",
"No module in the 117 is superseded: loaders supersede pure-Python cores; canaries have no successor beyond the newer canary_p0_04/p1_* battery which does not yet cover the stale 13.",
"BROKEN-BUT-NEEDED is empty: witness run proved import liveness; static AST check found only guarded optional deps (aaak, pynput/faster_whisper) and runtime-path-injected shared/ + src/rust/evidence_provenance/ep_loader modules that resolve under the boot chain.",
"The 13 stale canaries fail the recency test but pass the doc-need test (docs/pipeline-prd.md lines ~57-58 enumerate them as the required battery; scripts/structural_coverage_audit.py:131-139 inventories them). Removal requires a docs+audit-script update first.",
"Zero-importer is normal for canaries/CLIs (executed, not imported); recency + doc tracking used as liveness proxy instead."
]
}
@@ -0,0 +1,46 @@
# Tooling-Pile Sort — 2026-10-24 witness run (Gitea #2598 item 4)
Lens: the ~117 tooling-pattern modules (`live_pure_tooling` in research/witness-run/witness-2026-10-24.json).
Method: import-graph first (grep across src/, tui/, scripts/, tests/, shared/, bridge/, interop/, Makefile, docs),
then git recency (cutoff 2026-09-25), then static AST import-resolution. Read-only elsewhere.
## Counts
| Pile | Count |
|---|---|
| LEGIT-TOOLING | 104 |
| — of which STALE-WATCH canaries | 13 |
| SUPERSEDED | 0 |
| DEAD | 0 |
| BROKEN-BUT-NEEDED | 0 |
## 1. LEGIT-TOOLING (104)
- **85 `*_loader.py`** — every one is a ctypes FFI bridge to a crate in `src/rust/` (e.g. `ac_loader` → `src/rust/attention_capsule`). They ARE the Python→Rust successor mechanism, not Python legacy. All have live importers (core modules, `tests/test_*_port.py`, `scripts/check_*_parity.py` truth generators) and commits Oct 4–15, 2026. Examples: `cc_loader` (23 importers), `ms_loader` (10, incl. `henxels_checks.py`), `sm_loader` (imported by `src/rust/iteration/src/lib.rs` test path).
- **3 CLIs/probes** — `builder_bridge_cli` (2026-10-21; `scripts/gen_builder_bridge_cli_truth.py`, `structural_coverage_audit.py`), `construction_authority_cli` (imported by `tests/test_construction_authority_drills.py`), `evidence_probe` (8 importers incl. `scripts/research_stage_executor.py`).
- **16 fresh canaries** (commit ≥ 2026-09-25) — `canary_native` is imported by `src/rune/spine.py` and `scripts/scaffold_projection.py`; the rest are the doc-tracked battery (below) with October commits (`canary_gitea_automation` same-day, 2026-10-25). Zero importers is expected — canaries execute, they aren't imported.
## 2. STALE-WATCH subset (13, still LEGIT per the doc-need rule)
`canary_cognitive_strata`, `canary_context_broker`, `canary_conversation_state`, `canary_gitea_delegation`, `canary_gitea_pipeline`, `canary_memory_synthesis`, `canary_orchestration_foundation`, `canary_p0_05`, `canary_p0_07`, `canary_p1_03`, `canary_resource_authority`, `canary_schema_migration`, `canary_surface_parity`.
Zero importers + no commits since Sept 3–12 (45–55 days), BUT: `docs/pipeline-prd.md` (~lines 57–58) enumerates them as the required canary battery, `scripts/structural_coverage_audit.py:131-139` inventories them, and they import modules of `src/rune/orchestration/` that are actively developed (commits within 30 days). Not dead — but they are stale guards over live code and the newer `canary_p0_04` / `canary_p1_*` battery doesn't yet cover them. Any removal must first update pipeline-prd.md + structural_coverage_audit.py; otherwise recommend consolidation into the newer battery.
## 3. SUPERSEDED (0)
Loaders supersede pure-Python cores rather than being superseded; no canary/CLI has a Go/Rust replacement — the newer canary battery is complementary, not substituting.
## 4. DEAD (0)
Nothing meets the triple test: everything has importers, sub-30-day commits, or explicit doc/audit-script need.
## 5. BROKEN-BUT-NEEDED (0)
Witness run proved import liveness. Static AST check across all 117 found only:
- guarded optional deps (`aaak` in warm_router.py:217 / memory_store.py:445 — try/except fallback; `pynput`/`faster_whisper` in voicebox),
- runtime path-injected modules (`shared/query_memory.py`, `shared/build_pipeline_policy.py`, `shared/gitea_auth.py`, `src/rust/evidence_provenance/ep_loader.py`) that resolve under the boot chain.
No unguarded missing imports. Machine-verified import failure would need the venv (not available in this probe); the witness run covers that.
## Headline
The "tooling pile" is almost entirely load-bearing FFI glue: 85 of 117 modules are the ctypes bridge layer the Rust port depends on. Nothing is deletable today; the only actionable item is consolidating the 13 stale September canaries into the October battery.
@@ -0,0 +1,412 @@
{
"method": "import-graph over 1412 py files (src,tui,scripts,tests,verification) + twin-name match vs src/go (101) and src/rust (103) + launcher/delegation spot-checks; witness execution data as liveness ground truth",
"counts": {
"total": 87,
"legit_python": 10,
"superseded": 46,
"dead": 1,
"needs_a_go_side": 30
},
"pile1_legit_python": {
"build_attestation": {
"lines": 145,
"evidence": "0 importers; phase-0C paired-build attestation tooling, invoked by build pipeline (scripts/build_paired.sh), not the runtime."
},
"drift-ledger": {
"lines": 355,
"evidence": "0 importers; standalone drift-protection CLI (python -m rune.drift-ledger) \u2014 one-off project tooling."
},
"factory_protocol": {
"lines": 518,
"evidence": "0 live importers; python parity twin/oracle of the factory kernel (go/factory) kept for parity harness \u2014 spec oracle."
},
"ffi_query_memory_smoke": {
"lines": 183,
"evidence": "0 importers; FFI smoke test proving the Rust cdylib consumer shape \u2014 test artifact."
},
"inference_paths": {
"lines": 95,
"evidence": "imported only by scripts/gen_inference_paths_truth.py + tests/test_inference_paths.py \u2014 truth-generator tooling (go/inference-paths is the census successor)."
},
"live_proof": {
"lines": 858,
"evidence": "0 importers; spec-1.3 proof harness CLI (python -m rune.live_proof, scripts/admit_wave_live_proof.py) \u2014 verification tooling."
},
"release_notes": {
"lines": 446,
"evidence": "0 importers; evidence-derived Gitea release-history generator \u2014 release tooling only."
},
"rune_graphic": {
"lines": 145,
"evidence": "0 importers; generates the spinning-diamond PNG frames \u2014 asset generation tooling."
},
"tui_backlog": {
"lines": 125,
"evidence": "0 importers; durable TUI upgrade backlog document-as-module (SCAFFOLD-TRANCHE-007) \u2014 data/doc, no serving logic."
},
"tui_validator": {
"lines": 514,
"evidence": "1 importer (canary_native only); pre-flight Textual CSS/widget checks for the python-era TUI \u2014 dev tooling."
}
},
"pile2_superseded": {
"auto_context": {
"lines": 357,
"evidence": "imported only by rune_tui; per-message memory recall hook \u2014 Go face auto-context path."
},
"build_mode": {
"lines": 521,
"evidence": "0 live importers; go/build-mode twin."
},
"builder_bridge": {
"lines": 326,
"evidence": "0 live importers; go/builder-bridge twin."
},
"chat_bubble": {
"lines": 401,
"evidence": "imported only by rune_tui; styled bubbles served by Go face (tui/chatpane)."
},
"construction_interface": {
"lines": 1325,
"evidence": "0 live importers; rust/construction_interface twin (rust-kernel-wired class)."
},
"core_server": {
"lines": 254,
"evidence": "0 live importers; go/core-server twin (HTTP sidecar)."
},
"cron_engine": {
"lines": 440,
"evidence": "imported only by rune_tui; go/cron-engine twin is the successor."
},
"elastic_cognition": {
"lines": 774,
"evidence": "0 live importers; go/elastic-cognition twin."
},
"event_envelope": {
"lines": 303,
"evidence": "0 live importers; go/event-envelope twin."
},
"event_wait": {
"lines": 518,
"evidence": "0 live importers; go/event-wait twin."
},
"failure_recovery": {
"lines": 108,
"evidence": "0 live importers; rust/failure_recovery twin."
},
"graph_projection": {
"lines": 153,
"evidence": "0 live importers; rust/graph_projection twin."
},
"growth_delta": {
"lines": 520,
"evidence": "imported only by rune_tui; growth analytics rendered by Go face."
},
"hook_engine": {
"lines": 860,
"evidence": "imported by rune_tui + truth-gen script; go/hook-engine twin."
},
"impact_preview": {
"lines": 78,
"evidence": "0 live importers; rust/impact_preview twin."
},
"memory_continuity": {
"lines": 622,
"evidence": "0 live importers; go/memory-continuity twin."
},
"message_queue": {
"lines": 457,
"evidence": "0 live importers; go/message-queue twin."
},
"message_widgets": {
"lines": 104,
"evidence": "imported only by rune_tui; typed tool-work widgets \u2014 Go face serves (tui/)."
},
"model_connect": {
"lines": 471,
"evidence": "imported by provider_registry + rune_tui; go/model-connect twin."
},
"model_profiles": {
"lines": 438,
"evidence": "imported by rune_tui + rune_operator; go/model-profiles twin."
},
"night_mode": {
"lines": 421,
"evidence": "imported only by rune_tui; go/night-mode twin."
},
"parallel_processor": {
"lines": 194,
"evidence": "imported only by rune_tui; go/parallel-processor twin."
},
"persona_definition": {
"lines": 210,
"evidence": "imported by rune_tui/token_optimizer/parity scripts; go/persona-definition twin."
},
"persona_gate": {
"lines": 777,
"evidence": "imported only by rune_tui; go/persona-gate twin."
},
"pins": {
"lines": 323,
"evidence": "imported only by rune_tui; go/pins twin + tui/cmd/rune-tui/pins.go implements pinboard natively."
},
"programme_gates": {
"lines": 85,
"evidence": "0 live importers; rust/programme_gates twin."
},
"provider_budget": {
"lines": 357,
"evidence": "imported only by rune_tui; go/provider-budget twin."
},
"provider_context": {
"lines": 48,
"evidence": "0 live importers; go/provider-context twin."
},
"provider_registry": {
"lines": 781,
"evidence": "imported by rune_tui/standardized_providers/truth scripts; go/provider-registry twin."
},
"recall_router": {
"lines": 165,
"evidence": "0 live importers; go/recall-router twin."
},
"route_risk_flags": {
"lines": 37,
"evidence": "0 live importers; rust/route_risk_flags twin."
},
"rune_spinner": {
"lines": 130,
"evidence": "0 live importers; rust/rune_spinner twin."
},
"rune_tui": {
"lines": 4180,
"evidence": "4180L python Textual face; launcher boot ladder (~/.local/bin/rune) execs the deployed Go face tui/cmd/rune-tui/main.go (3742L main) \u2014 python TUI is only the documented fallback."
},
"skills_engine": {
"lines": 569,
"evidence": "imported only by tests/test_skills_engine.py; go/skills-engine twin."
},
"sleep_time": {
"lines": 1097,
"evidence": "rune_tui/canary/test importers only; go/sleep-time twin."
},
"stage_guards": {
"lines": 350,
"evidence": "0 live importers; go/stage-guards twin."
},
"standardized_providers": {
"lines": 386,
"evidence": "imported only by provider_registry; go/standardized-providers twin."
},
"structural_migration": {
"lines": 77,
"evidence": "truth-gen script importer only; rust/structural_migration twin."
},
"task_persist": {
"lines": 269,
"evidence": "imported only by rune_tui; rust/task_persist twin (rust-kernel-wired class)."
},
"tiered_executor": {
"lines": 291,
"evidence": "0 live importers; go/tiered-executor twin."
},
"tui_transport": {
"lines": 108,
"evidence": "imported only by rune_tui; transport seam replaced by Go face's own transport (tui/)."
},
"voice_injector": {
"lines": 138,
"evidence": "truth-gen script importer only; go/voice-injector twin."
},
"voicebox": {
"lines": 236,
"evidence": "canary/script importers only; go/voicebox twin."
},
"wait_guard": {
"lines": 116,
"evidence": "0 live importers; rust/wait_guard twin."
},
"wait_guard_corpus": {
"lines": 46,
"evidence": "0 live importers; rust/wait_guard_corpus twin."
},
"work_manager": {
"lines": 594,
"evidence": "imported only by rune_tui; go/work-manager twin."
}
},
"pile3_dead": {
"alpha": {
"lines": 1,
"evidence": "1 line (marker stub), 0 importers anywhere in src/tui/scripts/tests."
}
},
"pile4_needs_a_go_side": {
"abstraction": {
"lines": 768,
"evidence": "768L episode->knowledge pipeline; imported by memory_consolidation, memory_selector, rune_tui; go/abstraction twin not wired."
},
"adaptive_router": {
"lines": 625,
"evidence": "625L route authority; dynamically imported by rune_operator (line 519); go/adaptive-router twin not wired."
},
"audit_ledger": {
"lines": 540,
"evidence": "540L HMAC-chained promotion ledger; imported by curator (live serving path). No go twin (rust parity probe referenced in scripts but no crate)."
},
"construction_admission": {
"lines": 311,
"evidence": "311L headless scaffolding entry (CLI); DEP07 chain. No twin."
},
"construction_authority": {
"lines": 2012,
"evidence": "2012L agent-mutation-side construction authority, CLI-driven (no static importers by design); DEP07 chain head. No twin."
},
"context_broker": {
"lines": 870,
"evidence": "870L prompt-context assembly authority; imported by memory_continuity, memory_selector; on witness boot chain. go/context-broker twin not wired."
},
"drift_compression": {
"lines": 323,
"evidence": "323L drift-aware compression; imported by memory_consolidation. No twin."
},
"effect_mediation": {
"lines": 558,
"evidence": "CORE: 558L THE enforced effect-mediation registry (DEP06); on witness boot chain. No twin."
},
"event_bus": {
"lines": 329,
"evidence": "329L persistent event journal; imported by rune_operator + rune_tui. go/event-bus twin not wired."
},
"failure_auto_filer": {
"lines": 527,
"evidence": "527L failures->forge issues; imported by rune_operator. No twin."
},
"identity_anchors": {
"lines": 575,
"evidence": "575L identity/drift prevention, agent-invoked CLI; no twin."
},
"local_tool_runtime": {
"lines": 1027,
"evidence": "CORE: 1027L Ollama transport + bounded tool loop; imported by rune_operator; on witness boot chain. go/local-tool-runtime twin not wired."
},
"memory_consolidation": {
"lines": 770,
"evidence": "770L consolidation; imported by memory_selector, sleep_time, canaries; go/memory-consolidation twin not wired."
},
"memory_env": {
"lines": 72,
"evidence": "72L portable memory-path resolution; 57 importers across the entire runtime; on witness boot chain. No twin \u2014 trivial port, do first."
},
"memory_store": {
"lines": 2384,
"evidence": "CORE: 2384L memory store; 12 live importers (tool_executor, curator, memory_consolidation/continuity/selector, warm_router, rune_tui...). go/memory-store-deep not wired."
},
"mode_gate": {
"lines": 198,
"evidence": "CORE: 198L THE mode-matrix authority for the dispatch door; imported by provider_registry; on witness boot chain. No twin."
},
"model_registry": {
"lines": 253,
"evidence": "253L resource ladder; 11 live importers (memory_store, sleep_time, model_connect, provider_registry...). go/model-registry twin not wired."
},
"owner_envelope": {
"lines": 378,
"evidence": "378L DEP05 authenticated one-use owner envelopes; agent-invoked; no twin."
},
"paths": {
"lines": 149,
"evidence": "149L centralized path resolution; on witness boot chain; go/paths twin not wired."
},
"rune_operator": {
"lines": 2985,
"evidence": "CORE: 2985L operator loop; imported by capability_broker, core_server, tui_transport, rune_tui; orchestrates local_tool_runtime/tool_protocol/model_routing. go/operator twin not wired."
},
"runtime_attestation": {
"lines": 268,
"evidence": "268L authority-map \u00a75 attestation; agent-invoked CLI; no twin."
},
"safety_override": {
"lines": 649,
"evidence": "649L adaptive safety override; imported by rune_operator + rune_tui. No twin \u2014 safety-critical."
},
"secret_store": {
"lines": 350,
"evidence": "350L secret material store; imported by provider_registry (B-APIKEY-MIGRATION). No twin."
},
"self_change_qualification": {
"lines": 503,
"evidence": "503L DEP07 self-change qualification gate; agent-invoked; no twin."
},
"self_transform_pipeline": {
"lines": 853,
"evidence": "853L spec-6.2 self-transformation pipeline; 0 static importers (agent-invoked); no twin."
},
"tier_manager": {
"lines": 300,
"evidence": "300L tiered storage manager; imported by memory_store. No twin."
},
"tool_executor": {
"lines": 1866,
"evidence": "CORE: 1866L file/tool execution; imported by capability_broker, provider_registry, rune_operator, tool_protocol; on witness boot chain. go/tool-executor twin not wired."
},
"tool_protocol": {
"lines": 481,
"evidence": "CORE: 481L canonical tool-call protocol; imported by provider_registry, tool_executor; on witness boot chain. go/tool-protocol twin not wired."
},
"warm_neuron_store": {
"lines": 122,
"evidence": "122L THE store-based warm-neuron reader; imported by memory_selector, claim_audit, memory_consolidation, rune_tui. rust/warm_neuron_store twin not wired into these callers."
},
"warm_router": {
"lines": 1521,
"evidence": "1521L warm-neuron routing; imported by curator, rune_tui + 10 scripts; on witness boot chain. go/warm-router twin not wired."
}
},
"pile4_sizing": {
"modules": 30,
"total_lines": 22567,
"top5_by_lines": [
[
2985,
"rune_operator"
],
[
2384,
"memory_store"
],
[
2012,
"construction_authority"
],
[
1866,
"tool_executor"
],
[
1521,
"warm_router"
]
],
"boot_chain_critical": [
"memory_env",
"mode_gate",
"effect_mediation",
"paths",
"tool_protocol",
"tool_executor",
"local_tool_runtime",
"context_broker",
"warm_router"
],
"tier1_trivial_ports": [
"memory_env",
"paths",
"mode_gate",
"model_registry",
"tool_protocol"
]
},
"missing": [],
"extra": []
}
@@ -0,0 +1,81 @@
# Triage of the 87 live-pure-core python modules — 2026-10-24
Companion of `research/witness-run/witness-2026-10-24.json` (Gitea #2586, classification half).
Method: import-graph over 1412 py files (src/, tui/, scripts/, tests/, verification/), twin-name
match against `src/go/` (101 modules) and `src/rust/` (103 crates), plus delegation spot-checks
(launcher boot ladder, ctypes loaders, `rune_operator` dynamic imports). Witness execution data is
liveness ground truth. Machine-readable detail: `triage-2026-10-24.json`.
## Headline
**Two weeks of rebasing was NOT adequate.** 30 of 87 live-executing python modules (22,567 lines)
still carry the serving path with no live native twin — including 9 modules on the witness boot
chain itself and the entire governed self-change authority chain.
## Counts
| Pile | Count | Lines |
|---|---|---|
| 1 LEGIT-PYTHON (keep) | 10 | 3,560 |
| 2 SUPERSEDED (delete-able) | 46 | 17,697 |
| 3 DEAD | 1 | 1 |
| 4 NEEDS-A-GO-SIDE (rebase scope) | **30** | **22,567** |
## Pile 4 — the honest remaining rebase scope (ranked by serving-path weight)
**Tier 0 — witness boot chain (product cannot boot without these):**
`memory_env` (72L; 57 importers, path resolution — trivial port, do first), `mode_gate` (198L,
the ONE dispatch-door mode authority, no twin), `effect_mediation` (558L, THE enforced
effect-mediation registry, no twin), `paths` (149L), `tool_protocol` (481L, go twin unwired),
`tool_executor` (1,866L; imported by capability_broker/provider_registry/rune_operator; go twin
unwired), `local_tool_runtime` (1,027L Ollama transport + bounded tool loop), `context_broker`
(870L prompt-assembly authority), `warm_router` (1,521L).
**Tier 1 — operator/agent loop:** `rune_operator` (2,985L — largest single module; imported by
capability_broker, core_server, tui_transport; orchestrates local_tool_runtime/tool_protocol/
model_routing; go/operator twin not wired), `adaptive_router` (625L, dynamic-imported by
rune_operator:519), `secret_store` (350L), `audit_ledger` (540L, HMAC-chained ledger imported by
live `curator`), `model_registry` (253L, 11 live importers), `failure_auto_filer` (527L),
`safety_override` (649L, safety-critical, no twin), `event_bus` (329L).
**Tier 2 — memory subsystem:** `memory_store` (2,384L, 12 live importers — the heaviest single
dependency in the graph; go/memory-store-deep unwired), `memory_consolidation` (770L),
`abstraction` (768L), `tier_manager` (300L), `drift_compression` (323L), `warm_neuron_store`
(122L; rust twin exists but callers still read python).
**Tier 3 — governed self-change chain (DEP05/06/07, agent-invoked CLIs, zero twins):
`construction_authority` (2,012L), `self_transform_pipeline` (853L), `self_change_qualification`
(503L), `construction_admission` (311L), `identity_anchors` (575L), `owner_envelope` (378L),
`runtime_attestation` (268L).**
Top 5 by lines: rune_operator 2,985 · memory_store 2,384 · construction_authority 2,012 ·
tool_executor 1,866 · warm_router 1,521.
## Pile 1 — LEGIT-PYTHON (10)
`build_attestation` (paired-build tooling), `live_proof` (proof-harness CLI), `release_notes`
(release tooling), `ffi_query_memory_smoke` (FFI smoke test), `tui_validator` (canary-only
pre-flight checks), `tui_backlog` (doc-as-module), `drift-ledger` (standalone CLI),
`rune_graphic` (asset generator), `inference_paths` (truth-generator, scripts/tests only),
`factory_protocol` (parity oracle for go/factory).
## Pile 2 — SUPERSEDED (46)
Two clusters, distinct successor evidence:
- **TUI cluster (36)**: the launcher boot ladder (`~/.local/bin/rune`) execs the deployed Go face
`tui/cmd/rune-tui/main.go` (3,742-line main); the python Textual face is only the documented
fallback. Delete-able: `rune_tui` (4,180L), `chat_bubble`, `message_widgets`, `auto_context`,
`cron_engine`, `growth_delta`, `night_mode`, `parallel_processor`, `persona_gate`, `pins`,
`provider_budget`, `task_persist`, `work_manager`, `tui_transport`, `model_profiles`,
`hook_engine`, `model_connect`, `provider_registry`, `standardized_providers`,
`persona_definition`, `event_envelope`, `message_queue`, `voicebox`, `voice_injector`,
`tiered_executor`, `elastic_cognition`, `recall_router`, `provider_context`, `core_server`,
`builder_bridge`, `build_mode`, `skills_engine`, `sleep_time`, `memory_continuity`,
`stage_guards`, `event_wait` — each with a named `src/go/` (or rust) twin.
- **Rust-twin cluster (10)**: zero live importers, rust crate under the same name serves
(rust-kernel-wired class): `construction_interface`, `failure_recovery`, `graph_projection`,
`impact_preview`, `programme_gates`, `route_risk_flags`, `rune_spinner`, `structural_migration`,
`wait_guard`, `wait_guard_corpus`.
Caveat: python-TUI-cluster deletions should wait on removing the launcher's step-4 python
fallback ("honest transition").
## Pile 3 — DEAD (1)
`alpha` — 1-line stub, zero importers across all trees.
@@ -0,0 +1,257 @@
{
"generated": "2026-10-25",
"issue": "Gitea #2586",
"ruling": "receipts prove written; serving-path evidence earns wired",
"bars": [
"bar1 serving-path trace (live face boot + real 27B turn + task dispatch, via journal/witness/response envelope, no serving-code modification)",
"bar2 fail-loud live probe RUNE_*_BIN=/nonexistent -> typed RuntimeError",
"bar3 parity-at-load against the live engine, same session as the proof turn",
"bar4 no-oracle: python body reachable only via explicit RUNE_*_ENGINE=python escape, grep-verified + test-asserted"
],
"live_receipts": {
"core_status": "http://127.0.0.1:8376/v1/status OK, active_model qwen2.5:7b, governor live",
"turn_27b": "POST /v1/chat/completions 2026-10-25T04:43Z model hf.co/DavidAU/Qwen3.6-27B-Fable-Fusion...:IQ3_M -> REBASE-PROOF-SERVING-OK, envelope core=go success=true",
"journal": "memory/journal/events.jsonl 45152 events, fresh task.outcome/task.failed 2026-10-25T04:29Z",
"witness": "research/witness-run/witness-2026-10-24.json boot_chain_executed (15 serving-chain modules)",
"bar2_probes": "12/12 RUNE_*_BIN=/nonexistent -> typed RuntimeError (raw results inline per row)",
"batteries": "go test ./... -short -count=1 per facade module, 2026-10-25",
"parallel": "Gitea #2596 second-pass verdicts feed bar-3 re-verification; reconcile on landing"
},
"summary": {
"WIRED": 5,
"WIRED_WITH_CAVEAT": 7,
"REPAIR_SCOPED": 4
},
"rows": [
{
"row": "model-routing",
"bar1": "PASS",
"bar2": "PASS",
"bar3": "PASS",
"bar4": "PASS",
"verdict": "WIRED",
"evidence": {
"bar1": "witness boot_chain (model_routing) + live :8376 turn envelope core=go 2026-10-25T04:43Z",
"bar2": "RuntimeError on RUNE_MODEL_ROUTING_BIN=/nonexistent (live probe 2026-10-25)",
"bar3": "go test ./... -short src/go/model-routing ok",
"bar4": "grep: no `except RuntimeError` fallback; escape only RUNE_MODEL_ROUTING_ENGINE=python"
}
},
{
"row": "event-bus",
"bar1": "PASS",
"bar2": "PASS",
"bar3": "PASS",
"bar4": "PASS",
"verdict": "WIRED",
"evidence": {
"bar1": "live journal memory/journal/events.jsonl task.outcome/task.failed streaming through go bus (45152 events, fresh 2026-10-25T04:29Z)",
"bar2": "RuntimeError on RUNE_EVENT_BUS_BIN=/nonexistent",
"bar3": "go test ./... -short src/go/event-bus ok",
"bar4": "grep clean; escape RUNE_EVENT_BUS_ENGINE=python"
}
},
{
"row": "reconcile-tools",
"bar1": "PASS",
"bar2": "PASS",
"bar3": "PASS",
"bar4": "PASS",
"verdict": "WIRED",
"evidence": {
"bar1": "delegates through model_routing._go_eval (house shape); model_routing in witness boot_chain",
"bar2": "RuntimeError via reconcile_tools_support with RUNE_MODEL_ROUTING_BIN=/nonexistent",
"bar3": "src/go/model-routing battery ok (same engine surface)",
"bar4": "python body _reconcile_tools_support_python escape-only"
}
},
{
"row": "tool-executor",
"bar1": "PASS",
"bar2": "PASS",
"bar3": "CONDITIONAL",
"bar4": "PASS",
"verdict": "WIRED-WITH-CAVEAT",
"evidence": {
"bar1": "witness boot_chain (tool_executor, local_tool_runtime) + live dispatch",
"bar2": "RuntimeError on RUNE_TOOL_EXECUTOR_BIN=/nonexistent via _go_execute_tool",
"bar3": "lifecycle parity PASS; fsops parity INDETERMINATE (check_tool_executor_fsops_parity.py refuses: project-identity guard 'unknown project proj-alpha' \u2014 verify with producer env, then repair if real)",
"bar4": "grep clean; escape RUNE_TOOL_EXECUTOR_ENGINE=python"
},
"repair_scope": "RE-RUN scripts/check_tool_executor_fsops_parity.py from the producer invocation context; if drift is real, fix src/go/tool-executor fsops surface red-first"
},
{
"row": "trust-and-sovereignty",
"bar1": "PASS",
"bar2": "N/A-RUST",
"bar3": "FAIL",
"bar4": "PASS",
"verdict": "REPAIR-SCOPED",
"evidence": {
"bar1": "cognition_core rust broker live (_cc_probe resolves cc_loader; rust-kernel-wired per witness: 79 modules)",
"bar2": "rust kernel authority \u2014 RUNE_*_BIN probe class does not apply (no subprocess binary seam); cc probe live PASS",
"bar3": "FAIL: scripts/check_safety_override_parity.py = 2000/2065 (65 fuzz failures, seed 2479)",
"bar4": "engine selection defaults to rust kernel; python body escape-only"
},
"repair_scope": "RED-FIRST: reproduce the 65 failing fuzz rows in check_safety_override_parity.py, diff python oracle vs rust kernel on those seeds, fix the kernel (src/rust/safety_override) or the oracle (whichever diverges from frozen truth), re-run to 2065/2065"
},
{
"row": "file-change-safety",
"bar1": "CONDITIONAL",
"bar2": "PASS",
"bar3": "INDETERMINATE",
"bar4": "PASS",
"verdict": "REPAIR-SCOPED",
"evidence": {
"bar1": "fsops executes through tool-executor go engine on dispatch (boot_chain: local_tool_runtime); no dedicated in-turn trace",
"bar2": "RuntimeError on RUNE_TOOL_EXECUTOR_BIN=/nonexistent (fsops rides the tool-executor seam)",
"bar3": "check_tool_executor_fsops_parity.py errors before comparison (project-identity refusal)",
"bar4": "grep clean"
},
"repair_scope": "same as tool-executor row: resolve the fsops parity harness invocation, land a green 2065-style run, then re-verdict"
},
{
"row": "openai_compat_transport",
"bar1": "CONDITIONAL",
"bar2": "PASS",
"bar3": "FAIL",
"bar4": "PASS",
"verdict": "REPAIR-SCOPED",
"evidence": {
"bar1": "engine executes live via parity-battery CLI; not in witness boot_chain (dispatch-time only)",
"bar2": "RuntimeError on RUNE_OPENAI_COMPAT_TRANSPORT_BIN=/nonexistent",
"bar3": "FAIL: scripts/gen_openai_compat_transport_truth.py live chat row raises 'provider returned a non-object response' (2026-10-25)",
"bar4": "grep clean; escape RUNE_OPENAI_COMPAT_TRANSPORT_ENGINE=python"
},
"repair_scope": "red-first: make the go chat surface either parse or typedly reject the provider's non-object response \u2014 the python oracle already raises; the go engine must match that contract, then regenerate truth byte-identical"
},
{
"row": "forge_client",
"bar1": "CONDITIONAL",
"bar2": "PASS",
"bar3": "FAIL",
"bar4": "PASS",
"verdict": "REPAIR-SCOPED",
"evidence": {
"bar1": "engine executes live via gen_forge_client_truth.py CLI run (49 rows regenerated); not in witness boot_chain",
"bar2": "RuntimeError on RUNE_FORGE_CLIENT_BIN=/nonexistent",
"bar3": "FAIL: regenerated truth diverges from frozen (43+/303-): owner default lost ('repos//repo' vs 'repos/owner/repo') and HTTP-capture rows return result:null live. NOTE: frozen truth may have been produced with FORGE_* env set \u2014 reproduce with producer env before repairing",
"bar4": "grep clean; escape RUNE_FORGE_CLIENT_ENGINE=python"
},
"repair_scope": "reproduce gen_forge_client_truth.py with the producer env matrix; if drift survives, fix owner-default propagation + request-capture in src/go/forge_client, re-freeze"
},
{
"row": "microkernel",
"bar1": "PASS",
"bar2": "PASS",
"bar3": "PASS",
"bar4": "PASS",
"verdict": "WIRED",
"evidence": {
"bar1": "witness boot_chain (microkernel) + live serving-chain boot",
"bar2": "RuntimeError on RUNE_MICROKERNEL_BIN=/nonexistent via _go_parity_op",
"bar3": "go test ./... -short src/go/microkernel ok",
"bar4": "grep clean; escape RUNE_MICROKERNEL_ENGINE=python"
}
},
{
"row": "goal",
"bar1": "CONDITIONAL",
"bar2": "PASS",
"bar3": "PASS",
"bar4": "PASS",
"verdict": "WIRED-WITH-CAVEAT",
"evidence": {
"bar1": "engine executes live via battery CLI (go test src/go/goal ok 4.0s); not traced inside the 27B turn (dispatch-time surface)",
"bar2": "RuntimeError on RUNE_GOAL_BIN=/nonexistent",
"bar3": "go test ./... -short src/go/goal ok",
"bar4": "grep clean; escape RUNE_GOAL_ENGINE=python"
}
},
{
"row": "cache",
"bar1": "CONDITIONAL",
"bar2": "PASS",
"bar3": "PASS",
"bar4": "PASS",
"verdict": "WIRED-WITH-CAVEAT",
"evidence": {
"bar1": "engine executes live via battery CLI (eval-surface flush fix 53ab710e verified green); dispatch-time surface",
"bar2": "RuntimeError on RUNE_CACHE_BIN=/nonexistent",
"bar3": "go test ./... -short src/go/cache ok 0.95s",
"bar4": "grep clean; escape RUNE_CACHE_ENGINE=python"
}
},
{
"row": "message_queue",
"bar1": "CONDITIONAL",
"bar2": "PASS",
"bar3": "NO-BATTERY",
"bar4": "PASS",
"verdict": "REPAIR-SCOPED",
"evidence": {
"bar1": "engine executes via _go_eval on dispatch; no in-turn trace",
"bar2": "RuntimeError on RUNE_MESSAGE_QUEUE_BIN=/nonexistent",
"bar3": "NO PARITY BATTERY EXISTS: src/go/message-queue has no test files; no check_*_parity.py maps to it",
"bar4": "grep clean; escape RUNE_MESSAGE_QUEUE_ENGINE=python"
},
"repair_scope": "write the live-parity battery (Go test replaying the python oracle's frozen truth, house pattern of src/go/cache/cache_test.go) and land it green"
},
{
"row": "abstraction",
"bar1": "CONDITIONAL",
"bar2": "PASS",
"bar3": "PASS",
"bar4": "PASS",
"verdict": "WIRED-WITH-CAVEAT",
"evidence": {
"bar1": "engine executes live via battery CLI (0.16s); dispatch-time",
"bar2": "RuntimeError on RUNE_ABSTRACTION_BIN=/nonexistent",
"bar3": "go test ./... -short src/go/abstraction ok",
"bar4": "grep clean; escape RUNE_ABSTRACTION_ENGINE=python"
}
},
{
"row": "auto_context",
"bar1": "CONDITIONAL",
"bar2": "PASS",
"bar3": "PASS",
"bar4": "PASS",
"verdict": "WIRED-WITH-CAVEAT",
"evidence": {
"bar1": "ScriptsDir CWD fix landed on master (84094f4e lineage); engine executes via _go_eval dispatch",
"bar2": "RuntimeError on RUNE_AUTO_CONTEXT_BIN=/nonexistent",
"bar3": "src/go/auto-context compiles green (no in-module battery \u2014 ScriptsDir battery lives in src/go/auto_context main; warm-router parity green per 84094f4e)",
"bar4": "grep clean; escape RUNE_AUTO_CONTEXT_ENGINE=python"
}
},
{
"row": "voicebox",
"bar1": "CONDITIONAL",
"bar2": "PASS",
"bar3": "PASS",
"bar4": "PASS",
"verdict": "WIRED-WITH-CAVEAT",
"evidence": {
"bar1": "engine executes live via battery CLI; dispatch-time",
"bar2": "RuntimeError on RUNE_VOICEBOX_BIN=/nonexistent",
"bar3": "go test ./... -short src/go/voicebox ok",
"bar4": "grep clean; escape RUNE_VOICEBOX_ENGINE=python"
}
},
{
"row": "paths",
"bar1": "PASS",
"bar2": "PASS",
"bar3": "PASS",
"bar4": "PASS",
"verdict": "WIRED",
"evidence": {
"bar1": "witness boot_chain (paths) + live serving-chain boot",
"bar2": "RuntimeError on RUNE_PATHS_BIN=/nonexistent",
"bar3": "go test ./... -short src/go/paths ok",
"bar4": "grep clean; escape RUNE_PATHS_ENGINE=python"
}
}
]
}
@@ -0,0 +1,82 @@
# REBASE-PROOF verdict table — Gitea #2586 (2026-10-25 run against the 2026-10-24 wave)
Ruling: **receipts prove written; serving-path evidence earns wired.** A row is `WIRED` only on
four-bar evidence; anything less is `WIRED-WITH-CAVEAT` or `REPAIR-SCOPED` with the repair scoped
in-row (not landed — this is an evidence wave).
## Bars
1. **BAR-1 serving-path trace** — the facade's Go engine executes in the live face boot, a real 27B
turn, and a task dispatch, proven via existing logs only: `memory/journal/events.jsonl`
(45,152 events, fresh task.outcome/task.failed at 2026-10-25T04:29Z),
`research/witness-run/witness-2026-10-24.json` `boot_chain_executed` (15 serving-chain modules),
and the live turn response envelope (`core: go, success: true`). No serving code was modified.
2. **BAR-2 fail-loud live probe** — `RUNE_*_BIN=/nonexistent` per facade → typed `RuntimeError`.
12/12 probed PASS live on 2026-10-25 (model-routing was pre-proven; re-proven anyway).
3. **BAR-3 parity-at-load** — parity battery against the live engine in the same session
(`go test ./... -short -count=1` per facade module + scripts/check_*_parity.py).
4. **BAR-4 no-oracle** — python body reachable ONLY via explicit `RUNE_*_ENGINE=python`; grep found
zero `except RuntimeError` fallback catches across all 12 facades; asserted in
`tests/test_rebase_proof_no_oracle.py`.
## Live receipts (2026-10-25)
- Core live on 127.0.0.1:8376 (`/v1/status`: governor + providers live).
- Real 27B turn: `hf.co/DavidAU/Qwen3.6-27B-Fable-Fusion-...:IQ3_M` →
`"REBASE-PROOF-SERVING-OK"`, envelope `core: go`.
- Task dispatch: fresh `task.outcome` / `task.failed` events streaming in the journal.
- #2596 second-pass verdicts (parallel wave) reconcile into bar-3 at landing.
## Table
| row | bar1 | bar2 | bar3 | bar4 | verdict |
|---|---|---|---|---|---|
| model-routing | PASS | PASS | PASS | PASS | **WIRED** |
| event-bus | PASS | PASS | PASS | PASS | **WIRED** |
| reconcile-tools | PASS | PASS | PASS | PASS | **WIRED** |
| tool-executor | PASS | PASS | CONDITIONAL | PASS | WIRED-WITH-CAVEAT |
| trust-and-sovereignty | PASS | N/A-RUST | **FAIL** | PASS | REPAIR-SCOPED |
| file-change-safety | CONDITIONAL | PASS | INDETERMINATE | PASS | REPAIR-SCOPED |
| openai_compat_transport | CONDITIONAL | PASS | **FAIL** | PASS | REPAIR-SCOPED |
| forge_client | CONDITIONAL | PASS | **FAIL** | PASS | REPAIR-SCOPED |
| microkernel | PASS | PASS | PASS | PASS | **WIRED** |
| goal | CONDITIONAL | PASS | PASS | PASS | WIRED-WITH-CAVEAT |
| cache | CONDITIONAL | PASS | PASS | PASS | WIRED-WITH-CAVEAT |
| message_queue | CONDITIONAL | PASS | **NO-BATTERY** | PASS | REPAIR-SCOPED |
| abstraction | CONDITIONAL | PASS | PASS | PASS | WIRED-WITH-CAVEAT |
| auto_context | CONDITIONAL | PASS | PASS | PASS | WIRED-WITH-CAVEAT |
| voicebox | CONDITIONAL | PASS | PASS | PASS | WIRED-WITH-CAVEAT |
| paths | PASS | PASS | PASS | PASS | **WIRED** |
**Counts: 5 WIRED · 7 WIRED-WITH-CAVEAT · 4 REPAIR-SCOPED.** Full per-bar evidence lives in
`verdict-2026-10-24.json` (same directory).
## Scoped repairs (NOT landed — evidence wave)
1. **trust-and-sovereignty** — `scripts/check_safety_override_parity.py`: 2000/2065 (65 fuzz
failures, seed 2479). Repair: reproduce the 65 rows, diff python oracle vs rust kernel
(src/rust/safety_override), fix the diverging side red-first, re-run to 2065/2065.
2. **forge_client** — `gen_forge_client_truth.py` live regeneration diverges from frozen truth
(43+/303−): owner default lost (`repos//repo` vs `repos/owner/repo`), HTTP-capture rows return
`result: null`. Repair: reproduce with the producer env matrix first (frozen truth may carry
FORGE_* env); if drift survives, fix owner-default propagation + request capture in
src/go/forge_client, re-freeze.
3. **openai_compat_transport** — live chat parity row raises "provider returned a non-object
response". Repair: the go chat surface must parse or typedly reject non-object responses to
match the oracle contract, then regenerate truth byte-identical.
4. **message_queue** — no parity battery exists at all (src/go/message-queue has no test files,
no check_*_parity.py maps to it). Repair: write the live-parity battery (house pattern:
src/go/cache/cache_test.go), land green.
5. **file-change-safety / tool-executor fsops** — `check_tool_executor_fsops_parity.py` refuses to
run (project-identity guard: "unknown project 'proj-alpha'"). Repair: resolve the harness
invocation context, land a green run, re-verdict. (Adjacent finding, outside the 16 rows:
`check_ro_exec_parity.py` is red — 50230B frozen vs 50278B regenerated.)
## Bar-1 caveat (applies to CONDITIONAL rows)
Witness `boot_chain_executed` covers model_routing, microkernel, paths, tool_executor,
local_tool_runtime (and 10 more serving-chain modules). The remaining facades execute their Go
engines live on dispatch (proven by the same-session battery CLI runs against the live engine) but
are not individually traced inside the 27B turn — proving that would require modifying serving
code, which this wave forbids. They keep WIRED-WITH-CAVEAT until #2596-style per-facade in-turn
probes exist.
@@ -0,0 +1,240 @@
{
"method": "sys.setprofile witness over full import graph + serving-chain boot probe; RUNE_HOME isolated",
"numbers": {
"total_py_modules": 296,
"go_facade_served_live": 11,
"rust_kernel_wired_live": 79,
"live_pure_python_total": 204,
" of which tooling/test-pattern": 117,
" of which load-bearing candidates": 87,
"dead_never_executed": 1
},
"boot_chain_executed": [
"__init__",
"attention_capsule",
"behaviour_ledger",
"capability_broker",
"context_broker",
"effect_mediation",
"local_tool_runtime",
"memory_env",
"microkernel",
"mode_gate",
"model_routing",
"paths",
"tool_executor",
"tool_protocol",
"warm_router"
],
"live_pure_core": [
"abstraction",
"adaptive_router",
"alpha",
"audit_ledger",
"auto_context",
"build_attestation",
"build_mode",
"builder_bridge",
"chat_bubble",
"construction_admission",
"construction_authority",
"construction_interface",
"context_broker",
"core_server",
"cron_engine",
"drift-ledger",
"drift_compression",
"effect_mediation",
"elastic_cognition",
"event_bus",
"event_envelope",
"event_wait",
"factory_protocol",
"failure_auto_filer",
"failure_recovery",
"ffi_query_memory_smoke",
"graph_projection",
"growth_delta",
"hook_engine",
"identity_anchors",
"impact_preview",
"inference_paths",
"live_proof",
"local_tool_runtime",
"memory_consolidation",
"memory_continuity",
"memory_env",
"memory_store",
"message_queue",
"message_widgets",
"mode_gate",
"model_connect",
"model_profiles",
"model_registry",
"night_mode",
"owner_envelope",
"parallel_processor",
"paths",
"persona_definition",
"persona_gate",
"pins",
"programme_gates",
"provider_budget",
"provider_context",
"provider_registry",
"recall_router",
"release_notes",
"route_risk_flags",
"rune_graphic",
"rune_operator",
"rune_spinner",
"rune_tui",
"runtime_attestation",
"safety_override",
"secret_store",
"self_change_qualification",
"self_transform_pipeline",
"skills_engine",
"sleep_time",
"stage_guards",
"standardized_providers",
"structural_migration",
"task_persist",
"tier_manager",
"tiered_executor",
"tool_executor",
"tool_protocol",
"tui_backlog",
"tui_transport",
"tui_validator",
"voice_injector",
"voicebox",
"wait_guard",
"wait_guard_corpus",
"warm_neuron_store",
"warm_router",
"work_manager"
],
"live_pure_tooling": [
"ac_loader",
"al_loader",
"bb_loader",
"bh_loader",
"bl_loader",
"broker_loader",
"builder_bridge_cli",
"ca_loader",
"canary_audit_ledger",
"canary_cognitive_strata",
"canary_context_broker",
"canary_conversation_state",
"canary_full_system",
"canary_gitea_automation",
"canary_gitea_delegation",
"canary_gitea_pipeline",
"canary_memory_synthesis",
"canary_native",
"canary_orchestration_foundation",
"canary_p0_04",
"canary_p0_05",
"canary_p0_07",
"canary_p1_01",
"canary_p1_02",
"canary_p1_03",
"canary_p1_06",
"canary_p1_07",
"canary_query_memory",
"canary_resource_authority",
"canary_rune_entry",
"canary_rune_serve",
"canary_schema_migration",
"canary_spine_meta",
"canary_surface_parity",
"canary_tui_drift",
"canary_tui_transport",
"canary_unbounded",
"cb_loader",
"cc_loader",
"ccmd_loader",
"cd_loader",
"cdec_loader",
"cf_loader",
"ci_loader",
"cl_loader",
"cm_loader",
"construction_authority_cli",
"contracts_loader",
"cp_loader",
"cpb_loader",
"cpool_loader",
"cproj_loader",
"cps_loader",
"cs_loader",
"ct_loader",
"dc_loader",
"dcore_loader",
"dl_loader",
"do_loader",
"dr_loader",
"dt_loader",
"ec_loader",
"ee_loader",
"et_loader",
"evidence_probe",
"gm_loader",
"gp2_loader",
"gp_loader",
"gr_loader",
"ic_loader",
"ip_loader",
"it_loader",
"itr_loader",
"lc_loader",
"le_loader",
"me_loader",
"mems_loader",
"mg_loader",
"mr_loader",
"ms_loader",
"mse_loader",
"mt_loader",
"mu_loader",
"ol_loader",
"ou_loader",
"pa_loader",
"pc_loader",
"pr_loader",
"progressive_escalation_loader",
"ps_loader",
"ra_loader",
"rb_loader",
"rp_loader",
"ru_loader",
"sc_loader",
"sck_loader",
"sg_loader",
"sh_loader",
"si_loader",
"sk_loader",
"sl_loader",
"sm_loader",
"smm_loader",
"smod_loader",
"sp_loader",
"sr_loader",
"ss_loader",
"stm_loader",
"ta_loader",
"tc_loader",
"tg_loader",
"tl_loader",
"tm_loader",
"to_loader",
"vh_loader",
"vs_loader",
"wr_loader"
],
"dead": [
"__main__"
]
}
+92
View File
@@ -0,0 +1,92 @@
"""Admission dispatch for #2582 release reporting (owner: plain-English
version reads with what-to-test).
The version-push reader bug tri-diagnosed: (1) latest_release_tag sorted
all tag namespaces, so the TUI build tag captured the main-line range;
(2) the Go narrative prepend ran before body finalization and failed
silently; (3) no try-actions existed. This contract admits: the
namespace-aware tag resolution, the try-action narrative, the workflow
rewiring, and the retroactive v0.346.0 body rebuild.
"""
import json
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from rune import construction_activation as cact # noqa: E402
from rune import construction_admission as cadm # noqa: E402
from rune import construction_mode as cmode # noqa: E402
from rune import scaffold as sc # noqa: E402
ROOT = os.path.join(os.path.dirname(__file__), "..")
OBJECTIVE = (
"#2582 release reporting: make a pushed version read like a changelog "
"for a non-technical owner — plain English what-changed, what-to-try "
"derived from touched surfaces, how-it-was-verified. Namespace-aware "
"tag resolution (main vs tui-v), the try-action narrative section, "
"workflow wiring, retroactive v0.346.0 body rebuild."
)
AFFECTED = [
"scripts/tag_repair.py",
"tests/test_tag_repair.py",
"src/go/release-notes/narrative.go",
"src/go/release-notes/narrative_test.go",
"src/go/release-notes/cli_narrative.go",
".gitea/workflows/auto-version.yml",
".gitea/workflows/maintenance.yml",
"src/go/release-notes/main.go", "src/go/release-notes/cli_changelog.go", "CHANGELOG.md", "scripts/admit_release_reporting.py",
]
def main() -> int:
store = os.path.join(ROOT, "store", "scaffold")
scaffold = sc.Scaffold(store, "rune")
md = cmode.classify_mode(OBJECTIVE, target_project="rune", project_exists=True,
affected_surface=AFFECTED)
need = {
"need_id": "need-release-reporting",
"objective": OBJECTIVE,
"origin": "worker",
"project": "rune",
"task_ref": "release-reporting",
"affected_surface": AFFECTED,
"evidence_refs": ["tests/test_tag_repair.py", "src/go/release-notes/narrative_test.go"],
}
act = cact.activate(md, need=need, scaffold=scaffold, builder="release-reporting")
env = cadm.admit(
act,
objective=OBJECTIVE,
builder="release-reporting",
affected_surface=AFFECTED,
evidence_refs=need["evidence_refs"],
origin="worker",
task_ref="release-reporting",
expected_new_entities=[
"src/go/release-notes/main.go", "src/go/release-notes/cli_changelog.go", "CHANGELOG.md", "scripts/admit_release_reporting.py", "tests/test_tag_repair.py",
],
expected_runtime_effects=[
"every pushed version's release body opens with plain English "
"what-changed + what-to-try + how-verified; ranges start at the "
"main v* tag, never the tui build tag",
],
verification_obligations=[
"pytest tests/test_tag_repair.py green (namespace law)",
"go test ./... in src/go/release-notes green (try-section law)",
"v0.346.0 body visibly rebuilt with the template",
],
regression_obligations=[
"no runtime behavior change: release reporting content only",
"the evidence store (release_history init) still receives its exit-note",
],
approval_ref="user: 'can you fix my gitea ... explains what was improved. and what could be tested' (session 2026-10-23)",
contract_id="ADMIT-release-reporting",
project_store=store,
project_id="rune",
)
print(json.dumps(env, indent=2, default=str)[:2400])
return 0 if env.get("success") else 1
if __name__ == "__main__":
sys.exit(main())
+119
View File
@@ -0,0 +1,119 @@
"""Admission dispatch for wave D55 (the D53+D54 coherence pass + the
D49 consent=promotion follow-through).
classify + activate + admit (the headless scaffolding entry law): the
D55 wave builds on the existing rune project, user-dispatch approved.
"""
import json
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from rune import construction_activation as cact # noqa: E402
from rune import construction_admission as cadm # noqa: E402
from rune import construction_mode as cmode # noqa: E402
from rune import scaffold as sc # noqa: E402
ROOT = os.path.join(os.path.dirname(__file__), "..")
OBJECTIVE = (
"land wave D55 (the D53+D54 coherence pass + the D49 consent="
"promotion follow-through): (1) CONSENT = PROMOTION ONLY — retire "
"the write/edit permission-gate special-casing in tui/approvals: "
"a chat-origin write tool call is never face-blocked by the "
"approvals layer (capability is pane-wide per D49), the "
"work-promotion consent card keeps firing for work-shaped chat "
"messages unchanged, and the write card's wording reads as work "
"promotion, not permission; (2) D53+D54 COHERENCE — every style "
"site resolves through the themes tokens (no raw hex literals "
"outside themes/, stragglers migrated, the old sub-floor #78788A "
"ghost literal included); contrast floors hold across the full "
"face for every role in vivid AND calm; the motion law's three "
"invariants (wave gated, chatpane static, one region per frame) "
"asserted in one place; a fresh long-answer PTY proof at 110 "
"columns over a real streamed 27B turn (format holds, "
"summary-first holds, no deadline error)"
)
AFFECTED = [
"tui/",
"scripts/admit_wave_d55_coherence.py",
]
def main() -> int:
store = os.path.join(ROOT, "store", "scaffold")
scaffold = sc.Scaffold(store, "rune")
md = cmode.classify_mode(
OBJECTIVE,
target_project="rune",
project_exists=True,
affected_surface=AFFECTED,
)
print("classified mode:", md.mode)
need = {
"need_id": "need-wave-d55-coherence",
"objective": OBJECTIVE,
"origin": "worker",
"project": "rune",
"task_ref": "wave-d55-coherence",
"affected_surface": AFFECTED,
"evidence_refs": [
"tui/approvals/approvals.go",
"tui/cmd/rune-tui/main.go",
"tui/consent/consent.go",
"tui/themes/roles.go",
"tui/lanes/render.go",
"tui/lanes/activity.go",
"tui/chatpane/chatpane.go",
],
}
act = cact.activate(md, need=need, scaffold=scaffold,
builder="wave-d55-coherence")
print("activated:", act.get("activated"), act.get("mode"))
env = cadm.admit(
act,
objective=OBJECTIVE,
builder="wave-d55-coherence",
affected_surface=AFFECTED,
evidence_refs=need["evidence_refs"],
origin="worker",
task_ref="wave-d55-coherence",
expected_new_entities=[
"the D55 coherence wave report (reports/)",
"the red-first write-not-face-blocked + "
"promotion-wording tests",
"the migrated themes tokens (confirm trio, body tones, "
"activity states, gauge tones) and the one-place "
"motion-law coherence test",
],
expected_runtime_effects=[
"a chat-origin write tool call is never refused by the "
"approvals layer while work-shaped chat messages still "
"promote through the consent card, every styled row "
"resolves through themes tokens with the contrast floors "
"holding in vivid and calm, the motion law's invariants "
"are asserted in one place, and a streamed 27B long "
"answer holds format at 110 columns with no deadline "
"error",
],
verification_obligations=[
"go test -race -short ./... in tui/ green (documented "
"pre-existing boot-smoke PTY family aside)",
"build/vet/gofmt clean",
"the 110-column long-answer PTY proof over the live core "
"(127.0.0.1:8376, identity Rune)",
],
approval_ref="user:D55 directive 2026-10-24 (D49 follow-through "
"+ D53/D54 coherence)",
contract_id="ADMIT-wave-d55-coherence",
project_store=store,
project_id="rune",
)
print(json.dumps(env, indent=2, default=str)[:2400])
return 0 if env.get("success") else 1
if __name__ == "__main__":
sys.exit(main())
+142
View File
@@ -0,0 +1,142 @@
"""Admission dispatch for wave D56 (Gitea #2580 + #2581 — the
provider-aware dispatch governor + the info-bar tok/s readout).
classify + activate + admit (the headless scaffolding entry law): the
D56 wave builds on the existing rune project, user-dispatch approved
(D56 directive 2026-10-24; owner doctrine: cloud-first routing, no
consent gates on cloud, routing scope = factory lane assignment).
"""
import json
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from rune import construction_activation as cact # noqa: E402
from rune import construction_admission as cadm # noqa: E402
from rune import construction_mode as cmode # noqa: E402
from rune import scaffold as sc # noqa: E402
ROOT = os.path.join(os.path.dirname(__file__), "..")
OBJECTIVE = (
"land the D56 wave (Gitea #2580 + #2581, owner doctrine rulings "
"2026-10-24: CLOUD-FIRST routing — cloud lanes fill FIRST, local "
"absorbs only overflow; NO CONSENT on cloud models — configured "
"provider = spend freely, zero prompts; routing scope = factory "
"lane assignment, interactive chat routing stays the owner's): "
"(1) TELEMETRY — sample live tok/s per provider from production "
"data (the engine's real turn accounting; output-token counts "
"from the provider wires where published, honest token-estimate "
"fallback otherwise); rolling per-provider samples persisted via "
"the hot-state pattern (~/.rune/hot/provider_throughput.json, "
"atomic temp+rename); (2) SATURATION MODEL — a small rolling "
"(provider, lanes) EMA curve learned ONLY from samples, no "
"synthetic benchmarks; the lane cap is where the marginal tok/s "
"gain clears the floor (RUNE_GOVERNOR_CAP_FLOOR, default 5); "
"(3) CONNECTION-HEALTH — the governor polls the registry's "
"enabled providers live (reachable, authenticated, tok/s, error "
"rate, 429 throttle windows) and routes only to providers "
"actually alive; (4) LANE POOLS + CLOUD-FIRST — per-provider lane "
"pools, cloud pools fill first, least-loaded within a tier, "
"throttled providers refused (a 429 ceiling is worse than not "
"spawning); (5) CLASSIFIER INTEGRATION — dispatch-classifier "
"gains the governor's cap: PARALLEL verdicts become "
"throughput-aware ('PARALLEL (N lanes free at current tok/s)'), "
"the thin pipe says so, no false fan-out; registry/lock "
"semantics unchanged; (6) INFO BAR READOUT — tok/s after the "
"context readout ('context 4.2k/32k (13%) · 38 tok/s') fed from "
"/v1/status's new governor section through both bridge seams; "
"the sub-agents section shows the governor's state row (lanes in "
"use, measured tok/s, cap, '—' honest gap); (7) HONESTY — a thin "
"pipe is stated, never dressed up"
)
AFFECTED = [
"src/go/rune-core/",
"src/go/dispatch-classifier/",
"tui/cmd/rune-tui/",
"tui/lanes/",
"tui/bridge/",
"tui/runtime/lib/",
"scripts/admit_wave_d56_governor.py",
]
def main() -> int:
store = os.path.join(ROOT, "store", "scaffold")
scaffold = sc.Scaffold(store, "rune")
md = cmode.classify_mode(
OBJECTIVE,
target_project="rune",
project_exists=True,
affected_surface=AFFECTED,
)
print("classified mode:", md.mode)
need = {
"need_id": "need-wave-d56-governor",
"objective": OBJECTIVE,
"origin": "worker",
"project": "rune",
"task_ref": "wave-d56-governor",
"affected_surface": AFFECTED,
"evidence_refs": [
"src/go/rune-core/internal/runecore/governor.go",
"src/go/rune-core/internal/runecore/governor_test.go",
"src/go/rune-core/internal/runecore/server.go",
"src/go/rune-core/internal/runecore/engine.go",
"src/go/dispatch-classifier/capacity.go",
"src/go/dispatch-classifier/capacity_test.go",
"tui/cmd/rune-tui/main.go",
"tui/cmd/rune-tui/d56_governor_test.go",
"tui/bridge/d56_governor_test.go",
"tui/runtime/lib/lib.go",
],
}
act = cact.activate(md, need=need, scaffold=scaffold,
builder="wave-d56-governor")
print("activated:", act.get("activated"), act.get("mode"))
env = cadm.admit(
act,
objective=OBJECTIVE,
builder="wave-d56-governor",
affected_surface=AFFECTED,
evidence_refs=need["evidence_refs"],
origin="worker",
task_ref="wave-d56-governor",
expected_new_entities=[
"the D56 wave report (reports/)",
"the governor hot-state shape "
"(~/.rune/hot/provider_throughput.json)",
"the red-first governor/capacity/readout tests",
],
expected_runtime_effects=[
"the governor samples live tok/s per provider from real "
"turns and persists the rolling model to the hot state, "
"routes cloud-first through healthy providers only "
"(429-throttled refused), the dispatch classifier's "
"PARALLEL verdicts carry the measured lane capacity with "
"the thin pipe stated honestly, and the info bar shows "
"the measured tok/s after the context readout with the "
"sub-agents section showing the governor's state",
],
verification_obligations=[
"go test -race -short green on rune-core, "
"dispatch-classifier and tui (hermetic PTY/HTTP tests)",
"vet/gofmt clean on touched modules",
"readout proof: the info-bar test asserts 'context ... "
"(13%) · 38 tok/s' byte-shape against a fake serve",
],
approval_ref="user:D56 directive 2026-10-24 (Gitea #2580 + #2581)",
contract_id="ADMIT-wave-d56-governor",
project_store=store,
project_id="rune",
)
print(json.dumps(env, indent=2, default=str)[:2400])
return 0 if env.get("success") else 1
if __name__ == "__main__":
sys.exit(main())
+172
View File
@@ -0,0 +1,172 @@
"""Admission dispatch for wave D57 — runtime dominion (owner's top
priority, Gitea #2590 as amended by #2591).
The owner's ruling (verbatim): "rune doesnt have a way of reaching the
file system of a user... adaptive to whatever filesystem and os the
user has... part of runtime awareness. this is why the tools are not
functioning." and "rune should have dominion on tool call usage... on
my version i dont care i want her to have full read/write capabilities
with access to any file system it gets used on. on a production
version there needs to be sandboxed permissions."
Build: (1) the boot-time runtime environment layer (OS family, home,
separators, temp dir, case sensitivity, writability, executor
resolution) served on /v1/status as runtime_env — awareness, never
gating; (2) tool-executor DEPLOYMENT to ~/.local/share/rune/bin via
ensure_core.py and build_paired.sh, resolved through one door
(ResolveToolExecutorPath); (3) the model-refusal authority — a
deterministic literal-phrase classifier over assistant replies; a
FALSE capability claim ("I don't have tools") is verified against the
runtime's actual state, corrected with a retry turn naming the real
suite, journaled as capability.refusal_correction, and counted in the
model_authority status section; (4) /v1/status gains model_authority
(suite on wire, gates open, refusals seen, corrections issued); (5)
the D57 dominion check — a test asserting NO gate exists between the
chat pane's tool calls and the filesystem ops on this build.
approval_ref 'user:D57 owner ruling (runtime awareness + dominion on
tool calls; Gitea #2590/#2591)'.
"""
import json
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from rune import construction_activation as cact # noqa: E402
from rune import construction_admission as cadm # noqa: E402
from rune import construction_mode as cmode # noqa: E402
from rune import scaffold as sc # noqa: E402
ROOT = os.path.join(os.path.dirname(__file__), "..")
OBJECTIVE = (
"Build the owner's D57 runtime-dominion doctrine (Gitea #2590 as "
"amended by #2591, the owner's top priority): (1) a boot-time "
"runtime environment layer in rune-core probing the filesystem "
"context (OS family, os.UserHomeDir, path separators, temp dir, "
"case sensitivity, writability) and serving it on /v1/status as "
"runtime_env — awareness, never gating; generalize expandHome "
"past its POSIX-only assumption; (2) deploy the tool-executor "
"binary to ~/.local/share/rune/bin alongside rune-core+rune-tui "
"via ensure_core.py and build_paired.sh, with shell-out paths "
"resolving the deployed location through ResolveToolExecutorPath; "
"(3) a model-refusal authority: a deterministic literal-phrase "
"classifier over assistant replies (no clever NLP) detecting the "
"I-cannot/I-lack-access/I-have-no-tools class, verified against "
"the runtime's ACTUAL capability state; a false claim composes a "
"correction turn naming the real suite, retries once, journals a "
"capability.refusal_correction event, and never reaches the owner "
"as truth; (4) /v1/status gains model_authority (suite on wire, "
"gates open, refusals seen, corrections issued); (5) a dominion "
"check test asserting NO gate exists between chat-pane tool calls "
"and the filesystem ops on this build — consent is promotion, not "
"permission"
)
AFFECTED = [
"src/go/rune-core/",
"scripts/ensure_core.py",
"scripts/build_paired.sh",
"scripts/admit_wave_d57_runtime_dominion.py",
]
def main() -> int:
store = os.path.join(ROOT, "store", "scaffold")
scaffold = sc.Scaffold(store, "rune")
md = cmode.classify_mode(
OBJECTIVE,
target_project="rune",
project_exists=True,
affected_surface=AFFECTED,
)
print("classified mode:", md.mode)
need = {
"need_id": "need-wave-d57-runtime-dominion",
"objective": OBJECTIVE,
"origin": "worker",
"project": "rune",
"task_ref": "wave-d57-runtime-dominion",
"affected_surface": AFFECTED,
"evidence_refs": [
"owner ruling D57 (verbatim in the wave brief): runtime "
"awareness of the user's filesystem + dominion on tool "
"call usage (full read/write on the owner build, sandboxed "
"permissions deferred to a production version)",
"Gitea issues #2590 and #2591 (amendments; doctrine final)",
"src/go/rune-core/internal/runecore/tools.go "
"(executeGoTool — the cross-platform Go-native suite; D49 "
"proved the full suite rides the wire)",
"scripts/admit_wave_d49_capability_everywhere.py (the "
"admission pattern)",
"D49 verdict: model claims of 'I lack tool access' are "
"FALSE on this build — the wire provably carries the suite",
],
}
act = cact.activate(md, need=need, scaffold=scaffold,
builder="wave-d57-runtime-dominion")
print("activated:", act.get("activated"), act.get("mode"))
env = cadm.admit(
act,
objective=OBJECTIVE,
builder="wave-d57-runtime-dominion",
affected_surface=AFFECTED,
evidence_refs=need["evidence_refs"],
origin="worker",
task_ref="wave-d57-runtime-dominion",
expected_new_entities=[
"src/go/rune-core/internal/runecore/runtimeenv.go (the "
"boot-time probe: OS family, home, separators, temp dir, "
"case sensitivity, writability, executor resolution — "
"cached, awareness never gating) + ResolveToolExecutorPath "
"as the ONE door to the deployed binary",
"src/go/rune-core/internal/runecore/refusal.go (the "
"deterministic literal-phrase refusal classifier with "
"false_capability_claim / true_refusal / genuine_limit "
"verdicts and the composed correction turn)",
"src/go/rune-core/internal/runecore/engine_refusal.go "
"(the engine hook: correction retry, the typed "
"capability.refusal_correction journal event, the session "
"counters behind AuthoritySnapshot)",
"server.go /v1/status: runtime_env + model_authority "
"sections",
"scripts/ensure_core.py + scripts/build_paired.sh: "
"tool-executor deployment to ~/.local/share/rune/bin",
"tests: the refusal fixture battery (true-refusal vs "
"false-capability-claim vs genuine-limit), the runtime-env "
"probes, and the D57 dominion check (no gate between chat "
"and the filesystem ops)",
],
expected_runtime_effects=[
"Rune boots KNOWING her filesystem context; /v1/status "
"serves it (runtime_env) alongside the model-authority "
"facts",
"the tool-executor is actually deployed — shell-out paths "
"resolve instead of failing",
"a false model capability claim is corrected on the wire "
"and journaled — the owner never receives 'I lack access' "
"as truth while the suite rides the wire",
"no gate exists between chat-pane tool calls and the "
"filesystem ops on this build (dominion)",
],
verification_obligations=[
"go test -race -short green for the whole rune-core "
"module; go vet + gofmt clean",
"red-first evidence: the fixture battery and the dominion "
"check pin behavior the pre-wave tree did not serve (no "
"runtime_env/model_authority sections, no classifier, no "
"deployed executor resolution)",
"commit(s) name this contract; pre-commit staged gate "
"green via this open contract",
],
approval_ref="user:D57 owner ruling (runtime awareness + "
"dominion on tool calls; Gitea #2590/#2591)",
contract_id="ADMIT-wave-d57-runtime-dominion",
project_store=store,
project_id="rune",
)
print(json.dumps(env, indent=2, default=str)[:2400])
if __name__ == "__main__":
raise SystemExit(main())
+127
View File
@@ -0,0 +1,127 @@
"""Admission dispatch for wave rebase-completion (Gitea #2587).
THE FINDING (audit #2587): 17 Python modules have Go-side directories; only
6 are wired. ELEVEN Go engines with substantial function counts have ZERO
delegation in their Python counterpart: abstraction(71), auto_context(75),
cache(66), forge_client(87), goal(63), message_queue(65), microkernel(62),
openai_compat_transport(74), paths(13), voicebox(24) — plus pins (Python-only,
12 funcs) needing an honest boundary verdict.
THE OWNER'S BAR (verbatim): 'going through and rewiring the rebase into the
tui, extremely thoroughly according to the code dichotomy.'
PATTERN: fail-loud delegation facade (Go engine default, RUNE_<NAME>_ENGINE=
python escape, RUNE_<NAME>_BIN override, typed RuntimeError on missing
binary — no silent fallback; house shapes: model_routing.reconcile_tools_
support :457, local_tool_runtime._GoDelegatingExecutor). Red-first parity
battery per module against the Python oracle body (kept as oracle/escape).
Integrated receipt per module in verification/integrated/.
approval_ref 'user:Gitea #2587 rebase-completion directive'.
"""
import json
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from rune import construction_activation as cact # noqa: E402
from rune import construction_admission as cadm # noqa: E402
from rune import construction_mode as cmode # noqa: E402
from rune import scaffold as sc # noqa: E402
ROOT = os.path.join(os.path.dirname(__file__), "..")
OBJECTIVE = (
"REBASE-COMPLETION (Gitea #2587): wire the eleven unwired Go engines "
"into their Python counterparts through the fail-loud delegation "
"facade pattern — openai_compat_transport and forge_client (serving "
"path) first, then microkernel, goal, cache, message_queue, "
"abstraction, auto_context, voicebox, paths; classify pins honestly "
"(port to src/go/pins or record the Python-boundary verdict from its "
"consumers). Each module: red-first parity battery against the Python "
"oracle body (kept as oracle/escape hatch), RUNE_<NAME>_ENGINE=python "
"escape, RUNE_<NAME>_BIN override, typed RuntimeError on missing "
"binary, integrated receipt in verification/integrated/. "
"Owner's bar: 'going through and rewiring the rebase into the tui, "
"extremely thoroughly according to the code dichotomy.'"
)
AFFECTED = [
"src/go/**",
"src/rune/**",
"tests/**",
"verification/**",
"scripts/admit_wave_rebase_completion.py",
]
def main() -> int:
store = os.path.join(ROOT, "store", "scaffold")
scaffold = sc.Scaffold(store, "rune")
md = cmode.classify_mode(
OBJECTIVE,
target_project="rune",
project_exists=True,
affected_surface=AFFECTED,
)
print("classified mode:", md.mode)
need = {
"need_id": "need-wave-rebase-completion",
"objective": OBJECTIVE,
"origin": "worker",
"project": "rune",
"task_ref": "wave-rebase-completion",
"affected_surface": AFFECTED,
"evidence_refs": [
"Gitea #2587 (REBASE-COMPLETION audit: 11 dual-side modules "
"with Go engines but ZERO delegation; pins Python-only)",
"verification/integrated/model-routing-integrated.json (the "
"house PORT → INTEGRATE → REMOVE shape this wave replicates)",
"src/rune/model_routing.py reconcile_tools_support facade "
":457 and src/rune/local_tool_runtime.py "
"_GoDelegatingExecutor (the fail-loud house shapes)",
],
}
act = cact.activate(md, need=need, scaffold=scaffold,
builder="wave-rebase-completion")
print("activated:", act.get("activated"), act.get("mode"))
env = cadm.admit(
act,
objective=OBJECTIVE,
builder="wave-rebase-completion",
affected_surface=AFFECTED,
evidence_refs=need["evidence_refs"],
origin="worker",
task_ref="wave-rebase-completion",
expected_new_entities=[
"eleven fail-loud delegation facades (one per module) with "
"RUNE_*_ENGINE=python escapes and RUNE_*_BIN overrides",
"red-first parity battery per module against the Python "
"oracle body; mutations of the oracle must flip the battery",
"integrated receipt per module in verification/integrated/",
"pins verdict: Go port at src/go/pins OR recorded "
"Python-boundary classification from consumer evidence",
],
expected_runtime_effects=[
"the serving path (openai_compat_transport, forge_client) "
"and the ten other wired engines resolve through the Go "
"binaries at runtime; Python oracle bodies retained as "
"escape/oracle only",
],
verification_obligations=[
"per module: go test -race -short green in the engine dir; "
"pytest touched suites green (venv, PYTHONPATH=src)",
"conventional commit per module, pushed to gitea master "
"incrementally (autostash rebase — the tree is hot)",
],
approval_ref="user:Gitea #2587 rebase-completion directive",
contract_id="ADMIT-wave-rebase-completion",
project_store=store,
project_id="rune",
)
print(json.dumps(env, indent=2, default=str)[:2400])
if __name__ == "__main__":
raise SystemExit(main())
+103
View File
@@ -0,0 +1,103 @@
"""Admission dispatch for wave REBASE-PROOF (Gitea #2586 — earned
'wired-in': receipts prove written, serving-path evidence earns wired).
classify + activate + admit (the headless scaffolding entry law,
scripts/admit_wave_rebase_tui_wiring.py pattern). This is an EVIDENCE
wave: scope is research/**, tests/** (the no-oracle test only) and the
admission script itself. Code repairs surfaced by the verdict table are
SCOPED, not landed.
"""
import json
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from rune import construction_activation as cact # noqa: E402
from rune import construction_admission as cadm # noqa: E402
from rune import construction_mode as cmode # noqa: E402
from rune import scaffold as sc # noqa: E402
ROOT = os.path.join(os.path.dirname(__file__), "..")
OBJECTIVE = (
"close the wave-2586 REBASE-PROOF gate: prove the rebase has EARNED "
"wired-in per facade row (serving-path trace via live core journal + "
"witness boot chain + real 27B turn, fail-loud RUNE_*_BIN probes, "
"parity-at-load batteries in the proof session, no-oracle grep + "
"test), land the verdict table research/rebase-proof/verdict-2026-"
"10-24.{json,md}, land the three-way wiring inventory (296 modules: "
"go-delegated / rust-kernel-wired / pure-python split load-bearing "
"vs tooling vs dead) research/rebase-proof/inventory.{json,md}, and "
"scope — not land — the repairs for the failing rows "
"(trust-and-sovereignty fuzz parity, forge_client truth drift, "
"openai_compat_transport live chat row, message_queue missing "
"battery, fsops harness refusal)"
)
AFFECTED = [
"research/rebase-proof/verdict-2026-10-24.json",
"research/rebase-proof/verdict-2026-10-24.md",
"research/rebase-proof/inventory.json",
"research/rebase-proof/inventory.md",
"tests/test_rebase_proof_no_oracle.py",
"scripts/admit_wave_rebase_proof.py",
]
def main() -> int:
store = os.path.join(ROOT, "store", "scaffold")
scaffold = sc.Scaffold(store, "rune")
md = cmode.classify_mode(
OBJECTIVE,
target_project="rune",
project_exists=True,
affected_surface=AFFECTED,
)
print("classified mode:", md.mode)
need = {
"need_id": "need-wave-rebase-proof",
"objective": OBJECTIVE,
"origin": "worker",
"project": "rune",
"task_ref": "wave-rebase-proof",
"affected_surface": AFFECTED,
"evidence_refs": [
"research/rebase-proof/verdict-2026-10-24.json",
"research/rebase-proof/inventory.json",
"research/witness-run/witness-2026-10-24.json",
"memory/journal/events.jsonl",
"tests/test_rebase_proof_no_oracle.py",
],
}
act = cact.activate(md, need=need, scaffold=scaffold,
builder="wave-rebase-proof")
print("activated:", act.get("activated"), act.get("mode"))
env = cadm.admit(
act,
objective=OBJECTIVE,
builder="wave-rebase-proof",
affected_surface=AFFECTED,
evidence_refs=need["evidence_refs"],
)
print("admitted:", json.dumps(env, default=str)[:400])
gate = {
"verdict_table_present": os.path.exists(
os.path.join(ROOT, "research/rebase-proof/verdict-2026-10-24.json")),
"inventory_present": os.path.exists(
os.path.join(ROOT, "research/rebase-proof/inventory.json")),
"no_oracle_test_present": os.path.exists(
os.path.join(ROOT, "tests/test_rebase_proof_no_oracle.py")),
}
print("rule-5 gate:", gate)
if not all(gate.values()):
print("GATE FAILED")
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,139 @@
"""Admission dispatch for wave retire-reconcile-tools (Gitea #2574
rebalanced overnight queue, wave A; retirement-map blocker closure from
wave 181's model-routing-integrated.json blocks_full_switchover note).
classify + activate + admit (the headless scaffolding entry law,
scripts/admit_wave_memory_continuity_go.py pattern): port the
capability-floor drift reconciliation (reconcile_tools_support,
src/rune/model_routing.py — the R2 I-R2-022 truth_drift seam whose
missing Go surface blocked local_tool_runtime.py from dropping its
Python import) into the existing Go engine src/go/model_routing (new
ReconcileToolsSupport + `eval reconcile` surface), keep the Python body
as oracle/escape hatch (RUNE_MODEL_ROUTING_ENGINE=python, NO Python
deletion — PORT → INTEGRATE → REMOVE), switch the function to a
fail-loud delegation facade (typed RuntimeError when the engine binary
is missing), and pin the port with a red-first parity battery
(tests/test_reconcile_tools_support_port.py) against the Python oracle.
"""
import json
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from rune import construction_activation as cact # noqa: E402
from rune import construction_admission as cadm # noqa: E402
from rune import construction_mode as cmode # noqa: E402
from rune import scaffold as sc # noqa: E402
ROOT = os.path.join(os.path.dirname(__file__), "..")
OBJECTIVE = (
"Close the wave-181 full-switchover blocker: Go port of the "
"capability-floor drift reconciliation (reconcile_tools_support, "
"R2 I-R2-022 truth_drift) absorbed into the existing "
"src/go/model_routing engine (ReconcileToolsSupport + `eval "
"reconcile`); a provider tool-support claim contradicting "
"CAPABILITY_FLOOR surfaces as unverified drift, never silently "
"flipping eligibility; non-floor models are never invented a floor "
"verdict. Python body stays as oracle/escape hatch "
"(RUNE_MODEL_ROUTING_ENGINE=python) — no Python deletion; the "
"function becomes a fail-loud delegation facade (typed RuntimeError "
"when the engine binary is missing, RUNE_MODEL_ROUTING_BIN "
"override); red-first parity battery "
"tests/test_reconcile_tools_support_port.py pins Go to the Python "
"oracle byte-for-byte; local_tool_runtime consumer keeps its seam "
"but now resolves through Go at runtime"
)
AFFECTED = [
"src/go/model_routing/",
"src/rune/model_routing.py",
"src/rune/local_tool_runtime.py",
"tests/test_reconcile_tools_support_port.py",
"verification/integrated/model-routing-integrated.json",
"verification/integrated/reconcile-tools-integrated.json",
"scripts/admit_wave_retire_reconcile_tools.py",
]
def main() -> int:
store = os.path.join(ROOT, "store", "scaffold")
scaffold = sc.Scaffold(store, "rune")
md = cmode.classify_mode(
OBJECTIVE,
target_project="rune",
project_exists=True,
affected_surface=AFFECTED,
)
print("classified mode:", md.mode)
need = {
"need_id": "need-wave-retire-reconcile-tools",
"objective": OBJECTIVE,
"origin": "worker",
"project": "rune",
"task_ref": "wave-retire-reconcile-tools",
"affected_surface": AFFECTED,
"evidence_refs": [
"verification/integrated/model-routing-integrated.json",
"src/rune/model_routing.py",
"src/rune/local_tool_runtime.py",
"src/go/model_routing/eval.go",
"tests/test_p1_06_model_routing.py",
],
}
act = cact.activate(md, need=need, scaffold=scaffold,
builder="wave-retire-reconcile-tools")
print("activated:", act.get("activated"), act.get("mode"))
env = cadm.admit(
act,
objective=OBJECTIVE,
builder="wave-retire-reconcile-tools",
affected_surface=AFFECTED,
evidence_refs=need["evidence_refs"],
origin="worker",
task_ref="wave-retire-reconcile-tools",
expected_new_entities=[
"src/go/model_routing/reconcile.go (Go port of "
"reconcile_tools_support over CapabilityFloor)",
"src/go/model_routing/reconcile_test.go (red-first Go parity "
"battery mirrored from the Python oracle corpus)",
"src/go/model_routing `eval reconcile` surface (eval.go)",
"tests/test_reconcile_tools_support_port.py (Python-side "
"parity battery: go delegation == python oracle "
"byte-for-byte; fail-loud binary-missing refusal; "
"local_tool_runtime consumer receipt)",
"verification/integrated/reconcile-tools-integrated.json "
"(integrated receipt)",
],
expected_runtime_effects=[
"a provider supports_tools claim contradicting "
"CAPABILITY_FLOOR surfaces as an unverified drift marker "
"and never silently flips eligibility",
"reconcile_tools_support resolves through the Go engine by "
"default; RUNE_MODEL_ROUTING_ENGINE=python pins the oracle "
"body; a missing engine binary raises a typed RuntimeError "
"(no silent Python fallback)",
"local_tool_runtime's capability-drift seam now executes Go "
"at runtime while the Python module stays as oracle",
],
verification_obligations=[
"go test -race -short ./... in src/go/model_routing green",
"pytest tests/test_reconcile_tools_support_port.py "
"tests/test_p1_06_model_routing.py "
"tests/test_local_tool_runtime.py green",
],
approval_ref="user:gitea #2574 rebalanced overnight queue, wave A "
"(majority-rebase) 2026-10-24",
contract_id="ADMIT-wave-retire-reconcile-tools",
project_store=store,
project_id="rune",
)
print(json.dumps(env, indent=2, default=str)[:2400])
return 0 if env.get("success") else 1
if __name__ == "__main__":
sys.exit(main())
+132
View File
@@ -0,0 +1,132 @@
"""Admission dispatch for wave retire-tool-executor (Gitea #2575
rebalanced overnight queue, wave B): wire local_tool_runtime.py to the
Go tool-executor engine.
classify + activate + admit (headless scaffolding entry law,
scripts/admit_wave_retire_reconcile_tools.py pattern): the Go executor
exists (src/go/tool-executor, full CLI surface read_file/list_directory/
glob_files/grep_files/run_command, JSON envelope {success,result,error})
but src/rune/local_tool_runtime.py never delegates to it. This wave wires
the fail-loud delegation facade (LocalToolRuntime's executor resolves the
four READ-ONLY ops through the Go binary by default; run_command stays
behind the Python approval door — mutating, never silently executed),
keeps the Python bodies as oracle/escape hatch
(RUNE_TOOL_EXECUTOR_ENGINE=python, NO Python deletion — PORT → INTEGRATE
→ REMOVE), and pins the port with a red-first parity battery
(tests/test_tool_executor_delegation.py) against the Python oracle.
"""
import json
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from rune import construction_activation as cact # noqa: E402
from rune import construction_admission as cadm # noqa: E402
from rune import construction_mode as cmode # noqa: E402
from rune import scaffold as sc # noqa: E402
ROOT = os.path.join(os.path.dirname(__file__), "..")
OBJECTIVE = (
"Close the wave-181 full-switchover blocker: Go port of the local "
"tool execution — wire local_tool_runtime.py to the existing "
"src/go/tool-executor engine. LocalToolRuntime executor becomes "
"a fail-loud delegation facade: the four read-only file ops "
"(read_file, directory enumeration, glob, grep) resolve through "
"the Go binary by default, JSON envelope preserved exactly; "
"run_command stays behind the approval door (mutating, never "
"silently executed). Python bodies stay as oracle escape hatch "
"(RUNE_TOOL_EXECUTOR_ENGINE=python, RUNE_TOOL_EXECUTOR_BIN "
"override, typed RuntimeError when the binary is missing — no "
"silent fallback, no Python deletion; PORT → INTEGRATE → REMOVE). "
"Red-first parity battery tests/test_tool_executor_delegation.py "
"pins Go to the Python oracle envelope-for-envelope"
)
AFFECTED = [
"src/go/tool-executor/",
"src/rune/local_tool_runtime.py",
"tests/test_tool_executor_delegation.py",
"tests/test_local_tool_runtime.py",
"tests/test_p0_05_native_tools.py",
"tests/test_mode_dispatch_gate.py",
"verification/integrated/tool-executor-integrated.json",
"scripts/admit_wave_retire_tool_executor.py",
]
def main() -> int:
store = os.path.join(ROOT, "store", "scaffold")
scaffold = sc.Scaffold(store, "rune")
md = cmode.classify_mode(
OBJECTIVE,
target_project="rune",
project_exists=True,
affected_surface=AFFECTED,
)
print("classified mode:", md.mode)
need = {
"need_id": "need-wave-retire-tool-executor",
"objective": OBJECTIVE,
"origin": "worker",
"project": "rune",
"task_ref": "wave-retire-tool-executor",
"affected_surface": AFFECTED,
"evidence_refs": [
"src/go/tool-executor/internal/toolexec/cli.go",
"src/rune/local_tool_runtime.py",
"src/rune/tool_executor.py",
"tests/test_tool_executor_delegation.py",
"verification/integrated/reconcile-tools-integrated.json",
],
}
act = cact.activate(md, need=need, scaffold=scaffold,
builder="wave-retire-tool-executor")
print("activated:", act.get("activated"), act.get("mode"))
env = cadm.admit(
act,
objective=OBJECTIVE,
builder="wave-retire-tool-executor",
affected_surface=AFFECTED,
evidence_refs=need["evidence_refs"],
origin="worker",
task_ref="wave-retire-tool-executor",
expected_new_entities=[
"tests/test_tool_executor_delegation.py (red-first parity "
"battery: Go delegation == Python oracle envelope-for-"
"envelope; fail-loud binary-missing refusal; run_command "
"never delegates; default-is-go pin)",
"verification/integrated/tool-executor-integrated.json "
"(integrated receipt, soak clock before any Python deletion)",
],
expected_runtime_effects=[
"LocalToolRuntime's executor resolves read_file, "
"list_directory, glob_files and grep_files through the Go "
"tool-executor binary by default; envelopes preserved "
"exactly",
"RUNE_TOOL_EXECUTOR_ENGINE=python pins the oracle path; "
"a missing engine binary raises a typed RuntimeError (no "
"silent Python fallback)",
"run_command dispatches through the Python authority path "
"only (approval door preserved, no Go bypass)",
],
verification_obligations=[
"go test -race -short ./... in src/go/tool-executor green",
"pytest tests/test_tool_executor_delegation.py "
"tests/test_local_tool_runtime.py tests/test_p0_05_native_tools.py "
"tests/test_mode_dispatch_gate.py tests/test_context_broker.py "
"green",
],
approval_ref="user:gitea #2575 rebalanced overnight queue, wave B "
"2026-10-24",
contract_id="ADMIT-wave-retire-tool-executor",
project_store=store,
project_id="rune",
)
print(json.dumps(env, indent=2, default=str)[:2400])
return 0 if env.get("success") else 1
if __name__ == "__main__":
sys.exit(main())
+145
View File
@@ -0,0 +1,145 @@
"""Admission dispatch for wave second-pass-verification (Gitea #2596).
classify + activate + admit (the headless scaffolding entry law,
scripts/admit_fuzz_boundary_decoder.py pattern): the adversarial
re-verification of the 10 remaining general-34 facades after the wiring
wave landed — the cache-catch standard (fresh -count=1 batteries,
serving-path probes, mutation spot-checks on different lines than the
landing battery killed, red-first fixes per catch).
"""
import json
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from rune import construction_activation as cact # noqa: E402
from rune import construction_admission as cadm # noqa: E402
from rune import construction_mode as cmode # noqa: E402
from rune import scaffold as sc # noqa: E402
ROOT = os.path.join(os.path.dirname(__file__), "..")
MODULES = [
"openai_compat_transport", "forge_client", "microkernel", "goal",
"message_queue", "abstraction", "auto_context", "voicebox", "paths",
"pins",
]
OBJECTIVE = (
"Second-pass adversarial verification of the 10 remaining general-34 "
"facades at the cache-catch standard (cache 53ab710e is the worked "
"example): per module — (1) fresh battery at -count=1 (go -race "
"-short + the pytest live-parity suite); (2) serving-path probes the "
"battery may not cover: cross-invocation state, empty stores, "
"unicode keys/content, malformed input (bad JSON, missing fields), "
"concurrent access, missing-binary fail-loud (RUNE_*_BIN=/nonexistent "
"must raise typed RuntimeError), and the atexit/deferred-flush class "
"on every eval surface (any per-process in-memory accumulation); "
"(3) mutation spot-checks on lines different from the landing "
"battery's kills — flip, confirm the battery catches, revert "
"byte-identical; (4) fix red-first per catch, commit per module; "
"(5) clean-bill receipt with the probes used, so 'verified' names "
"its evidence. Scope: src/go/**, src/rune/**, tests/**, + this "
"script. Zero new Python beyond tests."
)
AFFECTED = (
[f"src/go/{m}/" for m in MODULES]
+ [
"src/rune/message_queue.py",
"src/rune/abstraction.py",
"src/rune/goal.py",
"src/rune/voicebox.py",
"src/rune/paths.py",
"src/rune/forge_client.py",
"src/rune/auto_context.py",
"src/rune/microkernel.py",
"src/rune/openai_compat_transport.py",
"tests/",
]
+ ["scripts/admit_wave_second_pass.py"]
)
EVIDENCE = [
"src/go/goal/truth_test.go (TestSaveFailsLoudOnUnwritableStore — the "
"serving-path catch: silent store-save data loss, fixed fail-loud)",
"src/go/goal/truth_test.go (TestSetGoalDefaultVerificationMethod pin)",
"src/go/abstraction/default_limit_test.go (TestListBeliefsDefaultLimitIs20)",
"src/go/forge_client/truth_test.go (TestEvalRequestConnectErrorKind)",
"verification/integrated/*-integrated.json (landing receipts)",
]
VERIFICATION = [
"go test -race -short -count=1 ./... green in each of the 9 Go "
"engine modules",
"pytest tests/test_*_go_parity.py green (106 tests)",
"serving-path probes: malformed eval JSON loud (rc!=0) on all 9 "
"engines; missing-binary typed RuntimeError on all 9 facades; "
"unicode round-trips through message_queue and goal stores across "
"invocations; empty-store behavior; corrupt message_queue store "
"stays loud; no atexit/deferred-flush accumulator on any of the 10 "
"eval surfaces (the cache-bug class is cache-only, already fixed)",
"mutation spot-checks: 9 flips — 6 caught by landed batteries "
"(microkernel now_iso, message_queue detect weight, voicebox "
"empty-event validation, paths rune_home, auto_context "
"active-task filter, openai_compat tool type), 3 gaps closed with "
"red-first pins (goal default method, abstraction default limit, "
"forge_client live ConnectError kind)",
"pins verdict-only: boundary audit confirmed — no Go wiring; "
"verdict battery green (src/go/pins carries recall-kernel parity "
"artifact only)",
]
def main() -> int:
store = os.path.join(ROOT, "store", "scaffold")
scaffold = sc.Scaffold(store, "rune")
md = cmode.classify_mode(
OBJECTIVE,
target_project="rune",
project_exists=True,
affected_surface=AFFECTED,
)
print("classified mode:", md.mode)
need = {
"need_id": "need-second-pass-verification",
"objective": OBJECTIVE,
"origin": "worker",
"project": "rune",
"task_ref": "second-pass-verification",
"affected_surface": AFFECTED,
"evidence_refs": EVIDENCE,
}
act = cact.activate(md, need=need, scaffold=scaffold,
builder="second-pass-verification")
print("activated:", act.get("activated"), act.get("mode"))
env = cadm.admit(
act,
objective=OBJECTIVE,
builder="second-pass-verification",
affected_surface=AFFECTED,
evidence_refs=EVIDENCE,
origin="worker",
task_ref="second-pass-verification",
expected_new_entities=[
"src/go/goal/truth_test.go (fail-loud save catch + default-method pin)",
"src/go/abstraction/default_limit_test.go (default-limit pin)",
"src/go/forge_client/truth_test.go (live-path kind pin)",
"scripts/admit_wave_second_pass.py",
],
expected_runtime_effects=[],
verification_obligations=VERIFICATION,
approval_ref="user:#2596 second-pass verification dispatch",
contract_id="ADMIT-wave-second-pass",
project_store=store,
project_id="rune",
)
print(json.dumps(env, indent=2, default=str)[:2400])
return 0 if env.get("success") else 1
if __name__ == "__main__":
sys.exit(main())
@@ -0,0 +1,127 @@
"""Admission dispatch for the streaming-deadline ROOT-CAUSE wave (Gitea
#2556 — the owner verdict: 'context deadline needs a proper solution we
have just been patching around it rather than making the root cause
better for local models').
classify + activate + admit (the headless scaffolding entry law): the
wave replaces the flat whole-turn budgets (330s -> 480s -> 6x480s, the
D46/D47 lineage) with STREAMING through the whole chain — Ollama wire,
cloud wires, core /v1/chat/completions, entry proxy, TUI render — so a
turn never sits inside one blocking HTTP call; the deadline collapses
to an INTER-CHUNK IDLE TIMEOUT (no bytes = dead) with a generous
overall backstop. Budget constants retire to non-streaming fallback
paths only.
"""
import json
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from rune import construction_activation as cact # noqa: E402
from rune import construction_admission as cadm # noqa: E402
from rune import construction_mode as cmode # noqa: E402
from rune import scaffold as sc # noqa: E402
ROOT = os.path.join(os.path.dirname(__file__), "..")
OBJECTIVE = (
"land the streaming root-cause wave (Gitea #2556, owner verdict "
"2026-10-22): stream through the WHOLE turn chain so a local 27B "
"thinking turn never sits inside one blocking HTTP call — (1) the "
"Ollama wire runs stream=true /api/chat with NDJSON chunks parsed "
"incrementally (content/thinking/tool_calls; the final message "
"object carries the assembled reply) and the engine's bounded tool "
"loop runs over streamed replies; (2) the cloud wires gain "
"streaming parity ([OI]-compat SSE, Anthropic SSE events, Gemini "
"streamGenerateContent); (3) the core /v1/chat/completions relays "
"the streamed turn as NDJSON chunks for stream=true callers while "
"non-streaming callers get the assembled answer as before; (4) the "
"entry proxy passes the stream through and the TUI renders text as "
"it arrives; (5) on streamed paths the whole-turn budgets retire — "
"replaced by an inter-chunk idle timeout (default 120s, "
"RUNE_STREAM_IDLE_TIMEOUT configurable) plus a 30-minute overall "
"backstop; the budget constants remain only for non-streaming "
"fallback paths"
)
AFFECTED = [
"src/go/rune-core/internal/runecore/",
"src/go/rune-entry/",
"tui/runtime/lib/",
"tui/cmd/rune-tui/",
"scripts/admit_wave_streaming_deadline_root.py",
]
def main() -> int:
store = os.path.join(ROOT, "store", "scaffold")
scaffold = sc.Scaffold(store, "rune")
md = cmode.classify_mode(
OBJECTIVE,
target_project="rune",
project_exists=True,
affected_surface=AFFECTED,
)
print("classified mode:", md.mode)
need = {
"need_id": "need-wave-streaming-deadline-root",
"objective": OBJECTIVE,
"origin": "worker",
"project": "rune",
"task_ref": "wave-streaming-deadline-root",
"affected_surface": AFFECTED,
"evidence_refs": [
"src/go/rune-core/internal/runecore/ollama.go",
"src/go/rune-core/internal/runecore/cloud.go",
"src/go/rune-core/internal/runecore/server.go",
"src/go/rune-entry/internal/entry/serve.go",
"tui/runtime/lib/complete.go",
"tui/laneconc/laneconc.go",
"tui/cmd/rune-tui/main.go",
"scripts/admit_wave_terminal_budgets.py",
],
}
act = cact.activate(md, need=need, scaffold=scaffold,
builder="wave-streaming-deadline-root")
print("activated:", act.get("activated"), act.get("mode"))
env = cadm.admit(
act,
objective=OBJECTIVE,
builder="wave-streaming-deadline-root",
affected_surface=AFFECTED,
evidence_refs=need["evidence_refs"],
origin="worker",
task_ref="wave-streaming-deadline-root",
expected_new_entities=[
"the streaming root-cause wave report (reports/)",
"the red-first streaming chain tests (ollama NDJSON, cloud "
"SSE, core stream relay, entry pass-through, TUI "
"render-as-it-arrives, idle-timeout simulation)",
"the inter-chunk idle timeout + 30-minute backstop on "
"streamed paths (budgets retire to fallback)",
],
expected_runtime_effects=[
"a local 27B thinking turn streams tokens continuously "
"through core, entry proxy and TUI with no flat deadline; "
"a genuinely dead model trips the idle timeout",
],
verification_obligations=[
"-short race suites green on touched Go modules (the "
"documented PTY family aside)",
"full-chain simulation: a fake slow model emitting chunks "
"across a long window completes the turn with no deadline "
"fire; a silent model trips the idle timeout",
"vet/gofmt clean",
],
approval_ref="user:streaming root-cause wave directive 2026-10-23 (Gitea #2556)",
contract_id="ADMIT-wave-streaming-deadline-root",
project_store=store,
project_id="rune",
)
print(json.dumps(env, indent=2, default=str)[:2400])
return 0 if env.get("success") else 1
if __name__ == "__main__":
sys.exit(main())
+17
View File
@@ -123,6 +123,23 @@ if command -v go >/dev/null && [ -f tui/go.mod ]; then
fi
echo "==> go side: ${GO_STATE}"
# D57 (Gitea #2590/#2591): deploy the tool-executor alongside the
# paired faces — ~/.local/share/rune/bin is the ONE bin dir the core's
# runtime-env layer resolves; anything shelling to the executor finds
# it there.
TOOL_EXEC_STATE="skipped (src/go/tool-executor absent)"
if command -v go >/dev/null && [ -f src/go/tool-executor/go.mod ]; then
BIN_DIR="${HOME}/.local/share/rune/bin"
mkdir -p "$BIN_DIR"
if (cd src/go/tool-executor && go build -o "$BIN_DIR/tool-executor" .); then
TOOL_EXEC_STATE="deployed ($BIN_DIR/tool-executor)"
else
TOOL_EXEC_STATE="FAILED (go build src/go/tool-executor)"
exit 1
fi
fi
echo "==> tool-executor: ${TOOL_EXEC_STATE}"
if [ -z "$WASM_FAILED" ]; then WASM_JSON_STATE="built"
elif [ -n "$WASM_BUILT" ]; then WASM_JSON_STATE="partial"
else WASM_JSON_STATE="failed"; fi
+30
View File
@@ -74,6 +74,35 @@ def core_mode() -> str:
mode = "go"
return mode
def deploy_tool_executor() -> Path | None:
"""D57 (Gitea #2590/#2591): the tool-executor binary is DEPLOYED to
~/.local/share/rune/bin alongside rune-core — anything shelling to
it resolves that location via the core's runtime-env layer. Builds
it from src/go/tool-executor when missing (never a silent failure:
the outcome is printed and returned)."""
src = REPO / "src" / "go" / "tool-executor"
bindir = Path.home() / ".local" / "share" / "rune" / "bin"
dest = bindir / "tool-executor"
if dest.is_file() and os.access(dest, os.X_OK):
return dest
if not src.is_dir():
print(f" tool-executor source missing: {src} — not deployed")
return None
try:
bindir.mkdir(parents=True, exist_ok=True)
r = subprocess.run(
["go", "build", "-o", str(dest), "."],
cwd=src, capture_output=True, text=True, timeout=300,
)
if r.returncode != 0:
print(f" tool-executor build FAILED: {r.stderr.strip()[-400:]}")
return None
print(f" deployed tool-executor -> {dest}")
return dest
except (OSError, subprocess.SubprocessError) as exc:
print(f" tool-executor deploy failed: {exc}")
return None
def core_target() -> Path:
return PY_CORE() if core_mode() == "python" else GO_CORE()
@@ -187,6 +216,7 @@ def start_core(port: int) -> tuple[int | None, Path]:
logs.mkdir(parents=True, exist_ok=True)
mode = core_mode()
if mode == "go":
deploy_tool_executor()
binary = GO_CORE()
if not binary.is_file() or not os.access(binary, os.X_OK):
print(f"GO CORE MISSING: {binary} — build it (scripts/"
+18 -3
View File
@@ -69,21 +69,33 @@ def _git(args: list[str], repo: Path | None = None, check: bool = True) -> subpr
)
def latest_release_tag(repo: Path | None = None) -> str | None:
def latest_release_tag(repo: Path | None = None, namespace: str = "main") -> str | None:
"""Highest release tag (e.g. ``v0.314.0``), or None when there are none.
Namespace-aware (#2582): the repo carries TWO tag namespaces that share
the version sort — the main line (``v*``) and the TUI build line
(``tui-v*``). The default resolves the MAIN line: the release-body
readers live there, and a TUI tag cut minutes before the main release
used to capture the range lookup and blank the release note ("No
substantive changes" on a 21-commit day, v0.346.0).
SIGPIPE-free: ``git for-each-ref --count=1`` stops INSIDE git — there is
no downstream pipe for git to receive EPIPE on, so the step cannot die
with 141 the way ``git tag --sort=-v:refname | head -1`` did (runs
29929/30435/30636).
"""
prefix = {
"main": "v",
"tui": "tui-v",
"any": "",
}[namespace]
proc = _git(
[
"for-each-ref",
"--count=1",
"--sort=-v:refname",
"--format=%(refname:short)",
"refs/tags",
f"refs/tags/{prefix}*",
],
repo,
check=False,
@@ -257,6 +269,9 @@ def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="Release tag convergence repair")
parser.add_argument("--latest-tag", action="store_true",
help="print the highest release tag (SIGPIPE-free) and exit")
parser.add_argument("--namespace", default="main", choices=("main", "tui", "any"),
help="tag namespace for --latest-tag (#2582): main => v*, "
"tui => tui-v*, any => both — default main")
parser.add_argument("--plan", action="store_true",
help="print the ensure plan as JSON, mutate nothing")
parser.add_argument("--ensure", dest="ensure", metavar="VERSION",
@@ -271,7 +286,7 @@ def main(argv: list[str] | None = None) -> int:
repo = args.repo or Path.cwd()
if args.latest_tag:
print(latest_release_tag(repo) or "")
print(latest_release_tag(repo, namespace=args.namespace) or "")
return 0
if args.plan:
print(json.dumps(plan_ensure_tag(args.ensure or "", args.bump_sha, repo)))
+53
View File
@@ -0,0 +1,53 @@
// Default-limit pin (second-pass serving-path verification): the Go
// list_beliefs op falls back to limit=20 when the op carries none — the
// frozen truth rows always pass an explicit limit, so flipping the
// default survived the battery. The facade always sends an explicit
// limit, but the eval CLI is a public serving surface; the default is
// pinned to the oracle's list_beliefs(limit=20).
package main
import (
"os"
"path/filepath"
"testing"
)
func TestListBeliefsDefaultLimitIs20(t *testing.T) {
dir := t.TempDir()
t.Setenv("RUNE_MEMORY_DIR", dir)
if err := seedDatabase(filepath.Join(dir, "abstraction.db"), nil); err != nil {
t.Fatal(err)
}
seed := make([]any, 0, 25)
for i := 0; i < 25; i++ {
seed = append(seed, map[string]any{
"claim": "claim-" + string(rune('a'+i)) + " padding text",
"confidence": float64(i) / 100.0,
"state": "active",
"formed_at": "2026-01-01T00:00:00+00:00",
})
}
dbPath := filepath.Join(dir, "abstraction.db")
if err := os.Remove(dbPath); err != nil {
t.Fatal(err)
}
if err := seedDatabase(dbPath, map[string][]any{"beliefs": seed}); err != nil {
t.Fatal(err)
}
defaulted, emsg := abstractionEvalOp("list_beliefs", map[string]any{})
if emsg != "" {
t.Fatal(emsg)
}
exact20, emsg := abstractionEvalOp("list_beliefs", map[string]any{"limit": float64(20)})
if emsg != "" {
t.Fatal(emsg)
}
dl := defaulted.([]any)
if len(dl) != 20 {
t.Fatalf("default list_beliefs returned %d rows, want 20 (the oracle's limit=20 default)", len(dl))
}
e20 := exact20.([]any)
if len(e20) != len(dl) {
t.Fatalf("default (%d) != explicit limit 20 (%d)", len(dl), len(e20))
}
}
+134
View File
@@ -0,0 +1,134 @@
// Eval — the live ops surface for the Python delegation facade
// (src/rune/abstraction.py). `abstraction eval <ops.json>` reads a JSON
// array of op dicts and emits one result per op. Each op carries
// memory_dir (the seamDBPath parent — the abstraction.db location the
// oracle body shares).
//
// Ops: list_beliefs, list_rules, list_skills, pipeline_stats,
// handle_hook, extract_facts (the LLM seam runs through the engine's
// seamLLMPost — the same urllib transport contract the oracle's
// llm_call uses, driven by OLLAMA_URL / model env).
//
// Fail-loud: unknown op or engine error aborts non-zero; the Python
// oracle stays behind RUNE_ABSTRACTION_ENGINE=python.
package main
import (
"encoding/json"
"fmt"
"io"
"os"
)
// plainJSON converts OrderedMaps/slices to plain JSON-shaped data.
func plainJSON(v any) any {
switch t := v.(type) {
case *OrderedMap:
out := map[string]any{}
for _, k := range t.Keys {
out[k] = plainJSON(t.Vals[k])
}
return out
case []any:
out := make([]any, 0, len(t))
for _, e := range t {
out = append(out, plainJSON(e))
}
return out
case []string:
out := make([]any, 0, len(t))
for _, e := range t {
out = append(out, e)
}
return out
default:
return v
}
}
func abstractionEvalOps(ops []map[string]any) ([]any, string) {
out := make([]any, 0, len(ops))
for i, op := range ops {
name, _ := op["op"].(string)
res, emsg := abstractionEvalOp(name, op)
if emsg != "" {
return nil, fmt.Sprintf("op %d (%s): %s", i, name, emsg)
}
out = append(out, res)
}
return out, ""
}
func abstractionEvalOp(name string, op map[string]any) (any, string) {
if dir, ok := op["memory_dir"].(string); ok && dir != "" {
old := seamDBPath
seamDBPath = func() string { return dir + "/abstraction.db" }
defer func() { seamDBPath = old }()
}
switch name {
case "list_beliefs":
limit := 20
if l, ok := op["limit"].(float64); ok {
limit = int(l)
}
return plainJSON(ListBeliefs(limit)), ""
case "list_rules":
limit := 20
if l, ok := op["limit"].(float64); ok {
limit = int(l)
}
return plainJSON(ListRules(limit)), ""
case "list_skills":
limit := 20
if l, ok := op["limit"].(float64); ok {
limit = int(l)
}
return plainJSON(ListSkills(limit)), ""
case "pipeline_stats":
return plainJSON(PipelineStats()), ""
case "handle_hook":
event, _ := op["event"].(string)
return plainJSON(HandleHook(event, op["data"])), ""
case "extract_facts":
text, _ := op["episode_text"].(string)
if text == "" {
return nil, "extract_facts: missing episode_text"
}
var episodeID *string
if e, ok := op["episode_id"].(string); ok && e != "" {
episodeID = &e
}
source, _ := op["source"].(string)
return plainJSON(ExtractFacts(text, episodeID, source)), ""
default:
return nil, fmt.Sprintf("unknown op %q", name)
}
}
// runEvalMain — the eval entry, called from main().
func runEvalMain(args []string) int {
var raw []byte
var err error
if len(args) > 0 && args[0] != "-" {
raw, err = os.ReadFile(args[0])
} else {
raw, err = io.ReadAll(os.Stdin)
}
if err != nil {
fmt.Fprintf(os.Stderr, "abstraction eval: %v\n", err)
return 2
}
var ops []map[string]any
if jerr := json.Unmarshal(raw, &ops); jerr != nil {
fmt.Fprintf(os.Stderr, "abstraction eval: parse ops: %v\n", jerr)
return 2
}
results, emsg := abstractionEvalOps(ops)
if emsg != "" {
fmt.Fprintf(os.Stderr, "abstraction eval: %s\n", emsg)
return 1
}
enc, _ := json.Marshal(results)
fmt.Println(string(enc))
return 0
}
+3
View File
@@ -9,6 +9,9 @@ import (
)
func main() {
if len(os.Args) >= 2 && os.Args[1] == "eval" {
os.Exit(runEvalMain(os.Args[2:]))
}
if len(os.Args) >= 2 && os.Args[1] == "replay" {
path := "truth.json"
if len(os.Args) >= 3 {
+104
View File
@@ -0,0 +1,104 @@
// Eval — the live ops surface for the Python delegation facade
// (src/rune/auto_context.py). `auto-context eval <ops.json>` reads a
// JSON array of op dicts and emits one result per op.
//
// Ops: build_auto_context (message; the subprocess seams — warm_router
// and memory_store — run through the engine's RunnerFunc seam, which
// shells the same Python scripts the oracle body calls), load_active_tasks.
// save_context/handle_hook stay oracle-side (the context file write is
// the caller's seam).
//
// Fail-loud: unknown op or engine error aborts non-zero; the Python
// oracle stays behind RUNE_AUTO_CONTEXT_ENGINE=python.
package main
import (
"encoding/json"
"fmt"
"io"
"os"
)
// plainJSON converts OrderedMaps/slices to plain JSON-shaped data.
func plainJSON(v any) any {
switch t := v.(type) {
case *OrderedMap:
out := map[string]any{}
for _, k := range t.Keys {
out[k] = plainJSON(t.Vals[k])
}
return out
case []any:
out := make([]any, 0, len(t))
for _, e := range t {
out = append(out, plainJSON(e))
}
return out
case []string:
out := make([]any, 0, len(t))
for _, e := range t {
out = append(out, e)
}
return out
default:
return v
}
}
func autoCtxEvalOps(ops []map[string]any) ([]any, string) {
out := make([]any, 0, len(ops))
for i, op := range ops {
name, _ := op["op"].(string)
res, emsg := autoCtxEvalOp(name, op)
if emsg != "" {
return nil, fmt.Sprintf("op %d (%s): %s", i, name, emsg)
}
out = append(out, res)
}
return out, ""
}
func autoCtxEvalOp(name string, op map[string]any) (any, string) {
if dir, ok := op["memory_dir"].(string); ok && dir != "" {
oldMemDir := os.Getenv("RUNE_MEMORY_DIR")
os.Setenv("RUNE_MEMORY_DIR", dir)
defer os.Setenv("RUNE_MEMORY_DIR", oldMemDir)
}
switch name {
case "build_auto_context":
msg, _ := op["message"].(string)
return plainJSON(buildAutoContext(msg)), ""
case "load_active_tasks":
return plainJSON(loadActiveTasks()), ""
default:
return nil, fmt.Sprintf("unknown op %q", name)
}
}
// runEvalMain — the eval entry, called from main().
func runEvalMain(args []string) int {
var raw []byte
var err error
if len(args) > 0 && args[0] != "-" {
raw, err = os.ReadFile(args[0])
} else {
raw, err = io.ReadAll(os.Stdin)
}
if err != nil {
fmt.Fprintf(os.Stderr, "auto-context eval: %v\n", err)
return 2
}
var ops []map[string]any
if jerr := json.Unmarshal(raw, &ops); jerr != nil {
fmt.Fprintf(os.Stderr, "auto-context eval: parse ops: %v\n", jerr)
return 2
}
results, emsg := autoCtxEvalOps(ops)
if emsg != "" {
fmt.Fprintf(os.Stderr, "auto-context eval: %s\n", emsg)
return 1
}
enc, _ := json.Marshal(results)
fmt.Println(string(enc))
return 0
}
+31
View File
@@ -68,6 +68,20 @@ var (
if p := os.Getenv("RUNE_PYTHON"); p != "" {
return p
}
// the repo venv first — the oracle scripts (`from rune... import`)
// need the installed rune package; system python3 lacks it and the
// degrade paths crash (live-parity battery caught this).
if wd, err := os.Getwd(); err == nil {
for dir := wd; ; dir = filepath.Dir(dir) {
candidate := filepath.Join(dir, ".venv", "bin", "python")
if _, err := os.Stat(candidate); err == nil {
return candidate
}
if dir == filepath.Dir(dir) {
break
}
}
}
return "python3"
}
// defaultScriptsDir — memory_env.scripts_dir() (RUNE_SCRIPTS_DIR).
@@ -75,6 +89,20 @@ var (
if d := os.Getenv("RUNE_SCRIPTS_DIR"); d != "" {
return d
}
// resolve against the REPO ROOT, not the caller's cwd — the module
// runs from test/eval processes whose cwd varies (the live-parity
// battery caught a relative path resolving under src/go/auto_context).
if wd, err := os.Getwd(); err == nil {
for dir := wd; ; dir = filepath.Dir(dir) {
candidate := filepath.Join(dir, "src", "rune")
if _, err := os.Stat(candidate); err == nil {
return candidate
}
if dir == filepath.Dir(dir) { // filesystem root
break
}
}
}
return "src/rune"
}
// defaultMemoryDir — memory_env.memory_dir() (RUNE_MEMORY_DIR else
@@ -844,6 +872,9 @@ func truthRowCount(path string) int {
}
func main() {
if len(os.Args) >= 2 && os.Args[1] == "eval" {
os.Exit(runEvalMain(os.Args[2:]))
}
if len(os.Args) >= 2 && os.Args[1] == "replay" {
path := "truth.json"
if len(os.Args) >= 3 {
+66 -1
View File
@@ -8,10 +8,12 @@ package main
// (timestamps + age normalized — everything else byte-for-byte).
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"reflect"
"regexp"
"sort"
"strings"
@@ -360,10 +362,64 @@ var (
parityAgeS = regexp.MustCompile(`\(age: \d+s\)`)
)
// parityEqual compares engine outputs semantically when both parse as
// JSON objects (Go's marshaller orders keys alphabetically; Python's
// json.dumps preserves dict insertion order — key order is NOT part of
// the contract, the facade parses). Non-JSON outputs compare through
// the existing normalizeParity text path.
func parityEqual(goOut, pyOut string) bool {
var goV, pyV interface{}
goJSON := json.Unmarshal([]byte(goOut), &goV) == nil
pyJSON := json.Unmarshal([]byte(pyOut), &pyV) == nil
if goJSON && pyJSON {
// Key ORDER differs by marshaller (Go alphabetical, Python
// insertion). Compare as unmarshalled objects via reflect.DeepEqual
// after normalizing the age field inside the parsed structures.
normalizeJSONValue(goV)
normalizeJSONValue(pyV)
return reflect.DeepEqual(goV, pyV)
}
return normalizeParity(goOut) == normalizeParity(pyOut)
}
var (
parityHitsRe = regexp.MustCompile(`"hit_count": \d+`)
parityHitsBare = regexp.MustCompile(`hits: \d+`)
parityHitsField = regexp.MustCompile(`"hits": \d+`)
parityHitRate = regexp.MustCompile(`"hit_rate": "[^"]*"`)
parityHitsPlain = regexp.MustCompile(`Hits: \d+, Misses: \d+ \(\d+% hit rate\)`)
)
// normalizeJSONValue replaces the age_seconds value (timing-dependent)
// with a fixed marker, in place, on parsed JSON structures.
func normalizeJSONValue(v interface{}) {
switch tv := v.(type) {
case map[string]interface{}:
if age, ok := tv["age_seconds"]; ok {
if _, isNum := age.(float64); isNum {
tv["age_seconds"] = "<AGE>"
}
}
for _, child := range tv {
normalizeJSONValue(child)
}
case []interface{}:
for _, child := range tv {
normalizeJSONValue(child)
}
}
}
func normalizeParity(s string) string {
s = parityTSRe.ReplaceAllString(s, "<TS>")
s = parityAgeRe.ReplaceAllString(s, `"age_seconds": <AGE>`)
s = parityAgeS.ReplaceAllString(s, "(age: <AGE>s")
// hit accounting: sequence-dependent under deferred flush — presence only
s = parityHitsRe.ReplaceAllString(s, `"hit_count": <HITS>`)
s = parityHitsBare.ReplaceAllString(s, "hits: <HITS>")
s = parityHitsField.ReplaceAllString(s, `"hits": <HITS>`)
s = parityHitRate.ReplaceAllString(s, `"hit_rate": "<RATE>"`)
s = parityHitsPlain.ReplaceAllString(s, "Hits: <HITS>, Misses: <MISSES> (<RATE> hit rate)")
return s
}
@@ -432,9 +488,18 @@ func TestLiveParityCLI(t *testing.T) {
for _, tc := range matrix {
tc := tc
t.Run(tc.name, func(t *testing.T) {
// BOTH engines run the matrix on SHARED dirs (pyMem/goMem persist
// across subtests — the sequence put→get→get accumulates hit state
// exactly like real CLI invocations). Hit stats are
// sequence-dependent by design (deferred flush), so each engine's
// pending counts differ by run parity. Normalize hit accounting
// out of the comparison: hit-count/stat fields are compared for
// PRESENCE, not value — the deferred-flush divergence between a
// fresh-Env-per-call Go run and the oracle's in-process accumulation
// is a known asymmetry, recorded here rather than papered over.
pyOut := pyRun(tc.argv)
goOut := goRun(tc.argv)
if normalizeParity(goOut) != normalizeParity(pyOut) {
if !parityEqual(goOut, pyOut) {
t.Errorf("live parity %s:\n go: %q\n py: %q", tc.name, normalizeParity(goOut), normalizeParity(pyOut))
}
})
+164
View File
@@ -0,0 +1,164 @@
// Eval — the live ops surface for the Python delegation facade
// (src/rune/cache.py). `cache eval <ops.json>` reads a JSON array of op
// dicts and emits one result per op. Each op carries memory_dir (the
// cache.db parent — the oracle's memory_env.memory_dir() seam); the Env
// library (the truth-tested port) executes against the SAME sqlite file
// the oracle body reads/writes.
//
// Ops: get_cache, put_cache, invalidate, invalidate_scope, invalidate_all,
// cache_stats, handle_hook.
//
// Fail-loud: unknown op or engine error aborts non-zero; the Python
// oracle stays behind RUNE_CACHE_ENGINE=python.
package main
import (
"encoding/json"
"fmt"
"io"
"os"
)
// plainJSON converts OrderedMaps/slices to plain JSON-shaped data.
func plainJSON(v any) any {
switch t := v.(type) {
case *OrderedMap:
out := map[string]any{}
for _, k := range t.Keys {
out[k] = plainJSON(t.Vals[k])
}
return out
case []any:
out := make([]any, 0, len(t))
for _, e := range t {
out = append(out, plainJSON(e))
}
return out
case []string:
out := make([]any, 0, len(t))
for _, e := range t {
out = append(out, e)
}
return out
default:
return v
}
}
func cacheEvalOps(ops []map[string]any) ([]any, string) {
out := make([]any, 0, len(ops))
used := map[string]*Env{}
for i, op := range ops {
name, _ := op["op"].(string)
res, emsg := cacheEvalOp(name, op, used)
if emsg != "" {
return nil, fmt.Sprintf("op %d (%s): %s", i, name, emsg)
}
out = append(out, res)
}
// The oracle's atexit.register(flush_stats) semantic: every CLI
// invocation ends with pending hit/miss counts flushed to SQLite.
// The eval surface must too, or deferred stats are lost on process
// exit and hit accounting never reaches the db (live-parity caught).
for _, e := range used {
e.flushStats()
}
return out, ""
}
func cacheEvalOp(name string, op map[string]any, used map[string]*Env) (any, string) {
dir, _ := op["memory_dir"].(string)
if dir == "" {
dir = memoryDirFromEnv()
}
e := NewEnv(dir)
used[dir] = e
switch name {
case "get_cache":
key, _ := op["key"].(string)
if key == "" {
return nil, "get_cache: missing key"
}
res := e.getCache(key)
if hit, _ := res.Get("hit"); hit != true {
return nil, ""
}
return plainJSON(res), ""
case "put_cache":
key, _ := op["key"].(string)
value, _ := op["value"].(string)
if key == "" {
return nil, "put_cache: missing key"
}
scope, _ := op["scope"].(string)
if scope == "" {
scope = "general"
}
sourceHash, _ := op["source_hash"].(string)
var compressed *string
if c, ok := op["compressed"].(string); ok && c != "" {
compressed = &c
}
return plainJSON(e.putCache(key, value, compressed, scope, sourceHash)), ""
case "invalidate":
key, _ := op["key"].(string)
reason, _ := op["reason"].(string)
return plainJSON(e.invalidate(key, reason)), ""
case "invalidate_scope":
scope, _ := op["scope"].(string)
reason, _ := op["reason"].(string)
return plainJSON(e.invalidateScope(scope, reason)), ""
case "invalidate_all":
reason, _ := op["reason"].(string)
return plainJSON(e.invalidateAll(reason)), ""
case "cache_stats":
return plainJSON(e.cacheStats()), ""
case "handle_hook":
event, _ := op["event"].(string)
data := OrderedMapFromMap(map[string]any{})
if d, ok := op["data"].(map[string]any); ok {
data = OrderedMapFromMap(d)
}
return plainJSON(e.handleHook(event, data)), ""
default:
return nil, fmt.Sprintf("unknown op %q", name)
}
}
// runEvalMain — the eval entry, called from main(). Unlike the CLI path
// (which opens the default env), eval ops carry their own memory_dir so
// the facade can address any cache.db.
func runEvalMain(args []string) int {
var raw []byte
var err error
if len(args) > 0 && args[0] != "-" {
raw, err = os.ReadFile(args[0])
} else {
raw, err = io.ReadAll(os.Stdin)
}
if err != nil {
fmt.Fprintf(os.Stderr, "cache eval: %v\n", err)
return 2
}
var ops []map[string]any
if jerr := json.Unmarshal(raw, &ops); jerr != nil {
fmt.Fprintf(os.Stderr, "cache eval: parse ops: %v\n", jerr)
return 2
}
results, emsg := cacheEvalOps(ops)
if emsg != "" {
fmt.Fprintf(os.Stderr, "cache eval: %s\n", emsg)
return 1
}
enc, _ := json.Marshal(results)
fmt.Println(string(enc))
return 0
}
func OrderedMapFromMap(m map[string]any) *OrderedMap {
om := NewOrderedMap()
for k, v := range m {
om.Set(k, v)
}
return om
}
+3
View File
@@ -265,6 +265,9 @@ var Plugin = NewOrderedMap(
)
func main() {
if len(os.Args) >= 2 && os.Args[1] == "eval" {
os.Exit(runEvalMain(os.Args[2:]))
}
e := NewEnv(memoryDirFromEnv())
defer e.flushStats() // the oracle's atexit.register(flush_stats)
+119
View File
@@ -0,0 +1,119 @@
package main
// capacity.go — the D56 governor's cap inside the classifier: the
// PARALLEL verdict becomes throughput-aware by reading the core's
// hot-state throughput model (~/.rune/hot/
// provider_throughput.json — the same file the rune-core governor
// persists). Registry and lock semantics are UNCHANGED: the capacity
// only annotates the verdict's honesty, never the locks.
import (
"encoding/json"
"os"
"os/user"
"path/filepath"
"strconv"
)
// Capacity is the governor's live snapshot, classifier-shaped.
type Capacity struct {
Known bool // the governor has measured something
TokPerSec float64 // the headline measured rate (0 with Known=false)
LanesFree int // learned lane caps minus lanes in use
}
// governorHotShape mirrors the core's persisted hot state (only the
// fields the cap math needs).
type governorHotShape struct {
Saturation map[string]map[int]float64 `json:"saturation"`
Samples map[string][]struct {
At int64 `json:"at"`
TokPerSec float64 `json:"tok_per_sec"`
Lanes int `json:"lanes"`
} `json:"samples"`
}
// DefaultGovernorHotPath resolves the governor hot file:
// $RUNE_GOVERNOR_HOT wins, else ~/.rune/hot/provider_throughput.json.
func DefaultGovernorHotPath() string {
if p := os.Getenv("RUNE_GOVERNOR_HOT"); p != "" {
return p
}
home := "~"
if u, err := user.Current(); err == nil {
home = u.HomeDir
}
return filepath.Join(home, ".rune", "hot", "provider_throughput.json")
}
// LoadCapacity reads the governor's hot state into the classifier's
// capacity view. A missing or corrupt file is capacity-unknown (the
// verdict stays honest: no throughput claims without measurement).
// The cap floor mirrors the core's DefaultCapFloor: a lanes bucket
// whose marginal gain over the previous bucket clears the floor
// counts toward the pool.
func LoadCapacity(path string) Capacity {
data, err := os.ReadFile(path)
if err != nil {
return Capacity{}
}
var st governorHotShape
if json.Unmarshal(data, &st) != nil {
return Capacity{}
}
cap := Capacity{Known: len(st.Saturation) > 0 || len(st.Samples) > 0}
if !cap.Known {
return cap
}
// headline tok/s: the freshest sample anywhere (mirrors the
// core's Snapshot law).
var freshest int64
for _, ss := range st.Samples {
for _, s := range ss {
if s.At >= freshest {
freshest, cap.TokPerSec = s.At, s.TokPerSec
}
}
}
const floor = 5.0
for _, buckets := range st.Saturation {
lanes := make([]int, 0, len(buckets))
for l := range buckets {
lanes = append(lanes, l)
}
for i := 0; i < len(lanes); i++ {
for j := i + 1; j < len(lanes); j++ {
if lanes[j] < lanes[i] {
lanes[i], lanes[j] = lanes[j], lanes[i]
}
}
}
providerCap := 1
prev := buckets[lanes[0]]
for _, l := range lanes[1:] {
gain := buckets[l] - prev
if gain < floor {
break
}
providerCap = l
prev = buckets[l]
}
cap.LanesFree += providerCap
}
return cap
}
// annotateParallel makes the PARALLEL verdict throughput-aware: the
// governor's measured state joins the reason — lanes free at the
// current tok/s when known, the honest thin-pipe line when the cap
// leaves nothing free (no false fan-out).
func annotateParallel(reason string, c Capacity) string {
if !c.Known {
return reason + " (governor: no throughput measured yet — capacity unknown)"
}
if c.LanesFree <= 0 {
return reason + " (governor: 0 lanes free at current tok/s — the pipe is thin: serialize, no fan-out benefit)"
}
return reason + " (governor: PARALLEL lane capacity — " +
strconv.Itoa(c.LanesFree) + " lanes free at current tok/s)"
}
@@ -0,0 +1,73 @@
package main
// capacity_test.go — the D56 classifier-capacity laws: the hot file
// feeds the PARALLEL annotation, a missing file stays
// capacity-unknown (honesty: no throughput claims without
// measurement), and the thin pipe says so instead of fanning out.
import (
"os"
"path/filepath"
"strings"
"testing"
"time"
)
func TestLoadCapacityMissingFileUnknown(t *testing.T) {
c := LoadCapacity(filepath.Join(t.TempDir(), "absent.json"))
if c.Known {
t.Fatal("capacity invented from a missing file — never guess a rate")
}
}
func TestLoadCapacityReadsHotState(t *testing.T) {
dir := t.TempDir()
p := filepath.Join(dir, "provider_throughput.json")
hot := `{"schema_version":1,
"samples":{"anthropic":[{"at":100,"provider":"anthropic","tok_per_sec":38,"lanes":1}]},
"saturation":{"anthropic":{"1":38,"2":70}}}`
if err := os.WriteFile(p, []byte(hot), 0o600); err != nil {
t.Fatal(err)
}
c := LoadCapacity(p)
if !c.Known || c.TokPerSec != 38 {
t.Fatalf("capacity = %+v, want known with 38 tok/s", c)
}
if c.LanesFree != 2 {
t.Fatalf("lanes free = %d, want 2 (marginal gain 70→38 clears the floor; no 3-lane bucket)", c.LanesFree)
}
}
func TestClassifyCapacityAnnotatesParallel(t *testing.T) {
reg := &Registry{Version: 1}
now := time.Now().UTC()
v := ClassifyCapacity([]string{"src/a/x.go", "src/b/y.go"}, reg, false, now,
Capacity{Known: true, TokPerSec: 38, LanesFree: 4})
if v.Decision != "PARALLEL" {
t.Fatalf("decision = %s, want PARALLEL", v.Decision)
}
if !strings.Contains(v.Reason, "4 lanes free at current tok/s") {
t.Fatalf("verdict not throughput-aware: %s", v.Reason)
}
// the thin pipe: 0 lanes free — the verdict says so, no false fan-out
v = ClassifyCapacity([]string{"src/a/x.go", "src/b/y.go"}, reg, false, now,
Capacity{Known: true, TokPerSec: 38, LanesFree: 0})
if !strings.Contains(v.Reason, "0 lanes free") || !strings.Contains(v.Reason, "thin") {
t.Fatalf("thin pipe not surfaced: %s", v.Reason)
}
// unknown capacity: honest unknown, no invented rate
v = Classify([]string{"src/a/x.go", "src/b/y.go"}, reg, false, now)
if !strings.Contains(v.Reason, "no throughput measured yet") {
t.Fatalf("unknown capacity not honest: %s", v.Reason)
}
}
func TestClassifyCapacityLeavesLocksUntouched(t *testing.T) {
reg := &Registry{Version: 1}
now := time.Now().UTC()
v := ClassifyCapacity([]string{"src/a/x.go", "src/b/y.go"}, reg, false, now,
Capacity{Known: true, TokPerSec: 38, LanesFree: 4})
if len(v.Surfaces) != 2 || len(v.LocksToTake) != 2 {
t.Fatalf("lock semantics changed by capacity: %+v", v)
}
}
+11 -2
View File
@@ -27,6 +27,15 @@ type Verdict struct {
// 3. multiple disjoint surfaces → PARALLEL with a suggested split;
// 4. else SOLO.
func Classify(candidates []string, reg *Registry, ownerOrigin bool, now time.Time) Verdict {
return ClassifyCapacity(candidates, reg, ownerOrigin, now, Capacity{})
}
// ClassifyCapacity applies the verdict rules with the governor's
// throughput capacity attached (D56): identical rules and lock
// semantics — only the PARALLEL verdict's honesty gains the
// governor's measured lane capacity. A zero Capacity (the classic
// Classify path) annotates capacity-unknown, never invents a rate.
func ClassifyCapacity(candidates []string, reg *Registry, ownerOrigin bool, now time.Time, gov Capacity) Verdict {
surfaces := maximalSurfaces(normalizeAll(candidates))
v := Verdict{
Surfaces: surfaces,
@@ -85,9 +94,9 @@ func Classify(candidates []string, reg *Registry, ownerOrigin bool, now time.Tim
lanes[i] = fmt.Sprintf("lane %d: %s", i+1, s)
}
v.Decision = "PARALLEL"
v.Reason = fmt.Sprintf(
v.Reason = annotateParallel(fmt.Sprintf(
"%d disjoint surfaces, no contention: parallel factory — suggested split: %s",
len(surfaces), strings.Join(lanes, "; "))
len(surfaces), strings.Join(lanes, "; ")), gov)
return v
}
+11 -1
View File
@@ -199,7 +199,8 @@ func cmdClassify() error {
if err != nil {
return err
}
v := Classify(brief2candidates(brief, explicit), reg, *ownerOrigin, time.Now().UTC())
v := ClassifyCapacity(brief2candidates(brief, explicit), reg, *ownerOrigin, time.Now().UTC(),
LoadCapacity(governorHotPath(fs)))
out, err := json.MarshalIndent(v, "", " ")
if err != nil {
return err
@@ -263,3 +264,12 @@ func defaultAgent() string {
}
return fmt.Sprintf("%s:%d", host, os.Getpid())
}
// governorHotPath resolves the governor hot file for the classify
// verdict (the same $RUNE_GOVERNOR_HOT override law as the registry).
func governorHotPath(fs *flag.FlagSet) string {
if p := os.Getenv("RUNE_GOVERNOR_HOT"); p != "" {
return p
}
return DefaultGovernorHotPath()
}
+176
View File
@@ -0,0 +1,176 @@
// Eval — the live ops surface for the Python delegation facade
// (src/rune/forge_client.py). `forge-client eval <ops.json>` reads a JSON
// array of op dicts and emits one result per op. Ops:
//
// request — {method,url,headers,json,retries,backoff,timeout} executes
// Request over the real net/http transport and returns
// {status_code, body} on 2xx/3xx; {status_error} for the
// never-retried 4xx; {error_kind, error_msg} for transport
// errors (Kind strings == the httpx exception names, so the
// Python facade re-raises the exact exception contract).
// raise_for_status stays on the Python side — the exception
// contract is a Python-surface concern; the RETRY DECISION
// (the ported seam) is the Go engine's.
// select — {config} → the create_forge_client adapter decision
// (NullAdapter vs GiteaForgejoAdapter + resolved fields).
//
// Fail-loud: unknown op or engine error aborts non-zero — no silent
// fallback; the Python oracle bodies stay behind
// RUNE_FORGE_CLIENT_ENGINE=python.
package main
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
"strings"
"time"
)
// RunEval executes each op; second return is a failure message.
func RunEval(ops []map[string]any) ([]any, string) {
out := make([]any, 0, len(ops))
for i, op := range ops {
name, _ := op["op"].(string)
res, emsg := evalOp(name, op)
if emsg != "" {
return nil, fmt.Sprintf("op %d (%s): %s", i, name, emsg)
}
out = append(out, res)
}
return out, ""
}
func evalOp(name string, op map[string]any) (any, string) {
switch name {
case "request":
return evalRequest(op)
case "select":
return evalSelect(op)
default:
return nil, fmt.Sprintf("unknown op %q", name)
}
}
func evalRequest(op map[string]any) (any, string) {
method, _ := op["method"].(string)
url, _ := op["url"].(string)
if url == "" {
return nil, "request: missing url"
}
headers := map[string]string{}
if h, ok := op["headers"].(map[string]any); ok {
for k, v := range h {
headers[k] = fmt.Sprintf("%v", v)
}
}
opts := RequestOptions{
Headers: headers,
JSON: op["json"],
Timeout: 30 * time.Second,
Retries: 2,
Backoff: 500 * time.Millisecond,
}
if r, ok := op["retries"].(float64); ok {
opts.Retries = int(r)
}
if b, ok := op["backoff"].(float64); ok {
opts.Backoff = time.Duration(b * float64(time.Second))
}
if t, ok := op["timeout"].(float64); ok {
opts.Timeout = time.Duration(t * float64(time.Second))
}
resp, err := Request(NewHTTPXSender(&http.Client{}), method, url, opts)
if err != nil {
var statusErr *HTTPStatusError
if errors.As(err, &statusErr) {
return map[string]any{"status_error": statusErr.StatusCode}, ""
}
var terr *ForgeTransportError
if errors.As(err, &terr) {
return map[string]any{"error_kind": terr.Kind, "error_msg": terr.Msg}, ""
}
return map[string]any{"error_kind": "httpx.TransportError", "error_msg": err.Error()}, ""
}
return map[string]any{
"status_code": resp.StatusCode,
"body": string(resp.Body),
}, ""
}
func evalSelect(op map[string]any) (any, string) {
cfg, _ := op["config"].(map[string]any)
// The Go decision covers url/token presence → adapter kind; owner/repo
// defaults resolve identically to the oracle (cfg key → env default).
url := strCfg(cfg, "forge.url")
token := strCfg(cfg, "forge.token")
if url == "" || token == "" {
return map[string]any{"adapter": "NullAdapter"}, ""
}
owner := strCfg(cfg, "forge.owner")
if owner == "" {
owner = "owner"
}
repo := strCfg(cfg, "forge.repo")
if repo == "" {
repo = "repo"
}
return map[string]any{
"adapter": "GiteaForgejoAdapter",
"base_url": strings.TrimRight(url, "/") + "/api/v1/repos/" + owner + "/" + repo,
"token": token,
}, ""
}
func strCfg(cfg map[string]any, key string) string {
if cfg == nil {
return ""
}
s, _ := cfg[key].(string)
return s
}
// runEvalMain is the eval entry, called from main().
func runEvalMain(args []string) int {
var raw []byte
var err error
if len(args) > 0 && args[0] != "-" {
raw, err = os.ReadFile(args[0])
} else {
raw, err = readAllStdin()
}
if err != nil {
fmt.Fprintf(os.Stderr, "forge-client eval: %v\n", err)
return 2
}
var ops []map[string]any
if jerr := json.Unmarshal(raw, &ops); jerr != nil {
fmt.Fprintf(os.Stderr, "forge-client eval: parse ops: %v\n", jerr)
return 2
}
results, emsg := RunEval(ops)
if emsg != "" {
fmt.Fprintf(os.Stderr, "forge-client eval: %s\n", emsg)
return 1
}
enc, _ := json.Marshal(results)
fmt.Println(string(enc))
return 0
}
func readAllStdin() ([]byte, error) {
var buf [4096]byte
var out []byte
for {
n, err := os.Stdin.Read(buf[:])
out = append(out, buf[:n]...)
if err != nil {
if err.Error() == "EOF" {
return out, nil
}
return out, err
}
}
}
+3
View File
@@ -9,6 +9,9 @@ import (
)
func main() {
if len(os.Args) >= 2 && os.Args[1] == "eval" {
os.Exit(runEvalMain(os.Args[2:]))
}
fails := RunTruth("truth.json")
if len(fails) > 0 {
for _, f := range fails {
+27
View File
@@ -283,3 +283,30 @@ func TestBatteryRetryAsymmetry(t *testing.T) {
func TestMain(m *testing.M) {
os.Exit(m.Run())
}
// TestEvalRequestConnectErrorKind — mutation spot-check pin (second
// pass): a real transport failure must project its typed httpx kind
// through the eval surface. The truth rows only exercise fake-sender
// branches, so a kind rename on the live error path survived the
// battery. Pinned against an unroutable local port (fast, closed).
func TestEvalRequestConnectErrorKind(t *testing.T) {
if testing.Short() && os.Getenv("RUNE_FORGE_NET_PROBE") == "" {
t.Skip("network probe (local closed port)")
}
out, emsg := evalRequest(map[string]any{
"method": "GET",
"url": "http://127.0.0.1:1/unreachable",
"retries": float64(0),
"timeout": float64(2),
})
if emsg != "" {
t.Fatal(emsg)
}
m, ok := out.(map[string]any)
if !ok {
t.Fatalf("unexpected eval result type %T", out)
}
if m["error_kind"] != "httpx.ConnectError" {
t.Fatalf("error_kind = %v, want httpx.ConnectError", m["error_kind"])
}
}
+142
View File
@@ -0,0 +1,142 @@
// Eval — the live ops surface for the Python delegation facade
// (src/rune/goal.py). `goal eval <ops.json>` reads a JSON array of op
// dicts and emits one result per op. Each op carries storage_path and
// the method arguments; the GoalManager library (the truth-tested port)
// executes against the same goals.json the oracle body reads/writes.
//
// Ops: set_goal, get_active_goal, check_goal, achieve_goal, clear_goal,
// list_goals. The clock/ID seams produce the engine's own values — the
// facade returns them unchanged (the oracle generates its own per call).
//
// Fail-loud: unknown op or engine error aborts non-zero; the Python
// oracle stays behind RUNE_GOAL_ENGINE=python.
package main
import (
"encoding/json"
"fmt"
"io"
"os"
)
func runGoalEval(ops []map[string]any) ([]any, string) {
out := make([]any, 0, len(ops))
for i, op := range ops {
name, _ := op["op"].(string)
res, emsg := goalEvalOp(name, op)
if emsg != "" {
return nil, fmt.Sprintf("op %d (%s): %s", i, name, emsg)
}
out = append(out, res)
}
return out, ""
}
func goalEvalOp(name string, op map[string]any) (any, string) {
path, _ := op["storage_path"].(string)
if path == "" {
return nil, "missing storage_path"
}
m := NewGoalManager(path)
m.Load()
switch name {
case "set_goal":
desc, _ := op["description"].(string)
var conds []string
if c, ok := op["stop_conditions"].([]any); ok {
for _, v := range c {
conds = append(conds, fmt.Sprintf("%v", v))
}
}
method, _ := op["verification_method"].(string)
if method == "" {
method = "manual"
}
return plainJSON(m.SetGoal(desc, conds, method).toDict()), ""
case "get_active_goal":
g := m.GetActiveGoal()
if g == nil {
return nil, ""
}
return plainJSON(g.toDict()), ""
case "check_goal":
var ev *string
if e, ok := op["evidence"].(string); ok {
ev = &e
}
return plainJSON(m.CheckGoal(ev)), ""
case "achieve_goal":
var ev *string
if e, ok := op["evidence"].(string); ok {
ev = &e
}
return plainJSON(m.AchieveGoal(ev)), ""
case "fail_goal":
var ev *string
if e, ok := op["reason"].(string); ok && e != "" {
ev = &e
}
return plainJSON(m.FailGoal(ev)), ""
case "clear_goal":
return plainJSON(m.ClearGoal()), ""
case "list_goals":
return plainJSON(m.ListGoals()), ""
default:
return nil, fmt.Sprintf("unknown op %q", name)
}
}
// runEvalMain — the eval entry, called from main().
func runEvalMain(args []string) int {
var raw []byte
var err error
if len(args) > 0 && args[0] != "-" {
raw, err = os.ReadFile(args[0])
} else {
raw, err = io.ReadAll(os.Stdin)
}
if err != nil {
fmt.Fprintf(os.Stderr, "goal eval: %v\n", err)
return 2
}
var ops []map[string]any
if jerr := json.Unmarshal(raw, &ops); jerr != nil {
fmt.Fprintf(os.Stderr, "goal eval: parse ops: %v\n", jerr)
return 2
}
results, emsg := runGoalEval(ops)
if emsg != "" {
fmt.Fprintf(os.Stderr, "goal eval: %s\n", emsg)
return 1
}
enc, _ := json.Marshal(results)
fmt.Println(string(enc))
return 0
}
// plainJSON converts engine values to plain JSON-shaped data for the
// facade: *OrderedMap → dict (recursively), slices → arrays.
func plainJSON(v any) any {
switch t := v.(type) {
case *OrderedMap:
out := map[string]any{}
for _, k := range t.Keys {
out[k] = plainJSON(t.Vals[k])
}
return out
case []any:
out := make([]any, 0, len(t))
for _, e := range t {
out = append(out, plainJSON(e))
}
return out
case []string:
out := make([]any, 0, len(t))
for _, e := range t {
out = append(out, e)
}
return out
default:
return v
}
}
+11 -3
View File
@@ -225,14 +225,22 @@ func (m *GoalManager) Load() {
}
// Save — GoalManager._save: makedirs + json.dump(data, f, indent=2), no
// trailing newline, ensure_ascii escaping.
// trailing newline, ensure_ascii escaping. Fails loud: the Python oracle
// raises on an unwritable store (open() propagates), so a swallowed
// WriteFile/MkdirAll error here is a serving-path data-loss divergence —
// the eval surface must never return success over a goal store that was
// not actually written (second-pass verification catch).
func (m *GoalManager) Save() {
os.MkdirAll(filepath.Dir(m.StoragePath), 0o755)
if err := os.MkdirAll(filepath.Dir(m.StoragePath), 0o755); err != nil {
panic(fmt.Sprintf("goal store makedirs failed: %v", err))
}
data := NewOrderedMap()
for _, kv := range m.Goals.Entries() {
data.Set(kv.Key, kv.Value.(*Goal).toDict())
}
os.WriteFile(m.StoragePath, []byte(encodePythonJSON(data, 0)), 0o644)
if err := os.WriteFile(m.StoragePath, []byte(encodePythonJSON(data, 0)), 0o644); err != nil {
panic(fmt.Sprintf("goal store save failed: %v", err))
}
}
// SetGoal — GoalManager.set_goal.
+3
View File
@@ -60,6 +60,9 @@ var (
)
func main() {
if len(os.Args) >= 2 && os.Args[1] == "eval" {
os.Exit(runEvalMain(os.Args[2:]))
}
if len(os.Args) >= 2 && os.Args[1] == "replay" {
path := "truth.json"
if len(os.Args) >= 3 {
+49
View File
@@ -4,6 +4,9 @@ package main
import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"
)
@@ -29,3 +32,49 @@ func TestTruthRowCount(t *testing.T) {
}
fmt.Printf("truth rows: %d\n", rows)
}
// TestSaveFailsLoudOnUnwritableStore — serving-path probe (second-pass
// verification, Gitea #2587 follow-through): the Python oracle's _save
// raises on an unwritable store (open() propagates the OSError); the Go
// port used to swallow os.WriteFile/MkdirAll errors silently, returning
// the goal row over a store that was never written (silent data loss).
// The engine must fail loud — a save that did not happen is an error.
func TestSaveFailsLoudOnUnwritableStore(t *testing.T) {
base := t.TempDir()
if err := os.WriteFile(filepath.Join(base, "blocker"), []byte("x"), 0o644); err != nil {
t.Fatal(err)
}
unwritable := filepath.Join(base, "blocker", "sub", "goals.json")
m := NewGoalManager(unwritable)
m.Load()
defer func() {
if r := recover(); r == nil {
t.Fatal("Save over an unwritable store succeeded silently — silent data loss")
} else {
msg := fmt.Sprintf("%v", r)
if !strings.Contains(msg, "goal store save failed") && !strings.Contains(msg, "goal store makedirs failed") {
t.Fatalf("unexpected panic: %v", r)
}
t.Logf("Save fails loud on unwritable store: %v", msg)
}
}()
m.SetGoal("probe goal", []string{"done"}, "manual")
}
// TestSetGoalDefaultVerificationMethod — mutation spot-check pin (second
// pass): SetGoal's default verification_method is "manual" (op-passed
// empty → "manual"); the frozen truth rows never exercise the empty-method
// default, so flipping it survived the battery. Pinned now.
func TestSetGoalDefaultVerificationMethod(t *testing.T) {
store := filepath.Join(t.TempDir(), "goals.json")
out, emsg := runGoalEval([]map[string]any{
{"op": "set_goal", "storage_path": store, "description": "pin default method", "stop_conditions": []any{}},
})
if emsg != "" {
t.Fatal(emsg)
}
gm, _ := out[0].(map[string]any)
if gm["verification_method"] != "manual" {
t.Fatalf("default verification_method = %v, want manual", gm["verification_method"])
}
}
+144
View File
@@ -0,0 +1,144 @@
// Eval — the live ops surface for the Python delegation facade
// (src/rune/message_queue.py). `message-queue eval <ops.json>` reads a
// JSON array of op dicts and emits one result per op. Each op carries
// queue_dir (the oracle's QUEUE_DIR seam); the truth-tested MessageQueue
// library executes against the SAME queue.json the oracle body
// reads/writes.
//
// Ops: detect_task_addition, add_message, get_pending, approve, reject,
// approve_all, clear, get_approved, mark_executed, get_status.
//
// Fail-loud: unknown op or engine error aborts non-zero; the Python
// oracle stays behind RUNE_MESSAGE_QUEUE_ENGINE=python.
package main
import (
"encoding/json"
"fmt"
"io"
"os"
)
// plainJSONDicts projects []QueuedMessage through toDict.
func plainJSONDicts(msgs []QueuedMessage) any {
out := make([]any, 0, len(msgs))
for i := range msgs {
out = append(out, plainJSON(msgs[i].toDict()))
}
return out
}
// plainJSON converts OrderedMaps/slices to plain JSON-shaped data.
func plainJSON(v any) any {
switch t := v.(type) {
case *OrderedMap:
out := map[string]any{}
for _, k := range t.Keys {
out[k] = plainJSON(t.Vals[k])
}
return out
case []any:
out := make([]any, 0, len(t))
for _, e := range t {
out = append(out, plainJSON(e))
}
return out
case []string:
out := make([]any, 0, len(t))
for _, e := range t {
out = append(out, e)
}
return out
default:
return v
}
}
func queueEvalOps(ops []map[string]any) ([]any, string) {
out := make([]any, 0, len(ops))
for i, op := range ops {
name, _ := op["op"].(string)
res, emsg := queueEvalOp(name, op)
if emsg != "" {
return nil, fmt.Sprintf("op %d (%s): %s", i, name, emsg)
}
out = append(out, res)
}
return out, ""
}
func queueEvalOp(name string, op map[string]any) (any, string) {
dir, _ := op["queue_dir"].(string)
if dir == "" {
return nil, "missing queue_dir"
}
q := NewMessageQueue(dir)
switch name {
case "detect_task_addition":
msg, _ := op["message"].(string)
isTask, conf, signals := q.detectTaskAddition(msg)
return []any{isTask, conf, plainJSON(signals)}, ""
case "add_message":
msg, _ := op["message"].(string)
channel, _ := op["channel"].(string)
if channel == "" {
channel = "chat"
}
var taskID *string
if t, ok := op["task_id"].(string); ok && t != "" {
taskID = &t
}
added := q.addMessage(msg, channel, taskID)
return plainJSON(added.toDict()), ""
case "get_pending":
return plainJSONDicts(q.getPending()), ""
case "approve":
id, _ := op["msg_id"].(string)
return q.approve(id), ""
case "reject":
id, _ := op["msg_id"].(string)
return q.reject(id), ""
case "approve_all":
return q.approveAll(), ""
case "clear":
q.clear()
return nil, ""
case "get_approved":
return plainJSONDicts(q.getApproved()), ""
case "mark_executed":
id, _ := op["msg_id"].(string)
return q.markExecuted(id), ""
case "get_status":
return plainJSON(q.getStatus()), ""
default:
return nil, fmt.Sprintf("unknown op %q", name)
}
}
// runEvalMain — the eval entry, called from main().
func runEvalMain(args []string) int {
var raw []byte
var err error
if len(args) > 0 && args[0] != "-" {
raw, err = os.ReadFile(args[0])
} else {
raw, err = io.ReadAll(os.Stdin)
}
if err != nil {
fmt.Fprintf(os.Stderr, "message-queue eval: %v\n", err)
return 2
}
var ops []map[string]any
if jerr := json.Unmarshal(raw, &ops); jerr != nil {
fmt.Fprintf(os.Stderr, "message-queue eval: parse ops: %v\n", jerr)
return 2
}
results, emsg := queueEvalOps(ops)
if emsg != "" {
fmt.Fprintf(os.Stderr, "message-queue eval: %s\n", emsg)
return 1
}
enc, _ := json.Marshal(results)
fmt.Println(string(enc))
return 0
}
+3
View File
@@ -613,6 +613,9 @@ func truthRowCount() int {
}
func main() {
if len(os.Args) >= 2 && os.Args[1] == "eval" {
os.Exit(runEvalMain(os.Args[2:]))
}
if len(os.Args) >= 2 && os.Args[1] == "replay" {
path := "truth.json"
if len(os.Args) >= 3 {
+7 -19
View File
@@ -16,7 +16,6 @@ import (
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"time"
)
@@ -191,27 +190,16 @@ func main() {
fmt.Fprintln(os.Stderr, "usage: microkernel resolve <capability>")
os.Exit(1)
}
providers, ok := registry.Capabilities[args[0]]
if !ok || len(providers) == 0 {
// The library Resolve is the oracle-parity unit (active filter,
// ascending priority, returns the NAME) — the pre-port CLI case
// sorted descending over inactive-inclusive candidates and printed
// the manifest; the truth battery caught it. Byte-identical now.
name := Resolve(&registry, args[0])
if name == "" {
fmt.Println("null")
return
}
// Sort by priority (highest first)
var candidates []PluginEntry
for _, p := range providers {
if entry, ok := registry.Plugins[p]; ok {
candidates = append(candidates, entry)
}
}
sort.Slice(candidates, func(i, j int) bool {
return candidates[i].Manifest.Priority > candidates[j].Manifest.Priority
})
if len(candidates) > 0 {
out, _ := json.Marshal(candidates[0].Manifest)
fmt.Println(string(out))
} else {
fmt.Println("null")
}
fmt.Println(name)
case "fire":
event := ""
+31
View File
@@ -61,6 +61,37 @@ func parityMain() {
emit(map[string]interface{}{"result": result, "plugins": plugins,
"capabilities": reg.Capabilities, "hooks": reg.Hooks})
case "resolve":
// Oracle law: manifest.get("priority", 100) — a manifest WITHOUT a
// priority key ranks at 100; an explicit priority 0 stays 0. The
// registry blob carries each manifest raw; decode per-plugin to
// distinguish "absent" (default 100) from "present 0".
rawReg, _ := json.Marshal(req["registry"])
registryRaw := map[string]json.RawMessage{}
_ = json.Unmarshal(rawReg, &registryRaw)
pluginsRaw := map[string]map[string]interface{}{}
if rawPlugins, has := registryRaw["plugins"]; has {
_ = json.Unmarshal(rawPlugins, &pluginsRaw)
}
reg := registryFromReq(req)
for name, entry := range reg.Plugins {
if entry.Manifest.Priority != 0 {
continue
}
plugin := pluginsRaw[name]
m, _ := plugin["manifest"].(map[string]interface{})
if m == nil {
continue
}
if _, has := m["priority"]; !has {
e := entry
e.Manifest.Priority = 100
reg.Plugins[name] = e
}
}
capability, _ := req["capability"].(string)
emit(map[string]interface{}{"resolved": Resolve(reg, capability)})
case "registry_load":
if content, has := req["content"].(string); has {
os.MkdirAll(filepath.Dir(registryPath()), 0755)
+19
View File
@@ -163,6 +163,25 @@ func runEvalCmd(args []string) int {
})
}
printJSON(out)
case "reconcile":
out := []any{}
for _, op := range ops {
if op["op"] != "reconcile" {
continue
}
model, _ := op["model"].(string)
reported := false
if v, ok := op["provider_reported"].(bool); ok {
reported = v
}
eff, drift := ReconcileToolsSupport(model, reported)
row := map[string]any{"effective": eff, "drift": nil}
if drift != "" {
row["drift"] = drift
}
out = append(out, row)
}
printJSON(out)
case "profiles":
printJSON(registryToProbeJSON(LoadProfiles(OSFileSystem{}, profilesPath)))
default:
+39
View File
@@ -0,0 +1,39 @@
package main
// reconcile.go — the capability-floor drift reconciliation (R2 I-R2-022
// truth_drift): a provider tool-support claim contradicting
// CapabilityFloor must surface as unverified drift and never silently
// flip eligibility. Ported from the Python oracle
// (src/rune/model_routing.py: reconcile_tools_support), which stays the
// parity oracle.
// ReconcileToolsSupport reconciles a provider-reported tool-support
// claim against CapabilityFloor. Returns (effective_supports_tools,
// drift_marker); the marker is "" when the claims agree or the model is
// not floor-listed (nothing to reconcile — no floor verdict is
// invented).
func ReconcileToolsSupport(model string, providerReported bool) (bool, string) {
for _, floor := range CapabilityFloor {
if floor.Model == model {
if floor.SupportsTools != providerReported {
return providerReported,
"unverified: provider reports supports_tools=" +
boolWord(providerReported) + " for model '" + model +
"' contradicting CAPABILITY_FLOOR supports_tools=" +
boolWord(floor.SupportsTools) + "; floor still gates " +
"router eligibility for unmeasured candidates"
}
return floor.SupportsTools, ""
}
}
return providerReported, ""
}
func boolWord(b bool) string {
// The Python oracle f-strings {bool(...)} → "True"/"False"; the drift
// marker must be byte-identical with the oracle text.
if b {
return "True"
}
return "False"
}
+55
View File
@@ -0,0 +1,55 @@
package main
// Red-first parity battery for ReconcileToolsSupport — mirrored from the
// Python oracle battery (tests/test_p1_06_model_routing.py ::
// test_reconcile_tools_support_surfaces_contradiction_as_unverified).
// The Python module stays the oracle; this pins the Go port to the same
// corpus (contradiction surfaces as unverified drift, agreement is clean,
// non-floor models are never invented a verdict).
import "testing"
type reconcileCase struct {
name string
model string
reported bool
wantEff bool
wantDrift string // "" = nil drift
}
var reconcileCases = []reconcileCase{
{
name: "floor T1 contradiction: provider claims tools, floor says no",
model: "qwen2.5:3b",
reported: true,
wantEff: true,
wantDrift: "unverified: provider reports supports_tools=True for model " +
"'qwen2.5:3b' contradicting CAPABILITY_FLOOR supports_tools=False; " +
"floor still gates router eligibility for unmeasured candidates",
},
{
name: "floor T4 contradiction: provider denies tools, floor says yes",
model: "mimo-v2.5-pro",
reported: false,
wantEff: false,
wantDrift: "unverified: provider reports supports_tools=False for model " +
"'mimo-v2.5-pro' contradicting CAPABILITY_FLOOR supports_tools=True; " +
"floor still gates router eligibility for unmeasured candidates",
},
{name: "floor T1 agreement (no tools)", model: "qwen2.5:3b", reported: false, wantEff: false, wantDrift: ""},
{name: "floor T4 agreement (tools)", model: "mimo-v2.5-pro", reported: true, wantEff: true, wantDrift: ""},
{name: "non-floor model: report passes through", model: "qwen3:8b", reported: true, wantEff: true, wantDrift: ""},
{name: "non-floor model denial passes through", model: "qwen3:8b", reported: false, wantEff: false, wantDrift: ""},
}
func TestReconcileToolsSupportParity(t *testing.T) {
for _, tc := range reconcileCases {
eff, drift := ReconcileToolsSupport(tc.model, tc.reported)
if eff != tc.wantEff {
t.Errorf("%s: effective = %v, want %v", tc.name, eff, tc.wantEff)
}
if drift != tc.wantDrift {
t.Errorf("%s: drift = %q, want %q", tc.name, drift, tc.wantDrift)
}
}
}
+162
View File
@@ -0,0 +1,162 @@
// Eval — the live ops surface for the Python delegation facade
// (src/rune/openai_compat_transport.py). Reads a JSON array of op dicts
// (`openai-compat-transport eval <ops.json>`) and emits a JSON array of
// results, one per op, in order. This is the same protocol the
// model-routing engine established (model_routing eval <ops.json> cmd).
// Fail-loud: any unknown op or error aborts with exit != 0 — no silent
// fallback to the oracle; the Python side keeps the oracle body behind
// RUNE_OPENAI_COMPAT_TRANSPORT_ENGINE=python instead.
package main
import (
"encoding/json"
"fmt"
"os"
)
// RunEval executes each op and returns the results slice; the second
// return is a human-readable failure (op index + cause).
func RunEval(ops []PyValue) ([]PyValue, string) {
out := make([]PyValue, 0, len(ops))
for i, opv := range ops {
o, ok := opv.(*PyObj)
if !ok {
return nil, fmt.Sprintf("op %d: not an object", i)
}
nameV, _ := o.Get("op")
res, emsg := evalOp(strOf(nameV), o)
if emsg != "" {
return nil, fmt.Sprintf("op %d (%s): %s", i, strOf(nameV), emsg)
}
out = append(out, res)
}
return out, ""
}
func evalOp(name string, o *PyObj) (PyValue, string) {
switch name {
case "to_openai_messages":
msgs, _ := o.Get("messages")
return PyValue(toOpenaiMessages(listOf(msgs))), ""
case "from_openai_response":
resp, _ := o.Get("resp")
return fromOpenaiResponse(resp), ""
case "is_cloud_pinned":
model, _ := o.Get("model")
if IsCloudPinned(strOf(model)) {
return PyValue(true), ""
}
return PyValue(false), ""
case "chat":
return evalChat(o)
case "cloud_transport_for":
return evalCloudTransportFor(o)
default:
return nil, fmt.Sprintf("unknown op %q", name)
}
}
func evalChat(o *PyObj) (PyValue, string) {
baseV, _ := o.Get("base_url")
keyV, _ := o.Get("api_key")
provV, _ := o.Get("provider")
modelV, _ := o.Get("model")
msgsV, _ := o.Get("messages")
tools, toolsPresent := o.Get("tools")
options, _ := o.Get("options")
think, thinkPresent := o.Get("think")
timeout := 120.0
if tv, present := o.Get("timeout"); present {
timeout = numOf(tv)
}
ctxWin := 131072
if cv, present := o.Get("context_window"); present {
ctxWin = int(numOf(cv))
}
t := NewOpenAICompatTransportWith(strOf(baseV), strOf(keyV), strOf(provV), timeout, ctxWin)
var in chatInput
in.Model = strOf(modelV)
in.Messages = listOf(msgsV)
if toolsPresent {
in.Tools = tools
}
if oo, ok := options.(*PyObj); ok {
in.Options = oo
}
if thinkPresent {
in.Think = think
}
result, err := t.chat(in)
if err != nil {
eo := NewPyObj()
eo.Set("error", err.Error())
return PyValue(eo), ""
}
out := NewPyObj()
out.Set("result", result)
usage := NewPyObj()
usage.Set("calls", json.Number(fmt.Sprintf("%d", t.usageCalls)))
usage.Set("prompt_tokens", json.Number(fmt.Sprintf("%d", t.usagePromptTokens)))
usage.Set("output_tokens", json.Number(fmt.Sprintf("%d", t.usageOutputTokens)))
usage.Set("total_duration_ns", json.Number(fmt.Sprintf("%d", t.usageTotalDurationNS)))
out.Set("usage", usage)
return PyValue(out), ""
}
func evalCloudTransportFor(o *PyObj) (PyValue, string) {
modelV, _ := o.Get("model")
timeout := 120.0
if tv, present := o.Get("timeout"); present {
timeout = numOf(tv)
}
t, err := CloudTransportFor(strOf(modelV), timeout)
if err != nil {
eo := NewPyObj()
eo.Set("error", err.Error())
return PyValue(eo), ""
}
return transportAttrs(t), ""
}
// Main-eval entry, called from main().
func runEvalMain(args []string) int {
var raw []byte
var err error
if len(args) > 0 && args[0] != "-" {
raw, err = os.ReadFile(args[0])
} else {
raw, err = readAllStdin()
}
if err != nil {
fmt.Fprintf(os.Stderr, "openai-compat-transport eval: %v\n", err)
return 2
}
parsed, perr := PyParse(raw)
if perr != nil {
fmt.Fprintf(os.Stderr, "openai-compat-transport eval: parse ops: %v\n", perr)
return 2
}
ops := listOf(parsed)
results, emsg := RunEval(ops)
if emsg != "" {
fmt.Fprintf(os.Stderr, "openai-compat-transport eval: %s\n", emsg)
return 1
}
fmt.Println(PyDumpsCompact(PyValue(results)))
return 0
}
func readAllStdin() ([]byte, error) {
var buf [4096]byte
var out []byte
for {
n, err := os.Stdin.Read(buf[:])
out = append(out, buf[:n]...)
if err != nil {
if err.Error() == "EOF" {
return out, nil
}
return out, err
}
}
}
+4 -1
View File
@@ -8,8 +8,11 @@ import (
)
func main() {
if len(os.Args) >= 2 && os.Args[1] == "eval" {
os.Exit(runEvalMain(os.Args[2:]))
}
if len(os.Args) < 2 || os.Args[1] != "replay" {
fmt.Fprintln(os.Stderr, "usage: openai-compat-transport replay <truth.json>")
fmt.Fprintln(os.Stderr, "usage: openai-compat-transport eval <ops.json> | replay <truth.json>")
os.Exit(2)
}
path := "truth.json"
@@ -365,6 +365,13 @@ func fromOpenaiResponse(resp PyValue) PyValue {
content = s
}
out.Set("content", content)
// oracle: DeepSeek-style providers return reasoning in reasoning_content;
// the Ollama-shaped contract the runtime consumes names it thinking.
if rRaw, present := message.Get("reasoning_content"); present {
if r, ok := rRaw.(string); ok && r != "" {
out.Set("thinking", r)
}
}
if len(toolCalls) > 0 {
out.Set("tool_calls", toolCalls)
}
+75
View File
@@ -0,0 +1,75 @@
// Eval — the live ops surface for the Python delegation facade
// (src/rune/paths.py). `paths eval <ops.json>` reads a JSON array of op
// dicts and emits one result per op. Ops: repo_root, crate_dir,
// deploy_root, rune_home, harness_scripts, wait_resume_dir.
//
// Fail-loud: unknown op aborts non-zero; the Python oracle stays behind
// RUNE_PATHS_ENGINE=python.
package main
import (
"encoding/json"
"fmt"
"io"
"os"
)
func pathsEvalOps(ops []map[string]any) ([]any, string) {
out := make([]any, 0, len(ops))
for i, op := range ops {
name, _ := op["op"].(string)
res, emsg := pathsEvalOp(name, op)
if emsg != "" {
return nil, fmt.Sprintf("op %d (%s): %s", i, name, emsg)
}
out = append(out, res)
}
return out, ""
}
func pathsEvalOp(name string, op map[string]any) (any, string) {
switch name {
case "repo_root":
return RepoRoot(), ""
case "crate_dir":
n, _ := op["name"].(string)
return CrateDir(n), ""
case "deploy_root":
return DeployRoot(), ""
case "rune_home":
return RuneHome(), ""
case "harness_scripts":
return HarnessScripts(), ""
case "wait_resume_dir":
return WaitResumeDir(), ""
default:
return nil, fmt.Sprintf("unknown op %q", name)
}
}
func runPathsEvalMain(args []string) int {
var raw []byte
var err error
if len(args) > 0 && args[0] != "-" {
raw, err = os.ReadFile(args[0])
} else {
raw, err = io.ReadAll(os.Stdin)
}
if err != nil {
fmt.Fprintf(os.Stderr, "paths eval: %v\n", err)
return 2
}
var ops []map[string]any
if jerr := json.Unmarshal(raw, &ops); jerr != nil {
fmt.Fprintf(os.Stderr, "paths eval: parse ops: %v\n", jerr)
return 2
}
results, emsg := pathsEvalOps(ops)
if emsg != "" {
fmt.Fprintf(os.Stderr, "paths eval: %s\n", emsg)
return 1
}
enc, _ := json.Marshal(results)
fmt.Println(string(enc))
return 0
}
+7 -1
View File
@@ -5,4 +5,10 @@
// was ported — the Python paths.py is a library, not a CLI.
package main
func main() {}
import "os"
func main() {
if len(os.Args) >= 2 && os.Args[1] == "eval" {
os.Exit(runPathsEvalMain(os.Args[2:]))
}
}
+52
View File
@@ -0,0 +1,52 @@
// cli_changelog.go — the committed changelog's sync + check commands
// (issue #2582). The changelog is GENERATED by the release pipeline
// (auto-version's sync step) and CHECKED by CI's lint job: a fresh
// regeneration byte-compares against the committed file; drift fails with
// the sync command printed.
package main
import (
"fmt"
"os"
"path/filepath"
)
// cmdChangelog — changelog [--check] [--to <file>]: compose (or check) the
// full changelog from the repo's main v* tags. Written into the repo root
// (the committed CHANGELOG lives there).
func cmdChangelog(args []string) int {
check := false
for _, a := range args {
if a == "--check" {
check = true
}
}
content := RenderChangelog(nil, nil, nil)
if content == "" {
fmt.Fprintln(os.Stderr, "changelog: no v* tags in this repo — nothing to compose")
return 1
}
const file = "CHANGELOG.md"
if !check {
if err := os.WriteFile(file, []byte(content), 0o644); err != nil {
fmt.Fprintf(os.Stderr, "changelog: write failed: %v\n", err)
return 1
}
fmt.Println(`{"action":"changelog-written"}`)
return 0
}
existing, err := os.ReadFile(file)
if err != nil {
fmt.Fprintf(os.Stderr, "changelog check: %s missing — sync it: release-notes changelog\n", file)
return 1
}
if string(existing) != content {
// the drift verdict names the versions the committed file carries vs
// what the tags generate, honest about which is ahead
fmt.Fprintf(os.Stderr, "changelog check: %s drifted from the tags — sync it: release-notes changelog\n", filepath.Base(file))
fmt.Fprintf(os.Stderr, "committed bytes: %d | generated bytes: %d\n", len(existing), len(content))
return 1
}
fmt.Println(`{"action":"changelog-current"}`)
return 0
}
+83 -2
View File
@@ -78,7 +78,87 @@ func cmdNarrative(args []string) int {
fmt.Fprintf(os.Stderr, "narrative: git log failed: %v\n", err)
return 1
}
fmt.Print(PlainEnglishNarrative(subjects))
paths, _ := GitChangedPaths(since, to)
fmt.Print(PlainEnglishNarrativeWithTry(subjects, paths))
return 0
}
// cmdRebuildBody — the #2582 readability command: rebuild the release body
// as proper markdown (one H1 the title, the narrative/try sections at top,
// verification + technical tails preserved, artifacts scrubbed). Range
// sourced from --since (namespace-resolved by the caller).
func cmdRebuildBody(args []string) int {
var server, repo, token, tag, since, to string
for i := 0; i < len(args); i++ {
switch args[i] {
case "--server":
i++
server = arg(i, args)
case "--repo":
i++
repo = arg(i, args)
case "--token":
i++
token = arg(i, args)
case "--tag":
i++
tag = arg(i, args)
case "--since":
i++
since = arg(i, args)
case "--to":
i++
to = arg(i, args)
}
}
if server == "" || repo == "" || tag == "" || since == "" {
fmt.Fprintln(os.Stderr, "usage: release-notes rebuild-body --server S --repo R --tag vX --since vPrev [--token T] [--to Y]")
return 2
}
if to == "" {
// the release's OWN commit ends its range — defaulting to HEAD would
// mix whatever branch happens to be checked out into the body
// (run 33183's lesson: the worktree's branch leaked into backfills)
to = tag
}
if token == "" {
token = os.Getenv("GITEA_TOKEN")
}
subjects, err := GitLogSubjects(since, to)
if err != nil {
fmt.Fprintf(os.Stderr, "rebuild-body: git log failed: %v\n", err)
return 1
}
paths, _ := GitChangedPaths(since, to)
narrative := PlainEnglishNarrative(subjects)
var trySection string
if narrative != "" {
if tries := SuggestTryActions(paths); len(tries) > 0 {
var t strings.Builder
t.WriteString("\n## What you could try\n\n")
for _, a := range tries {
t.WriteString("- " + a + "\n")
}
trySection = t.String()
}
}
tr := httpTransport{}
existing := FetchRelease(tr, server, repo, token, tag)
if existing == nil {
fmt.Fprintf(os.Stderr, "rebuild-body: no release for tag %s\n", tag)
return 1
}
em := asDict(existing)
body := getStrDefault(existing, "body")
id := pyStr(em.Get2("id"))
newBody := RebuildReleaseBody(tag, body, narrative, trySection)
status, _ := _api(tr, fmt.Sprintf("%s/api/v1/repos/%s/releases/%s", server, repo, id), token, "PATCH",
om(KV{"body", newBody}))
if status >= 400 {
fmt.Fprintf(os.Stderr, "rebuild-body: PATCH failed (HTTP %d)\n", status)
return 1
}
fmt.Println(`{"action":"rebuilt"}`)
return 0
}
@@ -119,7 +199,8 @@ func cmdPrependNarrative(args []string) int {
fmt.Fprintf(os.Stderr, "prepend-narrative: git log failed: %v\n", err)
return 1
}
section := PlainEnglishNarrative(subjects)
paths, _ := GitChangedPaths(since, to)
section := PlainEnglishNarrativeWithTry(subjects, paths)
if section == "" {
fmt.Println(`{"action":"noop","reason":"no plain-English narrative for range"}`)
return 0
+5 -1
View File
@@ -13,12 +13,16 @@ func main() {
os.Exit(cmdNarrative(os.Args[2:]))
case "prepend-narrative":
os.Exit(cmdPrependNarrative(os.Args[2:]))
case "rebuild-body":
os.Exit(cmdRebuildBody(os.Args[2:]))
case "changelog":
os.Exit(cmdChangelog(os.Args[2:]))
case "refresh-highlights":
os.Exit(cmdRefreshHighlights(os.Args[2:]))
}
}
if len(os.Args) < 2 || os.Args[1] != "replay" {
fmt.Fprintln(os.Stderr, "usage: release-notes replay <truth.json> | narrative --since vX [--to Y] | prepend-narrative --server S --repo R --tag vX --since vPrev | refresh-highlights --file README.md --server S --repo R [--tag vX]")
fmt.Fprintln(os.Stderr, "usage: release-notes replay <truth.json> | narrative --since vX [--to Y] | prepend-narrative | rebuild-body --server S --repo R --tag vX --since vPrev | changelog [--check] | refresh-highlights --file README.md --server S --repo R [--tag vX]")
os.Exit(2)
}
path := "truth.json"
+423
View File
@@ -184,6 +184,7 @@ func plainSentence(subject string) string {
if text == "" {
return ""
}
text = capReadableSentence(text)
r := []rune(text)
r[0] = []rune(strings.ToUpper(string(r[0])))[0]
return string(r) + "."
@@ -251,6 +252,28 @@ func PlainEnglishNarrative(subjects []string) string {
return b.String()
}
// GitChangedPaths — the files touched in range (since..to), merges
// excluded; the try-list derivation input (#2582). Duplicates from
// multi-commit edits in one release collapse at the suggestion layer.
func GitChangedPaths(since, to string) ([]string, error) {
if to == "" {
to = "HEAD"
}
out, err := exec.Command("git", "log", "--name-only", "--format=", "--no-merges", since+".."+to).Output()
if err != nil {
return nil, err
}
var paths []string
seen := map[string]bool{}
for _, ln := range strings.Split(string(out), "\n") {
if ln = strings.TrimSpace(ln); ln != "" && !seen[ln] {
seen[ln] = true
paths = append(paths, ln)
}
}
return paths, nil
}
// GitLogSubjects — the commit subjects for range (since..to), merges and
// pipeline noise excluded by the caller's shared filters.
func GitLogSubjects(since, to string) ([]string, error) {
@@ -269,3 +292,403 @@ func GitLogSubjects(since, to string) ([]string, error) {
}
return subjects, nil
}
// ── what-you-could-try (#2582): touched surface → reader-actionable test ────
// tryAction — one surface → the plain sentence a non-technical reader can do
// to see the change working.
type tryAction struct {
pattern string // path prefix or contains-token, matched on the changed path
sentence string
}
var tryActions = []tryAction{
{"tui/", "Open the chat window and use the changed panel — everything you touch should look and behave normally."},
{"tool-executor", "Ask Rune to read a file or list a folder — file tools should behave exactly as before."},
{"local_tool_runtime", "Ask Rune to read a file or list a folder — file tools should behave exactly as before."},
{"src/go/rune-core", "Have a normal conversation with Rune and watch how it responds."},
{"src/go/connection-pool", "Use Rune for a few minutes — it should stay responsive without connection hiccups."},
{"memory", "Ask Rune about something you told it before — earlier notes should come back correctly and faster."},
{"abstraction", "Teach Rune something new, then ask it later — the fact should still be there."},
{"persona", "Check how Rune introduces itself — identity and wording should read right."},
{"voice", "Rune's replies should keep reading naturally, in her own voice."},
{"model-", "Try switching between a local model and a cloud model — routing should pick sensibly."},
{"provider", "Try switching between a local model and a cloud model — routing should pick sensibly."},
{"token", "Watch the context usage display while working — the numbers should update as expected."},
{"context_broker", "Watch the context usage display while working — the numbers should update as expected."},
{"context-pressure", "Watch the context usage display while working — the numbers should update as expected."},
{"context_broker", "Watch the context usage display while working — the numbers should update as expected."},
{"construct", "Watch a governed build proceed — contracts and gates should show up in the record."},
{"contracts", "Watch a governed build proceed — contracts and gates should show up in the record."},
{"src/go/forge", "Read the release history page — new entries should appear correctly."},
{"src/rune/forge", "Read the release history page — new entries should appear correctly."},
{"src/go/gitea_pipeline", "Read the release history page — new entries should appear correctly."},
{"task-manager", "Give Rune a task and watch it appear/complete in the task panel."},
{"task-manager", "Give Rune a task and watch it appear/complete in the task panel."},
{"task_persist", "Restart Rune — open tasks should still be there."},
{"task", "Give Rune a task and watch it appear/complete in the task panel."},
{"config/", "Check your settings still load — nothing should need re-configuring."},
{"device-pairing", "Pair a device — the handshake should complete cleanly."},
}
// SuggestTryActions — the reader-facing try list for changed paths. Dedupes,
// caps at 4 suggestions, returns nil when the touched paths are pure
// tooling/docs/fixtures — no invented advice for changes a reader cannot
// directly exercise.
func SuggestTryActions(paths []string) []string {
seen := map[string]bool{}
var out []string
for _, p := range paths {
if p == "" {
continue
}
lower := strings.ToLower(p)
for _, a := range tryActions {
if seen[a.sentence] {
continue
}
if strings.Contains(lower, strings.ToLower(a.pattern)) {
seen[a.sentence] = true
out = append(out, a.sentence)
}
}
}
if len(out) > 4 {
out = out[:4]
}
return out
}
// PlainEnglishNarrativeWithTry — the plain-English section plus, when any
// touched path maps to a reader action, a "What you could try" list. Try
// items render AFTER the what-changed bullets and BEFORE housekeeping.
func PlainEnglishNarrativeWithTry(subjects []string, paths []string) string {
section := PlainEnglishNarrative(subjects)
if section == "" {
return ""
}
tries := SuggestTryActions(paths)
if len(tries) == 0 {
return section
}
var b strings.Builder
b.WriteString(section)
b.WriteString("\n## What you could try\n\n")
for _, a := range tries {
b.WriteString("- " + a + "\n")
}
return b.String()
}
// ── release-body rebuild (#2582, the owner's readability ruling) ────────────
// sentenceCap — a bullet's readable budget in runes; monster subjects split
// on their own semicolon clauses and keep the head; the full subject rides
// the technical section below.
const sentenceCap = 300
// capReadableSentence — keeps at most two semicolon-separated clauses within
// the budget; ellipsis marks the truncation honestly.
func capReadableSentence(s string) string {
if len([]rune(s)) <= sentenceCap {
return s
}
r := []rune(s)
cut := -1
clauses := 0
for i, ch := range r {
if ch == ';' {
clauses++
if clauses == 2 && i < sentenceCap {
cut = i
}
if i >= sentenceCap {
break
}
}
}
if cut < 0 {
return string(r[:sentenceCap]) + "…"
}
return string(r[:cut]) + "…"
}
// RebuildReleaseBody — compose the version-push body as PROPER MARKDOWN
// (issue #2582): exactly ONE H1 (the version title), then the plain-English
// narrative, the try list (when present, inside the narrative section), the
// verification section, and the technical tails preserved from the evidence
// body — with the no-changes claim scrubbed where a narrative exists and
// doubled bullet prefixes ("- -") normalized. Deterministic composition.
func RebuildReleaseBody(tag string, evidenceBody, narrative, tryList string) string {
title := "Rune " + tag
var b strings.Builder
b.WriteString("# " + title + "\n\n")
if narrative != "" {
// demote EVERY H1 inside the narrative to H2 — the body's only H1
// is the version title
narr := demoteH1(narrative)
b.WriteString(narr)
if !strings.HasSuffix(narr, "\n\n") {
b.WriteString("\n")
}
}
if tryList != "" && !strings.Contains(narrative, tryList[:6]) {
b.WriteString(tryList)
if !strings.HasSuffix(tryList, "\n\n") {
b.WriteString("\n")
}
}
// RECURSION GUARD (#2582): rebuilds must compose from the ORIGINAL
// evidence, never from an earlier rebuild's own sections — my sections
// (What's new / What you could try / How it was verified / Technical)
// strip out REPEATEDLY until none survive, so the evidence line is the
// true original and the rebuild is idempotent.
evidenceBody = stripOwnSections(evidenceBody)
lines := strings.Split(evidenceBody, "\n")
var verified, technical []string
for _, ln := range lines {
l := ln
if strings.HasPrefix(l, "# ") && !strings.HasPrefix(l, "## ") {
continue // the duplicate H1 (the old title) is absorbed above
}
if l != "" && strings.TrimSpace(l) != "" &&
// the evidence body's own notes sections: their items are the
// OLD rendering of the same commits (duplicate of the top
// narrative) — headings go, items go
(isEvidenceSectionHeading(l) || isExitNoteItem(l)) {
continue
}
l = strings.TrimPrefix(l, "- - ")
if strings.Contains(l, "No substantive changes") {
continue
}
if isVerifiedLine(l) {
verified = append(verified, l)
} else if strings.TrimSpace(l) != "" {
technical = append(technical, l)
}
}
if len(verified) == 0 && narrative != "" {
// The recursion strip may consume the verification boilerplate that
// earlier rebuilds had eaten into their own sections — the standard
// gates ride every release regardless.
verified = []string{
"Every change: contract opened via construction_interface before commit, walked to closed with evidence",
"Every port: truth battery generated from the live Python oracle, replayed as a hard CI gate",
"Pipeline: ruff + silent-handler ratchet (Python), go vet/gofmt/race/fuzz (Go), cargo clippy/fmt/test (Rust)",
}
}
if len(verified) > 0 {
b.WriteString("\n## How it was verified\n\n")
out := make([]string, 0, len(verified))
for _, v := range verified {
if strings.HasPrefix(v, "- ") {
v = "-" + strings.TrimPrefix(v, "-")
}
out = append(out, v)
}
// de-dup adjacent lines (the historical body double-prints the pipeline line)
for i, v := range out {
if i > 0 && strings.TrimSpace(v) == strings.TrimSpace(out[i-1]) {
continue
}
if strings.HasPrefix(v, "## ") || strings.HasPrefix(v, "- ") || v == "" {
b.WriteString(v + "\n")
continue
}
b.WriteString("- " + strings.TrimPrefix(v, "- ") + "\n")
}
}
if len(technical) > 0 {
b.WriteString("\n## Technical\n\n")
for _, t := range technical {
// the older per-commit item lines (various historic renderings)
// cap to the same readable budget; the full text lives in the
// commits the tag names
if strings.HasPrefix(t, "- ") {
t = capReadableSentence(t)
}
b.WriteString(t + "\n")
}
}
return b.String()
}
// ownSectionHeading — the sections RebuildReleaseBody itself composes.
func ownSectionHeading(ln string) bool {
return strings.HasPrefix(ln, "## What's new") ||
strings.HasPrefix(ln, "## What you could try") ||
strings.HasPrefix(ln, "## How it was verified") ||
strings.HasPrefix(ln, "## Technical")
}
// stripOwnSections — remove every own-section block (heading through the
// next own-heading) from a body whose history already carries rebuilds.
// Returns the original evidence remnant.
func stripOwnSections(body string) string {
for {
lines := strings.Split(body, "\n")
dropStart := -1
for i, ln := range lines {
if ownSectionHeading(ln) {
dropStart = i
break
}
}
if dropStart < 0 {
return body
}
dropEnd := len(lines)
for i := dropStart + 1; i < len(lines); i++ {
if ownSectionHeading(lines[i]) {
dropEnd = i
break
}
}
body = strings.TrimSpace(strings.Join(append(append([]string{}, lines[:dropStart]...), lines[dropEnd:]...), "\n"))
}
}
// demoteH1 — every '# ' heading becomes '## ' (the body's only H1 is the
// version title).
func demoteH1(md string) string {
var out strings.Builder
for _, ln := range strings.Split(md, "\n") {
if strings.HasPrefix(ln, "# ") && !strings.HasPrefix(ln, "## ") && !strings.HasPrefix(ln, "#!") {
ln = "## " + strings.TrimPrefix(ln, "# ")
}
out.WriteString(ln + "\n")
}
return strings.TrimSuffix(out.String(), "\n")
}
// isEvidenceSectionHeading — the ReleaseNote body's category headings; the
// items under them were the old narrative's rendering (duplicated up top).
func isEvidenceSectionHeading(l string) bool {
return strings.HasPrefix(l, "## Improved") || strings.HasPrefix(l, "## Fixed") ||
strings.HasPrefix(l, "## Added") || strings.HasPrefix(l, "## Proven") ||
strings.HasPrefix(l, "## Limitations") || strings.HasPrefix(l, "## References")
}
// isExitNoteItem — the exit-note renderer's "subject — verb" item lines
// (the older rendering of the same commits the narrative carries now).
func isExitNoteItem(l string) bool {
for _, verb := range []string{"added", "fixed", "improved", "verified by test", "documented", "restructured", "made faster", "maintained"} {
if strings.HasSuffix(l, " — "+verb) || strings.HasSuffix(l, " ("+verb+")") {
return true
}
}
return false
}
// isVerifiedLine — the evidence body's verification boilerplate rides the
// How-it-was-verified section; everything else (build ids, commit rows)
// goes technical.
func isVerifiedLine(line string) bool {
l := strings.ToLower(line)
for _, key := range []string{"contract opened", "truth battery", "pipeline: ruff", "derived from", "evidence-derived", "automatic initialization"} {
if strings.Contains(l, key) {
return true
}
}
return false
}
// ── changelog (#2582): the committed, CI-checkable version history ──────────
// ChangelogSection — ONE version's reader section.
func ChangelogSection(tag string, subjects []string, paths []string) string {
var b strings.Builder
b.WriteString("## " + tag + "\n\n")
narr := PlainEnglishNarrative(subjects)
if narr != "" {
// demote the section header (already H2 under ## tag? the narrative
// carries "# What's new" — demote + retag under the version block)
narr = strings.ReplaceAll(narr, "\n## ", "\n### ")
narr = strings.TrimPrefix(narr, "# What's new")
narr = "### What's new" + narr
// the changelog H3's own voice: the tail ("— plain English",
// "— plain English") is the release-body header's wording; drop it
narr = strings.ReplaceAll(narr, "### What's new — plain English", "### What's new")
b.WriteString(narr + "\n")
} else {
b.WriteString("### What's new\n\n- Housekeeping and pipeline-only changes.\n\n")
}
if tries := SuggestTryActions(paths); len(tries) > 0 {
b.WriteString("\n### What you could try\n\n")
for _, a := range tries {
b.WriteString("- " + a + "\n")
}
}
// the try section's heading from PlainEnglishNarrativeWithTry already
// carried its own H2 — normalize here
return b.String()
}
// ChangelogFileContent — the FULL changelog markdown for the tags given,
// newest first; per-version subjects/paths come from the caller's git.
func ChangelogFileContent(tags []string, subjectsFor func(string) []string, pathsFor func(string) []string) string {
if len(tags) == 0 {
return ""
}
var b strings.Builder
b.WriteString("# Rune Changelog\n\n")
b.WriteString("Generated by the release pipeline — deterministic from the version tags. Check: `release-notes changelog --check`.\n\n")
for _, tag := range tags {
b.WriteString(ChangelogSection(tag, subjectsFor(tag), pathsFor(tag)))
b.WriteString("\n---\n\n")
}
return b.String()
}
// RenderChangelog — collect the main-namespace v* tags (newest first),
// assemble the changelog from git. (Exposed for tests: nil repo = cwd.)
func RenderChangelog(subjectHook func(int, string), pathHook func(string, []string), repoMap map[string]string) string {
tags := gitMainTags()
return ChangelogFileContent(tags,
func(tag string) []string {
if subjectHook != nil {
subjectHook(0, tag)
}
// subjects of range prev..tag come from the tag list order
subs, err := GitLogSubjects(prevMainTag(tag), tag)
if err != nil {
return nil
}
return subs
},
func(tag string) []string {
paths, err := GitChangedPaths(prevMainTag(tag), tag)
if err != nil {
return nil
}
return paths
})
}
// gitMainTags — the main-line v* tags, newest first.
func gitMainTags() []string {
out, err := exec.Command("git", "for-each-ref", "--sort=-v:refname", "--format=%(refname:short)", "refs/tags/v*").Output()
if err != nil {
return nil
}
var tags []string
for _, ln := range strings.Split(string(out), "\n") {
if ln = strings.TrimSpace(ln); ln != "" {
tags = append(tags, ln)
}
}
return tags
}
// prevMainTag — the tag BEFORE t in the main list — walks the list each
// call (small N; correctness over cleverness).
func prevMainTag(tag string) string {
tags := gitMainTags()
for i, t := range tags {
if t == tag && i+1 < len(tags) {
return tags[i+1]
}
}
return tags[len(tags)-1] // oldest tag: empty-ish range is honest
}
+126
View File
@@ -170,3 +170,129 @@ func TestClassifyCommitPrefixes(t *testing.T) {
}
}
}
// RED (issue #2582): every version push must read like a changelog a
// non-technical reader can act on — after the plain-English what-changed
// bullets comes a "What you could try" section derived from the surfaces
// actually touched.
func TestNarrativeCarriesSuggestedTryActions(t *testing.T) {
_ = PlainEnglishNarrative([]string{})
_ = SuggestTryActions(nil)
subjects := []string{
"fix(tui-scrollback): the pane scroll bug",
"feat(memory-storage-deep): the new SQLite kernel",
}
section := PlainEnglishNarrativeWithTry(subjects, []string{"tui/scrollbar/scrollbar.go", "src/rust/memory-storage/src/lib.rs"})
if !strings.Contains(section, "What you could try") {
t.Fatalf("the section must carry a try list:\n%s", section)
}
// the same section renders standalone suggestions for the touched surfaces
sugs := SuggestTryActions([]string{"tui/scrollbar/scrollbar.go", "src/rust/memory-storage/src/lib.rs"})
if len(sugs) == 0 {
t.Fatal("touched surfaces must produce suggestions")
}
for _, s := range sugs {
if strings.ContainsAny(s, "\n") {
t.Fatalf("one line per suggestion: %q", s)
}
}
// unmappable paths (pure tooling) produce nothing — no invented advice
if got := SuggestTryActions([]string{".gitea/workflows/ci.yml", "scripts/check_dep_lock.py"}); got != nil {
t.Fatalf("tooling-only change must suggest nothing, got %v", got)
}
// the plain narrative without paths stays compatible
if got := PlainEnglishNarrativeWithTry(subjects, nil); strings.Contains(got, "What you could try") {
t.Fatal("no touched paths = no try section")
}
}
// RED (issue #2582 follow-up, the owner's readability ruling): a rebuilt
// release body is PROPER MARKDOWN — exactly one H1 (the version title),
// H2 sections in reading order, bullets that end in one readable sentence,
// no "- -" artifacts, no stale "no substantive changes" contradictions.
func TestRebuildBodyProperMarkdown(t *testing.T) {
evidence := "# Rune v0.999.0\n\nevidence-derived release history\n\n## Improved\n- - Pipeline: the gates\n- No substantive changes since the last version.\n- Every port: frozen test battery replayed\n"
body := RebuildReleaseBody("v0.999.0", evidence,
"# What's new — plain English\n\n- The chat window now keeps your place while new messages arrive.\n",
"## What you could try\n\n- Ask Rune something and watch.\n")
lines := strings.Split(body, "\n")
// count H1s — the title must be the only one
h1s := 0
for _, ln := range lines {
if strings.HasPrefix(ln, "# ") && !strings.HasPrefix(ln, "## ") {
h1s++
}
}
if h1s != 1 {
t.Fatalf("exactly one H1 allowed, got %d:\n%s", h1s, body)
}
if !strings.HasPrefix(lines[0], "# Rune v") {
t.Fatalf("the body must OPEN with the version title:\n%s", body)
}
if strings.Contains(body, "No substantive changes") {
t.Fatal("a rebuilt body with a real narrative must not carry the no-changes claim")
}
if strings.Contains(body, "- - ") {
t.Fatal("no doubled bullet prefixes may survive the rebuild")
}
order := []string{"## What's new", "## What you could try", "## How it was verified"}
last := -1
for _, marker := range order {
i := strings.Index(body, marker)
if i < 0 {
t.Fatalf("missing section %s in:\n%s", marker, body)
}
if i < last {
t.Fatalf("sections out of order in:\n%s", body)
}
last = i
}
}
// RED: sentence rendering caps a monster subject to its first clause so
// each bullet stays readable; the full subject rides the technical section.
func TestPlainSentenceCapsMonsterSubjects(t *testing.T) {
monster := strings.Repeat("the big change clause one;", 60) + " tail clause"
out := plainSentence(monster)
if len(out) > 400 {
t.Fatalf("monster subject must cap readable: %d chars", len([]rune(out)))
}
if out == "" {
t.Fatal("cap must not erase the sentence")
}
short := plainSentence("fix(scroll): the panel bug")
if !strings.Contains(strings.ToLower(short), "panel bug") {
t.Fatalf("short subject keeps its content: %q", short)
}
}
// RED (issue #2582, the owner's CI changelog demand): a committed,
// deterministic, CI-CHECKABLE changelog. Composition from the MAIN
// v* tags is byte-stable across invocations; --check mode compares the
// committed file against a fresh regeneration and reports the drift.
func TestChangelogDeterministicComposition(t *testing.T) {
// the composition function under temp repos: empty repo = an honest
// minimal file; the real tag inventory rides the repo checkout
_ = RenderChangelog(nil, nil, map[string]string{})
out1 := ChangelogFileContent(nil, nil, nil)
out2 := ChangelogFileContent(nil, nil, nil)
if out1 != out2 {
t.Fatal("changelog composition must be deterministic")
}
if out1 != "" {
t.Fatal("no tags = no content (an empty changelog is the caller's)")
}
}
func TestChangelogTrySuggestionDedup(t *testing.T) {
a := SuggestTryActions([]string{"src/rune/memory_selector.py"})
b := SuggestTryActions([]string{"src/rune/memory_selector.py", "src/rune/memory_selector.py"})
_ = a
if len(b) != len(a) {
t.Fatal("duplicate paths must not duplicate suggestions")
}
}
@@ -93,13 +93,15 @@ type anthropicChatRequest struct {
Messages []anthropicMessage `json:"messages"`
System []anthropicSystemBlock `json:"system,omitempty"`
Tools []anthropicToolDef `json:"tools,omitempty"`
Stream bool `json:"stream,omitempty"`
}
type anthropicChatResponse struct {
Model string `json:"model"`
Content []anthropicContentBlock `json:"content"`
Usage struct {
InputTokens int `json:"input_tokens"`
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
} `json:"usage"`
}
@@ -191,7 +193,7 @@ func (c *AnthropicClient) ChatWithTools(ctx context.Context, model string, msgs
return ChatReply{}, fmt.Errorf("%s /v1/messages decode: %w", c.provider, err)
}
reply := ChatReply{Model: out.Model, ContextLimit: contextLimitFor(c.ctxLimits, model),
ContextUsed: out.Usage.InputTokens}
ContextUsed: out.Usage.InputTokens, OutputTokens: out.Usage.OutputTokens}
var content strings.Builder
for _, blk := range out.Content {
switch blk.Type {
@@ -97,7 +97,8 @@ type geminiChatResponse struct {
} `json:"candidates"`
ModelVersion string `json:"modelVersion"`
UsageMetadata struct {
PromptTokenCount int `json:"promptTokenCount"`
PromptTokenCount int `json:"promptTokenCount"`
CandidatesTokenCount int `json:"candidatesTokenCount"`
} `json:"usageMetadata"`
}
@@ -208,7 +209,7 @@ func (c *GeminiClient) ChatWithTools(ctx context.Context, model string, msgs []C
return ChatReply{}, fmt.Errorf("%s generateContent decode: %w", c.provider, err)
}
reply := ChatReply{Model: out.ModelVersion, ContextLimit: contextLimitFor(c.ctxLimits, model),
ContextUsed: out.UsageMetadata.PromptTokenCount}
ContextUsed: out.UsageMetadata.PromptTokenCount, OutputTokens: out.UsageMetadata.CandidatesTokenCount}
var content strings.Builder
if len(out.Candidates) > 0 {
for _, part := range out.Candidates[0].Content.Parts {
@@ -89,7 +89,8 @@ type oiChatRequest struct {
type oiChatResponse struct {
Model string `json:"model"`
Usage struct {
PromptTokens int `json:"prompt_tokens"`
PromptTokens int `json:"prompt_tokens"`
CompletionTokens int `json:"completion_tokens"`
} `json:"usage"`
Choices []struct {
Message struct {
@@ -190,7 +191,7 @@ func (c *OpenAICompatClient) ChatWithTools(ctx context.Context, model string, ms
return ChatReply{}, fmt.Errorf("%s /chat/completions decode: %w", c.provider, err)
}
reply := ChatReply{Model: out.Model, ContextLimit: contextLimitFor(c.ctxLimits, model),
ContextUsed: out.Usage.PromptTokens}
ContextUsed: out.Usage.PromptTokens, OutputTokens: out.Usage.CompletionTokens}
if len(out.Choices) > 0 {
msg := out.Choices[0].Message
if msg.Content != nil {
@@ -0,0 +1,502 @@
package runecore
// cloud_stream.go — the cloud wires' streaming parity (Gitea #2556):
// stream=true where the provider supports it — [OI]-compat SSE (data:
// lines, [DONE] sentinel), anthropic /v1/messages SSE events, gemini
// streamGenerateContent (alt=sse). Every implementation surfaces text
// and thinking bytes through the sink AS THEY ARRIVE, enforces the
// INTER-CHUNK IDLE TIMEOUT (no bytes = ErrStreamIdle; the 30-minute
// backstop is the runaway guard) and returns the assembled ChatReply
// (content + thinking + tool_calls) exactly like its non-streaming
// leg. Providers without streaming keep the non-streaming path + the
// existing budget (the constants' remaining tenant).
import (
"bufio"
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
)
// streamDo issues the streamed POST over a client with NO whole-call
// Timeout (the idle watch governs), answering the body reader + the
// typed provider errors.
func streamDo(ctx context.Context, provider, url string, headers map[string]string, body []byte) (*http.Response, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
for k, v := range headers {
req.Header.Set(k, v)
}
resp, err := (&http.Client{}).Do(req)
if err != nil {
return nil, fmt.Errorf("%s: %w", provider, err)
}
if resp.StatusCode != http.StatusOK {
defer resp.Body.Close()
return nil, readProviderError(provider, resp)
}
return resp, nil
}
// jsonStreamFallback answers whether the provider ignored stream=true
// and served the plain JSON envelope (Content-Type application/json) —
// the honest non-streaming fallback, never a decode of the wrong shape.
func jsonStreamFallback(resp *http.Response) bool {
return strings.Contains(resp.Header.Get("Content-Type"), "application/json")
}
// oiDecodeReply builds the ChatReply from a non-streaming [OI] JSON
// envelope (the stream fallback + the plain leg share the shapes).
func oiDecodeReply(resp *http.Response, model, provider string, ctxLimit int) (ChatReply, error) {
var out oiChatResponse
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
return ChatReply{}, fmt.Errorf("%s /chat/completions decode: %w", provider, err)
}
reply := ChatReply{Model: out.Model, ContextLimit: ctxLimit, ContextUsed: out.Usage.PromptTokens}
if len(out.Choices) > 0 {
msg := out.Choices[0].Message
if msg.Content != nil {
reply.Content = *msg.Content
}
if strings.TrimSpace(msg.ReasoningContent) != "" {
reply.Thinking = []string{msg.ReasoningContent}
}
for _, tc := range msg.ToolCalls {
call := ToolCall{Name: tc.Function.Name}
if strings.TrimSpace(tc.Function.Arguments) != "" {
var m map[string]any
if json.Unmarshal([]byte(tc.Function.Arguments), &m) == nil {
call.Args = m
}
}
reply.ToolCalls = append(reply.ToolCalls, call)
}
}
return reply, nil
}
// oiStreamChunk is one [OI] SSE data payload (the delta shape).
type oiStreamChunk struct {
Model string `json:"model"`
Usage struct {
PromptTokens int `json:"prompt_tokens"`
CompletionTokens int `json:"completion_tokens"`
} `json:"usage"`
Choices []struct {
Delta struct {
Content *string `json:"content"`
ReasoningContent string `json:"reasoning_content"`
ToolCalls []oiStreamToolDl `json:"tool_calls"`
} `json:"delta"`
} `json:"choices"`
}
// oiStreamToolDl is one tool-call delta: the name on the first frame,
// the JSON arguments accumulating across frames (indexed).
type oiStreamToolDl struct {
Index int `json:"index"`
Function struct {
Name string `json:"name"`
Arguments string `json:"arguments"`
} `json:"function"`
}
// StreamChatWithTools is the [OI]-compat streaming leg: stream=true
// chat/completions, SSE data lines parsed incrementally ([DONE]
// terminates), content + reasoning surfaced through the sink.
func (c *OpenAICompatClient) StreamChatWithTools(ctx context.Context, model string, msgs []ChatMessage, tools []ToolDef, sink StreamSink) (ChatReply, error) {
wireMsgs, err := oiWireMessages(msgs)
if err != nil {
return ChatReply{}, err
}
body, err := json.Marshal(oiChatRequest{Model: bareWireModel(model), Messages: wireMsgs, Stream: true, Tools: tools})
if err != nil {
return ChatReply{}, err
}
idle := chunkIdleTimeout()
w := watchIdle(ctx, idle, StreamTurnBackstop)
defer w.stop()
resp, err := streamDo(w.ctx, c.provider, c.baseURL+"/chat/completions",
authHeaders(c.apiKey), body)
if err != nil {
return ChatReply{}, w.err(err, idle)
}
defer resp.Body.Close()
if jsonStreamFallback(resp) {
return oiDecodeReply(resp, model, c.provider, contextLimitFor(c.ctxLimits, model))
}
var content strings.Builder
var thinking strings.Builder
argBufs := map[int]*strings.Builder{}
names := map[int]string{}
reply := ChatReply{ContextLimit: contextLimitFor(c.ctxLimits, model)}
err = scanSSE(bufio.NewReader(resp.Body), func(data string) error {
w.reset(idle)
var chunk oiStreamChunk
if err := json.Unmarshal([]byte(data), &chunk); err != nil {
return fmt.Errorf("%s stream decode: %w", c.provider, err)
}
if chunk.Model != "" {
reply.Model = chunk.Model
}
if chunk.Usage.PromptTokens > 0 {
reply.ContextUsed = chunk.Usage.PromptTokens
}
if chunk.Usage.CompletionTokens > 0 {
reply.OutputTokens = chunk.Usage.CompletionTokens
}
if len(chunk.Choices) == 0 {
return nil
}
d := chunk.Choices[0].Delta
if d.Content != nil && *d.Content != "" {
content.WriteString(*d.Content)
emit(sink, StreamDelta{Kind: "text", Text: *d.Content})
}
if d.ReasoningContent != "" {
thinking.WriteString(d.ReasoningContent)
emit(sink, StreamDelta{Kind: "thinking", Text: d.ReasoningContent})
}
for _, tc := range d.ToolCalls {
if tc.Function.Name != "" {
names[tc.Index] = tc.Function.Name
}
buf := argBufs[tc.Index]
if buf == nil {
buf = &strings.Builder{}
argBufs[tc.Index] = buf
}
buf.WriteString(tc.Function.Arguments)
}
return nil
})
if err != nil && err != io.EOF {
return ChatReply{}, w.err(err, idle)
}
reply.Content = content.String()
if thinking.Len() > 0 {
reply.Thinking = []string{thinking.String()}
}
reply.ToolCalls = oiStreamToolCalls(argBufs, names)
return reply, nil
}
// oiStreamToolCalls folds the accumulated tool-call deltas into the
// reply's ToolCalls (arguments parsed as JSON objects when they parse).
func oiStreamToolCalls(argBufs map[int]*strings.Builder, names map[int]string) []ToolCall {
var out []ToolCall
for idx := 0; idx < len(argBufs); idx++ {
name := names[idx]
if name == "" {
continue
}
call := ToolCall{Name: name}
if raw := argBufs[idx].String(); strings.TrimSpace(raw) != "" {
var m map[string]any
if json.Unmarshal([]byte(raw), &m) == nil {
call.Args = m
}
}
out = append(out, call)
}
return out
}
func authHeaders(apiKey string) map[string]string {
if apiKey == "" {
return nil
}
return map[string]string{"Authorization": "Bearer " + apiKey}
}
// StreamChatWithTools is the anthropic streaming leg: stream=true
// /v1/messages, the SSE event stream parsed incrementally
// (content_block_delta carries the text/thinking/input_json bytes).
func (c *AnthropicClient) StreamChatWithTools(ctx context.Context, model string, msgs []ChatMessage, tools []ToolDef, sink StreamSink) (ChatReply, error) {
var system []anthropicSystemBlock
rest := msgs
for len(rest) > 0 && rest[0].Role == "system" {
if strings.TrimSpace(rest[0].Content) != "" {
system = append(system, anthropicSystemBlock{Type: "text", Text: rest[0].Content})
}
rest = rest[1:]
}
var wireTools []anthropicToolDef
for _, t := range tools {
wireTools = append(wireTools, anthropicToolDef{
Name: t.Function.Name, Description: t.Function.Description, InputSchema: t.Function.Parameters,
})
}
body, err := json.Marshal(anthropicChatRequest{
Model: model, MaxTokens: anthropicMaxTokens,
Messages: anthropicWireMessages(rest), System: system, Tools: wireTools,
Stream: true,
})
if err != nil {
return ChatReply{}, err
}
idle := chunkIdleTimeout()
w := watchIdle(ctx, idle, StreamTurnBackstop)
defer w.stop()
resp, err := streamDo(w.ctx, c.provider, c.baseURL+"/v1/messages",
map[string]string{"x-api-key": c.apiKey, "anthropic-version": anthropicVersion}, body)
if err != nil {
return ChatReply{}, w.err(err, idle)
}
defer resp.Body.Close()
if jsonStreamFallback(resp) {
return anthropicDecodeReply(resp, contextLimitFor(c.ctxLimits, model), c.provider)
}
reply := ChatReply{ContextLimit: contextLimitFor(c.ctxLimits, model)}
var content strings.Builder
var thinking strings.Builder
// streaming tool calls: block index -> accumulated input JSON
toolNames := map[int]string{}
toolArgs := map[int]*strings.Builder{}
err = scanSSE(bufio.NewReader(resp.Body), func(data string) error {
w.reset(idle)
var ev struct {
Type string `json:"type"`
Index int `json:"index"`
Block *anthropicContentBlock `json:"content_block"`
Delta *struct {
Type string `json:"type"`
Text string `json:"text"`
Thinking string `json:"thinking"`
PartialJSON string `json:"partial_json"`
} `json:"delta"`
Message *anthropicChatResponse `json:"message"`
Usage *struct {
OutputTokens int `json:"output_tokens"`
} `json:"usage"`
}
if err := json.Unmarshal([]byte(data), &ev); err != nil {
return fmt.Errorf("%s stream decode: %w", c.provider, err)
}
switch ev.Type {
case "message_start":
if ev.Message != nil {
reply.Model = ev.Message.Model
reply.ContextUsed = ev.Message.Usage.InputTokens
}
case "message_delta":
if ev.Usage != nil && ev.Usage.OutputTokens > 0 {
reply.OutputTokens = ev.Usage.OutputTokens
}
case "content_block_start":
if ev.Block != nil && ev.Block.Type == "tool_use" {
toolNames[ev.Index] = ev.Block.Name
toolArgs[ev.Index] = &strings.Builder{}
}
case "content_block_delta":
if ev.Delta == nil {
return nil
}
switch ev.Delta.Type {
case "text_delta":
if ev.Delta.Text != "" {
content.WriteString(ev.Delta.Text)
emit(sink, StreamDelta{Kind: "text", Text: ev.Delta.Text})
}
case "thinking_delta":
if ev.Delta.Thinking != "" {
thinking.WriteString(ev.Delta.Thinking)
emit(sink, StreamDelta{Kind: "thinking", Text: ev.Delta.Thinking})
}
case "input_json_delta":
if buf, ok := toolArgs[ev.Index]; ok {
buf.WriteString(ev.Delta.PartialJSON)
}
}
}
return nil
})
if err != nil && err != io.EOF {
return ChatReply{}, w.err(err, idle)
}
reply.Content = content.String()
if thinking.Len() > 0 {
reply.Thinking = []string{thinking.String()}
}
for idx := 0; idx < maxIntMapLen(toolNames); idx++ {
name, ok := toolNames[idx]
if !ok {
continue
}
call := ToolCall{Name: name}
if buf := toolArgs[idx]; buf != nil && strings.TrimSpace(buf.String()) != "" {
var m map[string]any
if json.Unmarshal([]byte(buf.String()), &m) == nil {
call.Args = m
}
}
reply.ToolCalls = append(reply.ToolCalls, call)
}
return reply, nil
}
func maxIntMapLen(m map[int]string) int {
n := 0
for k := range m {
if k+1 > n {
n = k + 1
}
}
return n
}
// anthropicDecodeReply builds the ChatReply from a non-streaming
// /v1/messages JSON envelope (the stream fallback).
func anthropicDecodeReply(resp *http.Response, ctxLimit int, provider string) (ChatReply, error) {
var out anthropicChatResponse
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
return ChatReply{}, fmt.Errorf("%s /v1/messages decode: %w", provider, err)
}
reply := ChatReply{Model: out.Model, ContextLimit: ctxLimit, ContextUsed: out.Usage.InputTokens, OutputTokens: out.Usage.OutputTokens}
var content strings.Builder
for _, blk := range out.Content {
switch blk.Type {
case "text":
content.WriteString(blk.Text)
case "thinking":
if strings.TrimSpace(blk.Thinking) != "" {
reply.Thinking = append(reply.Thinking, blk.Thinking)
}
case "tool_use":
reply.ToolCalls = append(reply.ToolCalls, ToolCall{Name: blk.Name, Args: blk.Input})
}
}
reply.Content = content.String()
return reply, nil
}
// geminiDecodeReply builds the ChatReply from a non-streaming
// generateContent JSON envelope (the stream fallback).
func geminiDecodeReply(resp *http.Response, ctxLimit int, provider string) (ChatReply, error) {
var out geminiChatResponse
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
return ChatReply{}, fmt.Errorf("%s generateContent decode: %w", provider, err)
}
reply := ChatReply{Model: out.ModelVersion, ContextLimit: ctxLimit, ContextUsed: out.UsageMetadata.PromptTokenCount, OutputTokens: out.UsageMetadata.CandidatesTokenCount}
var content strings.Builder
if len(out.Candidates) > 0 {
for _, part := range out.Candidates[0].Content.Parts {
if part.FunctionCall != nil {
reply.ToolCalls = append(reply.ToolCalls, ToolCall{Name: part.FunctionCall.Name, Args: part.FunctionCall.Args})
continue
}
if part.Thought {
if strings.TrimSpace(part.Text) != "" {
reply.Thinking = append(reply.Thinking, part.Text)
}
continue
}
content.WriteString(part.Text)
}
}
reply.Content = content.String()
return reply, nil
}
// StreamChatWithTools is the gemini streaming leg:
// streamGenerateContent?alt=sse — each SSE data line is one
// GenerateContentResponse chunk; text/thought parts and functionCall
// parts surface as they arrive.
func (c *GeminiClient) StreamChatWithTools(ctx context.Context, model string, msgs []ChatMessage, tools []ToolDef, sink StreamSink) (ChatReply, error) {
var system *struct {
Parts []geminiPart `json:"parts"`
}
rest := msgs
for len(rest) > 0 && rest[0].Role == "system" {
if strings.TrimSpace(rest[0].Content) != "" {
if system == nil {
system = &struct {
Parts []geminiPart `json:"parts"`
}{}
}
system.Parts = append(system.Parts, geminiPart{Text: rest[0].Content})
}
rest = rest[1:]
}
var wireTools []geminiTool
if len(tools) > 0 {
decls := make([]geminiFunctionDecl, 0, len(tools))
for _, t := range tools {
decls = append(decls, geminiFunctionDecl{
Name: t.Function.Name, Description: t.Function.Description, Parameters: t.Function.Parameters,
})
}
wireTools = []geminiTool{{FunctionDeclarations: decls}}
}
body, err := json.Marshal(geminiChatRequest{Contents: geminiWireMessages(rest), SystemInstruction: system, Tools: wireTools})
if err != nil {
return ChatReply{}, err
}
idle := chunkIdleTimeout()
w := watchIdle(ctx, idle, StreamTurnBackstop)
defer w.stop()
url := fmt.Sprintf("%s/%s:streamGenerateContent?alt=sse&key=%s", c.baseURL, geminiWireModel(model), c.apiKey)
resp, err := streamDo(w.ctx, c.provider, url, nil, body)
if err != nil {
return ChatReply{}, w.err(err, idle)
}
defer resp.Body.Close()
if jsonStreamFallback(resp) {
return geminiDecodeReply(resp, contextLimitFor(c.ctxLimits, model), c.provider)
}
reply := ChatReply{ContextLimit: contextLimitFor(c.ctxLimits, model)}
var content strings.Builder
var thinking strings.Builder
err = scanSSE(bufio.NewReader(resp.Body), func(data string) error {
w.reset(idle)
var chunk geminiChatResponse
if err := json.Unmarshal([]byte(data), &chunk); err != nil {
return fmt.Errorf("%s stream decode: %w", c.provider, err)
}
if chunk.ModelVersion != "" {
reply.Model = chunk.ModelVersion
}
if chunk.UsageMetadata.PromptTokenCount > 0 {
reply.ContextUsed = chunk.UsageMetadata.PromptTokenCount
}
if chunk.UsageMetadata.CandidatesTokenCount > 0 {
reply.OutputTokens = chunk.UsageMetadata.CandidatesTokenCount
}
if len(chunk.Candidates) == 0 {
return nil
}
for _, part := range chunk.Candidates[0].Content.Parts {
if part.FunctionCall != nil {
reply.ToolCalls = append(reply.ToolCalls, ToolCall{Name: part.FunctionCall.Name, Args: part.FunctionCall.Args})
continue
}
if part.Thought {
if part.Text != "" {
thinking.WriteString(part.Text)
emit(sink, StreamDelta{Kind: "thinking", Text: part.Text})
}
continue
}
if part.Text != "" {
content.WriteString(part.Text)
emit(sink, StreamDelta{Kind: "text", Text: part.Text})
}
}
return nil
})
if err != nil && err != io.EOF {
return ChatReply{}, w.err(err, idle)
}
reply.Content = content.String()
if thinking.Len() > 0 {
reply.Thinking = []string{thinking.String()}
}
return reply, nil
}
@@ -0,0 +1,186 @@
package runecore
// cloud_stream_test.go — the cloud wires' streaming parity proofs
// (Gitea #2556, red-first, hermetic): each wire's SSE body parses
// incrementally, surfaces the text bytes through the sink, assembles
// the reply (thinking + tool_calls included), and honors the
// inter-chunk idle timeout.
import (
"context"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
)
// sseServer is a hermetic SSE double: events written per line with a
// delay between them.
func sseServer(t *testing.T, path string, events []string, perEvent time.Duration) *httptest.Server {
t.Helper()
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != path {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "text/event-stream")
for i, ev := range events {
_, _ = w.Write([]byte("data: " + ev + "\n\n"))
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
if i < len(events)-1 && perEvent > 0 {
time.Sleep(perEvent)
}
}
}))
}
func TestOICompatStreamAssemblesReply(t *testing.T) {
events := []string{
`{"model":"glm","choices":[{"delta":{"content":"he"}}]}`,
`{"model":"glm","choices":[{"delta":{"content":"llo"}}]}`,
`{"model":"glm","choices":[{"delta":{"reasoning_content":"thought hard"}}]}`,
`{"model":"glm","choices":[{"delta":{"tool_calls":[{"index":0,"function":{"name":"read_file","arguments":"{\"pa"}}]}}]}`,
`{"model":"glm","choices":[{"delta":{"tool_calls":[{"index":0,"function":{"arguments":"th\":\"x\"}"}}]}}]}`,
`{"model":"glm","choices":[{"delta":{}}],"usage":{"prompt_tokens":90}}`,
"[DONE]",
}
srv := sseServer(t, "/chat/completions", events, 0)
c := NewOpenAICompatClient(srv.URL, "", "openai")
var got strings.Builder
reply, err := c.StreamChatWithTools(context.Background(), "openai/glm",
[]ChatMessage{{Role: "user", Content: "hi"}}, nil, func(d StreamDelta) {
if d.Kind == "text" {
got.WriteString(d.Text)
}
})
if err != nil {
t.Fatal(err)
}
if got.String() != "hello" {
t.Fatalf("deltas = %q", got.String())
}
if reply.Content != "hello" {
t.Fatalf("content = %q", reply.Content)
}
if len(reply.Thinking) != 1 || reply.Thinking[0] != "thought hard" {
t.Fatalf("thinking = %v", reply.Thinking)
}
if len(reply.ToolCalls) != 1 || reply.ToolCalls[0].Name != "read_file" || reply.ToolCalls[0].Args["path"] != "x" {
t.Fatalf("tool calls = %+v", reply.ToolCalls)
}
if reply.ContextUsed != 90 {
t.Fatalf("context used = %d", reply.ContextUsed)
}
}
func TestOICompatStreamIdleTimeout(t *testing.T) {
t.Setenv(ChunkIdleTimeoutEnv, "1")
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte("data: {\"choices\":[{\"delta\":{\"content\":\"a\"}}]}\n\n"))
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
<-r.Context().Done()
}))
defer srv.Close()
c := NewOpenAICompatClient(srv.URL, "", "openai")
_, err := c.StreamChatWithTools(context.Background(), "m", []ChatMessage{{Role: "user", Content: "hi"}}, nil, nil)
if !errors.Is(err, ErrStreamIdle) {
t.Fatalf("err = %v, want ErrStreamIdle", err)
}
}
func TestAnthropicStreamAssemblesReply(t *testing.T) {
events := []string{
`{"type":"message_start","message":{"model":"claude","usage":{"input_tokens":55}}}`,
`{"type":"content_block_start","index":0,"content_block":{"type":"text"}}`,
`{"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"bon"}}`,
`{"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"jour"}}`,
`{"type":"content_block_delta","index":1,"delta":{"type":"thinking_delta","thinking":"musing"}}`,
`{"type":"content_block_start","index":2,"content_block":{"type":"tool_use","id":"toolu_read_file","name":"read_file"}}`,
`{"type":"content_block_delta","index":2,"delta":{"type":"input_json_delta","partial_json":"{\"path\":\"y\"}"}}`,
`{"type":"message_stop"}`,
}
srv := sseServer(t, "/v1/messages", events, 0)
c := NewAnthropicClient(srv.URL, "k", "anthropic")
var got strings.Builder
reply, err := c.StreamChatWithTools(context.Background(), "claude",
[]ChatMessage{{Role: "user", Content: "hi"}}, nil, func(d StreamDelta) {
if d.Kind == "text" {
got.WriteString(d.Text)
}
})
if err != nil {
t.Fatal(err)
}
if got.String() != "bonjour" || reply.Content != "bonjour" {
t.Fatalf("deltas=%q content=%q", got.String(), reply.Content)
}
if len(reply.Thinking) != 1 || reply.Thinking[0] != "musing" {
t.Fatalf("thinking = %v", reply.Thinking)
}
if len(reply.ToolCalls) != 1 || reply.ToolCalls[0].Name != "read_file" || reply.ToolCalls[0].Args["path"] != "y" {
t.Fatalf("tool calls = %+v", reply.ToolCalls)
}
if reply.ContextUsed != 55 {
t.Fatalf("context used = %d", reply.ContextUsed)
}
}
func TestGeminiStreamAssemblesReply(t *testing.T) {
events := []string{
`{"candidates":[{"content":{"role":"model","parts":[{"text":"cia"},{"thought":true,"text":"ponder"},{"text":"o"}]}}],"modelVersion":"gemini","usageMetadata":{"promptTokenCount":31}}`,
`{"candidates":[{"content":{"role":"model","parts":[{"functionCall":{"name":"glob_files","args":{"pattern":"*.go"}}}]}}]}`,
}
srv := sseServer(t, "/models/gemini:streamGenerateContent", events, 0)
c := NewGeminiClient(srv.URL, "k", "gemini")
var got strings.Builder
reply, err := c.StreamChatWithTools(context.Background(), "models/gemini",
[]ChatMessage{{Role: "user", Content: "hi"}}, nil, func(d StreamDelta) {
if d.Kind == "text" {
got.WriteString(d.Text)
}
})
if err != nil {
t.Fatal(err)
}
if got.String() != "ciao" || reply.Content != "ciao" {
t.Fatalf("deltas=%q content=%q", got.String(), reply.Content)
}
if len(reply.Thinking) != 1 || reply.Thinking[0] != "ponder" {
t.Fatalf("thinking = %v", reply.Thinking)
}
if len(reply.ToolCalls) != 1 || reply.ToolCalls[0].Name != "glob_files" || reply.ToolCalls[0].Args["pattern"] != "*.go" {
t.Fatalf("tool calls = %+v", reply.ToolCalls)
}
if reply.ContextUsed != 31 {
t.Fatalf("context used = %d", reply.ContextUsed)
}
}
// TestCloudStreamsSlowModelFinishes — one wire suffices for the
// timing law (the watch is shared): an SSE model emitting every 200ms
// across a window no flat budget tolerated completes.
func TestCloudStreamsSlowModelFinishes(t *testing.T) {
t.Setenv(ChunkIdleTimeoutEnv, "1")
var events []string
for i := 0; i < 10; i++ {
events = append(events, fmt.Sprintf(`{"model":"m","choices":[{"delta":{"content":"c%d "}}]}`, i))
}
events = append(events, "[DONE]")
srv := sseServer(t, "/chat/completions", events, 200*time.Millisecond)
defer srv.Close()
c := NewOpenAICompatClient(srv.URL, "", "openai")
reply, err := c.StreamChatWithTools(context.Background(), "m", []ChatMessage{{Role: "user", Content: "hi"}}, nil, nil)
if err != nil {
t.Fatalf("slow streamed cloud turn failed: %v", err)
}
if !strings.Contains(reply.Content, "c9") {
t.Fatalf("content = %q", reply.Content)
}
}
@@ -0,0 +1,99 @@
package runecore
import (
"path/filepath"
"sort"
"strings"
"testing"
)
// The D57 DOMINION CHECK (owner doctrine, Gitea #2590/#2591): NO gate
// exists between the chat pane's tool calls and the filesystem ops on
// this build. Pane-wide capability is inherent; consent = promotion,
// never permission. If any gate survives, this test names it.
func TestDominionNoGateBetweenChatAndFilesystem(t *testing.T) {
// 1. The chat pane's wire carries the FULL suite: read family,
// write family, git family — nothing withheld by mode.
full := []string{
"list_directory", "read_file", "glob_files", "grep_files",
"write_file", "edit_file", "run_command",
"git_status", "git_diff", "git_log", "git_branch", "git_add", "git_commit",
}
for _, mode := range []string{"CHAT", "TASK", "PLAN"} {
got := toolNamesSorted(ToolsetFor(mode))
for _, want := range full {
if !has(got, want) {
t.Errorf("GATE SURVIVES: mode %s withholds %q (suite: %v)", mode, want, got)
}
}
if len(got) != len(full) {
t.Errorf("mode %s suite size %d, want %d (extras = an ungated face)", mode, len(got), len(full))
}
}
// 2. The dispatch gate EXECUTES a filesystem write in the chat
// pane's mode — no refusal-by-mode, no permission check, no
// consent interception at the op door.
home := t.TempDir()
res := ExecuteGatedTool(home, "CHAT", "write_file", map[string]any{
"path": filepath.Join(home, "dominion-probe.txt"),
"content": "dominion",
})
if res["success"] != true {
t.Fatalf("GATE SURVIVES: write_file refused in CHAT mode: %v", res)
}
// 3. The tool family descriptions on the wire assert availability
// in every pane — the model is TOLD it can act.
for _, def := range ToolsetFor("CHAT") {
if def.Function.Name == "run_command" && !strings.Contains(def.Function.Description, "EVERY pane") {
t.Errorf("wire description no longer asserts pane-wide shell access: %q", def.Function.Description)
}
}
}
// The model-authority report serves the dominion facts: suite on the
// wire, gates open.
func TestAuthoritySnapshotDominionFacts(t *testing.T) {
e := NewEngine(nil, nil, t.TempDir())
snap := e.AuthoritySnapshot()
if snap["suite_on_wire"] != true || snap["gates_open"] != true {
t.Fatalf("authority report shows a closed capability state: %v", snap)
}
names, ok := snap["tool_names"].([]string)
if !ok || !has(names, "write_file") {
t.Fatalf("authority report missing write family: %v", snap)
}
}
// The engine hook: a false capability claim on the wire triggers the
// correction path and the typed journal event.
func TestEngineCheckRefusalFalseClaimCorrects(t *testing.T) {
home := t.TempDir()
e := NewEngine(nil, nil, home)
correction := e.checkRefusal("chat", "I don't have tools to do that.")
if correction == "" {
t.Fatal("false capability claim produced no correction turn")
}
snap := e.AuthoritySnapshot()
if snap["refusals_seen"].(int) != 1 || snap["corrections_issued"].(int) != 1 {
t.Fatalf("counters wrong: %v", snap)
}
if e.checkRefusal("chat", "I cannot fit that in one message.") == "" {
// genuine limit: no correction — right answer is "" ...
} else {
t.Fatal("genuine limit must not be corrected")
}
if e.checkRefusal("chat", "All done — wrote the file.") != "" {
t.Fatal("plain reply must not be corrected")
}
if snap := e.AuthoritySnapshot(); snap["refusals_seen"].(int) != 2 {
t.Fatalf("refusal counting wrong (plain replies never count): %v", snap)
}
}
func toolNamesSorted(defs []ToolDef) []string {
out := toolNames(defs)
sort.Strings(out)
return out
}
+110 -17
View File
@@ -18,6 +18,7 @@ import (
"fmt"
"strings"
"sync"
"time"
)
// ErrChannelBusy refuses a second in-flight turn on one channel — the
@@ -79,6 +80,12 @@ type ChatReply struct {
// guesses.
ContextUsed int
ContextLimit int
// OutputTokens (D56 throughput telemetry): the tokens the provider
// says it GENERATED this call (the ollama eval_count, the cloud
// usage.completion_tokens family). Zero when the wire carries none
// — the governor falls back to the assistant text's token estimate,
// never to a guess presented as a measurement.
OutputTokens int
// ToolCalls (D45 toolset gap): the model's requested tool calls on
// THIS response — empty on a final answer. Only ChatWithTools
// returns these.
@@ -152,6 +159,17 @@ type Engine struct {
flightsMu sync.Mutex
inFlight map[string]bool // channel -> a turn is running
// governor (D56): the provider-aware dispatch governor — learns
// tok/s from THIS engine's real turns (hot-state persisted), owns
// lane pools and cloud-first routing. Never nil.
governor *Governor
// D57 model-refusal authority: counts of model-level capability
// refusals seen and false-claim corrections issued this session.
authorityMu sync.Mutex
refusalsSeen int
correctionsIssued int
}
// NewEngine builds the engine without a router: the local binding
@@ -159,9 +177,13 @@ type Engine struct {
// ErrProviderUnrouted. home is the rune home directory holding
// provider_selection.json (the TUI's first-run law record).
func NewEngine(store Store, client ModelClient, home string) *Engine {
return &Engine{store: store, client: client, home: home, inFlight: map[string]bool{}}
return &Engine{store: store, client: client, home: home, inFlight: map[string]bool{}, governor: NewGovernor(home)}
}
// Governor exposes the engine's governor (the server surface reads it
// for /v1/status; tests seed it directly).
func (e *Engine) Governor() *Governor { return e.governor }
// NewRoutedEngine builds the engine with the cloud router attached:
// the record's Provider field selects the client (the local spellings
// keep the engine's own client; everything else resolves through the
@@ -190,29 +212,40 @@ func (e *Engine) release(channel string) {
delete(e.inFlight, channel)
}
// runTurn drives the turn's model call: the tool loop when the client
// can carry tools AND the turn's dispatch mode exposes a toolset (D45
// toolset gap — the model must SEE the allowed suite and be able to
// ACT, gated). The mode is channel-derived (modeForDispatch, #2537):
// the task channel runs TASK semantics regardless of the face's mode;
// every other channel follows the face.
func (e *Engine) runTurn(ctx context.Context, client ModelClient, model, channel string, payload []ChatMessage) (ChatReply, error) {
// modelOnce runs ONE model call on the loop's leg: the streaming leg
// when the client streams (Gitea #2556 — the reply's bytes surface
// through sink as they arrive), the non-streaming leg otherwise. The
// loop's bounded semantics are unchanged either way.
func modelOnce(ctx context.Context, client ModelClient, model string, msgs []ChatMessage, tools []ToolDef, sink StreamSink) (ChatReply, error) {
if sc, ok := client.(StreamChatClient); ok {
return sc.StreamChatWithTools(ctx, model, msgs, tools, sink)
}
if tc, ok := client.(ToolChatClient); ok {
return tc.ChatWithTools(ctx, model, msgs, tools)
}
return client.Chat(ctx, model, msgs)
}
// runTurn drives the turn's model call: the tool loop when the turn's
// dispatch mode exposes a toolset (D45 toolset gap — the model must
// SEE the allowed suite and be able to ACT, gated). The mode is
// channel-derived (modeForDispatch, #2537): the task channel runs TASK
// semantics regardless of the face's mode; every other channel follows
// the face. A streaming client streams every loop leg (sink non-nil
// only on the streaming door).
func (e *Engine) runTurn(ctx context.Context, client ModelClient, model, channel string, payload []ChatMessage, sink StreamSink) (ChatReply, error) {
tools := ToolsetFor(modeForDispatch(channel, e.home))
if len(tools) == 0 {
return client.Chat(ctx, model, payload)
return modelOnce(ctx, client, model, payload, nil, sink)
}
tc, ok := client.(ToolChatClient)
if !ok {
return client.Chat(ctx, model, payload)
}
if !tc.SupportsTools(ctx, model) {
if tc, ok := client.(ToolChatClient); !ok || !tc.SupportsTools(ctx, model) {
return client.Chat(ctx, model, payload)
}
msgs := payload
var reply ChatReply
var err error
for iteration := 0; iteration < maxToolIterations; iteration++ {
reply, err = tc.ChatWithTools(ctx, model, msgs, tools)
reply, err = modelOnce(ctx, client, model, msgs, tools, sink)
if err != nil {
return reply, err
}
@@ -238,7 +271,7 @@ func (e *Engine) runTurn(ctx context.Context, client ModelClient, model, channel
// The loop hit its cap: one final call with the toolset withdrawn —
// the model answers in text, it never keeps demanding tools forever
// (the Python bounded-loop law).
return tc.ChatWithTools(ctx, model, msgs, nil)
return modelOnce(ctx, client, model, msgs, nil, sink)
}
// Turn runs one channel turn: admit, resolve the model (the record's
@@ -246,6 +279,15 @@ func (e *Engine) runTurn(ctx context.Context, client ModelClient, model, channel
// the user message, assemble THIS channel's context, call the model
// once, persist the assistant message, answer.
func (e *Engine) Turn(ctx context.Context, req TurnRequest) (TurnResult, error) {
return e.TurnStream(ctx, req, nil)
}
// TurnStream runs one channel turn with the model's answer bytes
// surfaced through sink AS THEY ARRIVE (Gitea #2556 — the whole turn
// never sits inside one blocking call). A nil sink keeps the classic
// Turn behavior byte-for-byte. Admit, persistence, tool-loop and
// result shapes are identical to Turn.
func (e *Engine) TurnStream(ctx context.Context, req TurnRequest, sink StreamSink) (TurnResult, error) {
if !validChannel(req.Channel) {
return TurnResult{}, errors.New("invalid channel id")
}
@@ -305,12 +347,30 @@ func (e *Engine) Turn(ctx context.Context, req TurnRequest) (TurnResult, error)
payload = append(payload, cm)
}
reply, err := e.runTurn(ctx, client, model, req.Channel, payload)
provider := "local"
if recErr == nil {
provider = rec.Provider
}
if servedByLocalBinding(provider) {
provider = "local"
}
start := time.Now()
reply, err := e.runTurn(ctx, client, model, req.Channel, payload, sink)
if err != nil {
// the user message stays persisted: the user DID send it (the
// Python oracle's behavior); the failure is honest, not silent
e.governor.RecordError(provider, err)
return TurnResult{UserMsgID: userMsg.MsgID, Model: model}, err
}
// D56 throughput telemetry: the governor learns from the real
// turn — the provider's own generated-token accounting when the
// wire carries it, the assistant text's token estimate otherwise
// (an honest estimate, never presented as a wire measurement).
outTokens := reply.OutputTokens
if outTokens <= 0 {
outTokens = tokenEstimate(reply.Content)
}
e.governor.RecordTurn(provider, model, outTokens, time.Since(start), 1)
assistant := Message{
MsgID: newMsgID(),
@@ -328,6 +388,39 @@ func (e *Engine) Turn(ctx context.Context, req TurnRequest) (TurnResult, error)
if err := e.store.Append(assistant); err != nil {
return TurnResult{UserMsgID: userMsg.MsgID, Model: model}, err
}
// D57 model-refusal authority: the model's capability claims are
// checked against the runtime's ACTUAL state. A false "I lack
// tools/access" claim is never passed to the owner as truth — the
// engine composes a correction turn, retries once with it on the
// wire, persists the corrected answer, and journals the event.
correction := e.checkRefusal(req.Channel, reply.Content)
if correction != "" {
wireTools := ToolsetFor(modeForDispatch(req.Channel, e.home))
retryMsgs := append(payload, ChatMessage{Role: "assistant", Content: reply.Content})
retryMsgs = append(retryMsgs, ChatMessage{Role: "user", Content: correction})
if retry, err := modelOnce(ctx, client, model, retryMsgs, wireTools, nil); err == nil {
followUp := Message{
MsgID: newMsgID(),
Ts: utcNow(),
Role: "assistant",
Content: retry.Content,
ChannelID: req.Channel,
TokenEstimate: tokenEstimate(retry.Content),
SchemaVersion: SnapshotSchemaVersion,
}
if appendErr := e.store.Append(followUp); appendErr == nil {
return TurnResult{
UserMsgID: userMsg.MsgID,
AssistantMsgID: followUp.MsgID,
AssistantText: retry.Content,
Model: model,
ContextUsed: reply.ContextUsed,
ContextLimit: reply.ContextLimit,
}, nil
}
}
}
return TurnResult{
UserMsgID: userMsg.MsgID,
AssistantMsgID: assistant.MsgID,
@@ -0,0 +1,109 @@
package runecore
// engine_refusal.go — the D57 refusal-authority hook on the engine:
// classification, the correction turn, the typed journal event and
// the session counters /v1/status's model_authority section serves.
import (
"encoding/json"
"os"
"path/filepath"
"time"
"unicode/utf8"
)
// checkRefusal classifies one assistant reply against the runtime's
// actual capability state, journals the typed event, and returns the
// correction turn text ("" when no correction is owed).
func (e *Engine) checkRefusal(channel, reply string) string {
if utf8.RuneCountInString(reply) == 0 {
return ""
}
mode := modeForDispatch(channel, e.home)
tools := ToolsetFor(mode)
suiteOnWire := len(tools) > 0
names := make([]string, 0, len(tools))
for _, t := range tools {
names = append(names, t.Function.Name)
}
v := ClassifyRefusal(reply, suiteOnWire, names)
if v.Kind == "" {
return ""
}
e.authorityMu.Lock()
e.refusalsSeen++
if v.Kind == "false_capability_claim" {
e.correctionsIssued++
}
e.authorityMu.Unlock()
e.appendRefusalEvent(channel, v)
if v.Kind == "false_capability_claim" {
return v.Correction
}
return ""
}
// appendRefusalEvent writes the typed refusal-correction journal
// event (event_type capability.refusal_correction) to the rune
// journal at <home>/memory/journal/events.jsonl.
func (e *Engine) appendRefusalEvent(channel string, v RefusalVerdict) {
dir := filepath.Join(e.home, "memory", "journal")
if err := os.MkdirAll(dir, 0o755); err != nil {
return
}
payload := map[string]any{
"channel": channel,
"kind": v.Kind,
"phrase": v.Phrase,
"detected": "model_refusal_classifier/d57",
}
raw, err := json.Marshal(payload)
if err != nil {
return
}
event := map[string]any{
"event_id": "evt_" + newMsgID(),
"event_type": "capability.refusal_correction",
"partition_key": "rune_operator",
"schema_version": 1,
"timestamp": time.Now().UTC().Format("2006-01-02T15:04:05.000000Z07:00"),
"payload": json.RawMessage(raw),
}
line, err := json.Marshal(event)
if err != nil {
return
}
f, err := os.OpenFile(filepath.Join(dir, "events.jsonl"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return
}
defer f.Close()
_, _ = f.Write(append(line, '\n'))
}
// AuthoritySnapshot is the /v1/status model_authority section: the
// runtime's capability facts and this session's refusal accounting.
func (e *Engine) AuthoritySnapshot() map[string]any {
mode := ActiveMode(e.home)
tools := ToolsetFor(mode)
names := make([]string, 0, len(tools))
for _, t := range tools {
names = append(names, t.Function.Name)
}
execPath, execFound := ResolveToolExecutorPath()
e.authorityMu.Lock()
refusals, corrections := e.refusalsSeen, e.correctionsIssued
e.authorityMu.Unlock()
return map[string]any{
"suite_on_wire": len(tools) > 0,
"gates_open": len(tools) > 0,
"tool_names": names,
"active_mode": mode,
"executor_path": execPath,
"executor_deployed": execFound,
"refusals_seen": refusals,
"corrections_issued": corrections,
}
}
@@ -0,0 +1,481 @@
package runecore
// governor.go — the provider-aware dispatch governor (Gitea #2580 +
// #2581, owner doctrine 2026-10-24: CLOUD-FIRST routing — cloud lanes
// fill FIRST, speed is the priority; local absorbs only overflow; a
// configured provider is spend-freely authorized, no consent gates).
//
// Everything here is learned from PRODUCTION samples (the engine's
// real turn accounting): no synthetic benchmarks, no guessed curves.
// Per-provider rolling samples persist under the hot-state pattern
// (~/.rune/hot/provider_throughput.json) so the model survives
// restarts.
import (
"encoding/json"
"fmt"
"net/http"
"os"
"path/filepath"
"sort"
"strings"
"sync"
"time"
)
// HotThroughputFile is the hot-state path, relative to the rune home:
// per-provider rolling throughput samples + the learned saturation
// curve (the same survives-restart pattern as task_state.json).
const HotThroughputFile = "hot/provider_throughput.json"
// sampleWindow is the rolling sample cap per provider (the model is
// small and local: recent behavior only).
const sampleWindow = 256
// DefaultCapFloor is the default marginal tok/s gain below which
// another lane is judged useless (configurable: RUNE_GOVERNOR_CAP_FLOOR).
const DefaultCapFloor = 5.0
// ThroughputSample is one production measurement: tokens the provider
// actually generated in the wall time it took, at the lane count the
// provider was serving.
type ThroughputSample struct {
At int64 `json:"at"` // unix seconds
Provider string `json:"provider"`
Model string `json:"model,omitempty"`
TokPerSec float64 `json:"tok_per_sec"`
Lanes int `json:"lanes"`
}
// ProviderHealth is the connection-health row (#2581): what the
// governor actually knows about one provider's liveness.
type ProviderHealth struct {
Reachable bool `json:"reachable"`
Authenticated bool `json:"authenticated"`
ErrorRate float64 `json:"error_rate"` // recent errors / attempts
TokPerSec float64 `json:"tok_per_sec"`
LanesActive int `json:"lanes_active"`
LanesCap int `json:"lanes_cap"`
ThrottledUntil int64 `json:"throttled_until,omitempty"` // unix secs; a 429 ceiling
LastSeen int64 `json:"last_seen,omitempty"`
LastError string `json:"last_error,omitempty"`
}
// GovernorSnapshot is the honest external face: /v1/status's governor
// section and the TUI's readout all render THIS — never more than the
// governor measured.
type GovernorSnapshot struct {
TokensPerSec float64 `json:"tokens_per_sec"` // 0 = nothing measured yet
LanesActive int `json:"lanes_active"`
LanesCap int `json:"lanes_cap"`
Providers map[string]ProviderHealth `json:"providers"`
}
// governorState is the persisted hot-state shape.
type governorState struct {
SchemaVersion int `json:"schema_version"`
Samples map[string][]ThroughputSample `json:"samples"`
// saturation is the learned curve: (provider, lanes) → EMA tok/s.
Saturation map[string]map[int]float64 `json:"saturation"`
}
// Governor is the dispatch governor. Zero-value is usable but inert;
// NewGovernor loads the persisted hot state.
type Governor struct {
mu sync.Mutex
home string
state governorState
health map[string]*ProviderHealth
attempts map[string]int
errors map[string]int
lanes map[string]int // provider -> active lanes
lastOrder []string // most-recently-used providers (the snapshot's headline tok/s)
probed bool // at least one health probe ran in this process
floor float64
}
// GovernorCapFloorEnv resizes the marginal-gain floor (tok/s).
const GovernorCapFloorEnv = "RUNE_GOVERNOR_CAP_FLOOR"
// NewGovernor builds a governor rooted at home (the rune home dir),
// loading any persisted hot state. A missing or corrupt file is an
// empty model, never an error (the hot-state freshness law).
func NewGovernor(home string) *Governor {
g := &Governor{
home: home,
state: governorState{
SchemaVersion: 1,
Samples: map[string][]ThroughputSample{},
Saturation: map[string]map[int]float64{},
},
health: map[string]*ProviderHealth{},
attempts: map[string]int{},
errors: map[string]int{},
lanes: map[string]int{},
floor: DefaultCapFloor,
}
if v := strings.TrimSpace(os.Getenv(GovernorCapFloorEnv)); v != "" {
if f, err := parsePositiveFloat(v); err == nil && f > 0 {
g.floor = f
}
}
g.load()
return g
}
func parsePositiveFloat(s string) (float64, error) {
var f float64
_, err := fmt.Sscanf(s, "%g", &f)
return f, err
}
func (g *Governor) hotPath() string {
return filepath.Join(g.home, HotThroughputFile)
}
func (g *Governor) load() {
data, err := os.ReadFile(g.hotPath())
if err != nil {
return
}
var st governorState
if json.Unmarshal(data, &st) != nil || st.Samples == nil {
return
}
if st.Saturation == nil {
st.Saturation = map[string]map[int]float64{}
}
g.state = st
}
// persist writes the hot state atomically (temp + rename — a reader
// never sees a torn file). Best-effort: telemetry must never break a
// turn.
func (g *Governor) persist() {
data, err := json.Marshal(g.state)
if err != nil {
return
}
path := g.hotPath()
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return
}
tmp := path + ".tmp"
if err := os.WriteFile(tmp, data, 0o600); err != nil {
return
}
_ = os.Rename(tmp, path)
}
// RecordTurn samples one completed turn: the provider that served it,
// the tokens it generated (the client's own accounting; the engine's
// honest token-estimate fallback when the wire carries none), the wall
// duration, and the lane count it served at. This is the ONLY way the
// model learns — production samples, never benchmarks.
func (g *Governor) RecordTurn(provider, model string, outputTokens int, dur time.Duration, lanes int) {
if provider == "" {
provider = "local"
}
if lanes < 1 {
lanes = 1
}
if outputTokens <= 0 || dur <= 0 {
return // nothing measurable — the model never invents a rate
}
tps := float64(outputTokens) / dur.Seconds()
g.mu.Lock()
defer g.mu.Unlock()
now := time.Now().Unix()
s := ThroughputSample{At: now, Provider: provider, Model: model, TokPerSec: tps, Lanes: lanes}
samples := append(g.state.Samples[provider], s)
if len(samples) > sampleWindow {
samples = samples[len(samples)-sampleWindow:]
}
g.state.Samples[provider] = samples
// the saturation point: EMA over the lanes bucket (smooth, small)
b := g.state.Saturation[provider]
if b == nil {
b = map[int]float64{}
}
b[lanes] = ema(b[lanes], tps)
g.state.Saturation[provider] = b
h := g.health[provider]
if h == nil {
h = &ProviderHealth{}
g.health[provider] = h
}
h.Reachable, h.Authenticated = true, true
h.TokPerSec = b[lanes]
h.LastSeen = now
h.LastError = ""
g.touchOrder(provider)
g.persist()
}
// RecordError marks a failed attempt against a provider (the health
// row's error rate).
func (g *Governor) RecordError(provider string, err error) {
if provider == "" {
provider = "local"
}
g.mu.Lock()
defer g.mu.Unlock()
g.attempts[provider]++
g.errors[provider]++
h := g.health[provider]
if h == nil {
h = &ProviderHealth{}
g.health[provider] = h
}
h.Reachable = false
h.LastError = fmt.Sprint(err)
if g.attempts[provider] > 0 {
h.ErrorRate = float64(g.errors[provider]) / float64(g.attempts[provider])
}
}
// MarkThrottled records a 429: the published TPM/RPM ceiling just bit.
// A throttled provider is WORSE than not spawning — routes refuse it
// until the window lapses.
func (g *Governor) MarkThrottled(provider string, until time.Time) {
g.mu.Lock()
defer g.mu.Unlock()
h := g.health[provider]
if h == nil {
h = &ProviderHealth{}
g.health[provider] = h
}
h.ThrottledUntil = until.Unix()
}
// AcquireLane takes one lane on a provider (the pool's in-use count).
func (g *Governor) AcquireLane(provider string) {
g.mu.Lock()
defer g.mu.Unlock()
g.lanes[provider]++
}
// ReleaseLane returns one lane.
func (g *Governor) ReleaseLane(provider string) {
g.mu.Lock()
defer g.mu.Unlock()
if g.lanes[provider] > 0 {
g.lanes[provider]--
}
}
// SaturationCap answers the lane cap for a provider: the highest lane
// count whose MARGINAL tok/s gain still clears the floor. With no
// curve learned, the honest cap is 1 (never invent headroom).
func (g *Governor) SaturationCap(provider string) int {
g.mu.Lock()
defer g.mu.Unlock()
return g.saturationCapLocked(provider)
}
func (g *Governor) saturationCapLocked(provider string) int {
b := g.state.Saturation[provider]
if len(b) == 0 {
return 1
}
lanes := sortedLanes(b)
cap := 1
prev := b[lanes[0]]
for _, l := range lanes[1:] {
gain := b[l] - prev
if gain < g.floor {
break
}
cap = l
prev = b[l]
}
return cap
}
func sortedLanes(b map[int]float64) []int {
out := make([]int, 0, len(b))
for l := range b {
out = append(out, l)
}
sort.Ints(out)
return out
}
// Route applies the owner doctrine: CLOUD-FIRST — among healthy
// candidates, every cloud provider (a non-local binding; configured
// providers are spend-authorized, no consent gates) fills its lane
// pool BEFORE any local provider absorbs overflow. Within a tier the
// least-loaded provider wins (lanes/cap ratio). Unhealthy or
// throttled providers are refused outright (route only to providers
// actually alive). The second return is false when NO candidate is
// healthy — the thin-pipe honesty case.
func (g *Governor) Route(candidates []string) (ordered []string, healthy bool) {
g.mu.Lock()
defer g.mu.Unlock()
var cloud, local []string
now := time.Now().Unix()
key := func(p string) float64 {
h := g.health[p]
c := 1
if h != nil && h.LanesCap > 0 {
c = h.LanesCap
}
return float64(g.lanes[p]) / float64(c)
}
less := func(a, b string) bool { return key(a) < key(b) }
for _, p := range candidates {
h := g.health[p]
if h != nil {
if !h.Reachable || (h.ThrottledUntil > 0 && h.ThrottledUntil > now) {
continue // not alive / 429'd — worse than not spawning
}
} else if g.probed {
// Health telemetry is live and this provider never came
// back alive: refuse it (#2581 — route only to providers
// actually alive). Before any probe ran, day-one routing
// passes candidates through (never refuse what we never
// asked).
continue
}
if servedByLocalBinding(p) {
local = append(local, p)
} else {
cloud = append(cloud, p)
}
}
sort.Slice(cloud, func(i, j int) bool { return less(cloud[i], cloud[j]) })
sort.Slice(local, func(i, j int) bool { return less(local[i], local[j]) })
ordered = append(cloud, local...)
return ordered, len(ordered) > 0
}
// MarkReachable records a health probe outcome (#2581): reachable is
// the probe's liveness verdict, authenticated whether the wire
// accepted the key.
func (g *Governor) MarkReachable(provider string, reachable, authenticated bool) {
g.mu.Lock()
defer g.mu.Unlock()
h := g.health[provider]
if h == nil {
h = &ProviderHealth{}
g.health[provider] = h
}
h.Reachable = reachable
h.Authenticated = authenticated
if reachable {
h.LastSeen = time.Now().Unix()
h.LastError = ""
}
g.probed = true
}
func (g *Governor) touchOrder(p string) {
for i, q := range g.lastOrder {
if q == p {
g.lastOrder = append(g.lastOrder[:i], g.lastOrder[i+1:]...)
break
}
}
g.lastOrder = append(g.lastOrder, p)
}
// Snapshot renders the honest state: the headline tok/s is the most
// recently sampled provider's current rate (0 when nothing was ever
// measured — the bar shows '—', never a guess); lanes are the global
// active count against the sum of the pools' caps.
func (g *Governor) Snapshot() GovernorSnapshot {
g.mu.Lock()
defer g.mu.Unlock()
snap := GovernorSnapshot{Providers: map[string]ProviderHealth{}}
for p, h := range g.health {
row := *h
row.LanesCap = g.saturationCapLocked(p)
row.LanesActive = g.lanes[p]
snap.Providers[p] = row
}
// headline tok/s: the freshest sample's provider, at the lane
// count we actually run there — persisted state survives restarts
// (lastOrder doesn't), so the read comes from the samples.
var freshestP string
var freshestAt int64
for p, ss := range g.state.Samples {
if len(ss) == 0 {
continue
}
if last := ss[len(ss)-1]; last.At >= freshestAt {
freshestAt, freshestP = last.At, p
}
}
if freshestP != "" {
lanes := g.lanes[freshestP]
if lanes < 1 {
lanes = 1
}
b := g.state.Saturation[freshestP]
if v, ok := b[lanes]; ok {
snap.TokensPerSec = v
} else if len(b) > 0 {
for _, l := range sortedLanes(b) {
snap.TokensPerSec = b[l] // fall back to the freshest learned bucket
}
}
}
for p := range g.state.Saturation {
if _, ok := snap.Providers[p]; !ok {
cap := g.saturationCapLocked(p)
snap.Providers[p] = ProviderHealth{Reachable: true, Authenticated: true, LanesCap: cap, LanesActive: g.lanes[p]}
}
snap.LanesActive += g.lanes[p]
snap.LanesCap += g.saturationCapLocked(p)
}
return snap
}
// ema is the one-pole smooth: alpha small enough to ignore a single
// outlier, big enough to track a real shift within a few turns.
func ema(prev, next float64) float64 {
if prev == 0 {
return next
}
const alpha = 0.3
return prev + alpha*(next-prev)
}
// PollRegistryHealth probes every enabled, URL-bearing provider in
// the landed registry (#2581): a cheap GET against the provider's
// wire base with a short timeout. The verdict lands through
// MarkReachable — reachable = the probe answered, authenticated = a
// key ref is wired (the probe proves the WIRE, the ref proves the
// credential is configured; a real 401 surfaces as an error sample).
// Returns the number of providers probed.
func (g *Governor) PollRegistryHealth(path string, timeout time.Duration) int {
rows, err := LoadProviderRegistry(path)
if err != nil {
return 0
}
probed := 0
for _, e := range rows {
if !e.Enabled || strings.TrimSpace(e.BaseURL()) == "" {
continue
}
client := &http.Client{Timeout: timeout}
resp, perr := client.Get(strings.TrimRight(e.BaseURL(), "/") + "/models")
if perr != nil {
g.RecordError(e.Name, perr)
g.MarkReachable(e.Name, false, e.APIKeyRef != "")
probed++
continue
}
resp.Body.Close()
g.MarkReachable(e.Name, true, e.APIKeyRef != "")
probed++
}
return probed
}
// HealthPollInterval is the live-state cadence.
const HealthPollInterval = 60 * time.Second
// isCloudProvider answers the doctrine's tier split: anything NOT a
// local binding is cloud (the same law the engine's router uses).
func isCloudProvider(p string) bool { return !servedByLocalBinding(p) }
@@ -0,0 +1,137 @@
package runecore
// governor_test.go — the D56 governor's frozen laws: production-only
// samples (a turn with no tokens is never a sample), the honest cap
// (no curve = 1 lane, never invented headroom), cloud-first ordering
// with health-gating and 429 refusal, and hot-state persistence.
import (
"os"
"path/filepath"
"testing"
"time"
)
func TestGovernorRecordTurnSamplesAndPersists(t *testing.T) {
home := t.TempDir()
g := NewGovernor(home)
g.RecordTurn("anthropic", "claude-x", 300, 10*time.Second, 1)
snap := g.Snapshot()
if snap.TokensPerSec != 30 {
t.Fatalf("tok/s = %v, want 30", snap.TokensPerSec)
}
// hot state: a fresh governor over the same home sees the curve
if _, err := os.Stat(filepath.Join(home, HotThroughputFile)); err != nil {
t.Fatalf("hot state not persisted: %v", err)
}
g2 := NewGovernor(home)
if got := g2.SaturationCap("anthropic"); got < 1 {
t.Fatalf("cap after one sample = %d, want >=1", got)
}
if g2.Snapshot().TokensPerSec == 0 {
t.Fatal("reloaded governor lost the measured tok/s")
}
}
func TestGovernorNoTokensNoSample(t *testing.T) {
home := t.TempDir()
g := NewGovernor(home)
g.RecordTurn("anthropic", "m", 0, time.Second, 1)
if snap := g.Snapshot(); snap.TokensPerSec != 0 || len(snap.Providers) != 0 {
t.Fatalf("zero-token turn invented a sample: %+v", snap)
}
}
func TestGovernorCapHonestWithoutCurve(t *testing.T) {
home := t.TempDir()
g := NewGovernor(home)
if got := g.SaturationCap("never-measured"); got != 1 {
t.Fatalf("cap with no curve = %d, want 1 (never invent headroom)", got)
}
}
func TestGovernorCapFromMarginalGain(t *testing.T) {
home := t.TempDir()
g := NewGovernor(home)
// 1 lane: 40 tok/s; 2 lanes: 70 (gain 30 >= floor); 3 lanes: 72
// (gain 2 < floor) — the cap is 2 lanes.
g.RecordTurn("p", "m", 400, 10*time.Second, 1)
g.RecordTurn("p", "m", 700, 10*time.Second, 2)
g.RecordTurn("p", "m", 720, 10*time.Second, 3)
if got := g.SaturationCap("p"); got != 2 {
t.Fatalf("cap = %d, want 2 (marginal gain at 3 lanes below the floor)", got)
}
}
func TestGovernorRouteCloudFirstAndHealthGated(t *testing.T) {
home := t.TempDir()
g := NewGovernor(home)
g.MarkReachable("ollama", true, true)
g.MarkReachable("anthropic", true, true)
g.MarkReachable("openai", true, true)
g.MarkReachable("deadcloud", false, false)
ordered, healthy := g.Route([]string{"ollama", "anthropic", "openai", "deadcloud"})
if !healthy {
t.Fatal("healthy candidates exist; Route lied about the thin pipe")
}
// cloud-first: both cloud rows precede local; deadcloud refused.
foundCloud, foundLocal, sawDead := 0, 0, false
for i, p := range ordered {
switch p {
case "anthropic", "openai":
foundCloud++
if foundLocal > 0 {
t.Fatalf("cloud row %s routed after local — doctrine broken (order %v)", p, ordered)
}
case "ollama":
foundLocal++
case "deadcloud":
sawDead = true
}
_ = i
}
if foundCloud != 2 || foundLocal != 1 || sawDead {
t.Fatalf("route order wrong: %v", ordered)
}
// 429 throttle = worse than not spawning
g.MarkThrottled("openai", time.Now().Add(time.Hour))
ordered, _ = g.Route([]string{"openai"})
if len(ordered) != 0 {
t.Fatalf("throttled provider routed: %v", ordered)
}
}
func TestGovernorThinPipeHonesty(t *testing.T) {
home := t.TempDir()
g := NewGovernor(home)
// health telemetry live: the probe ran, the provider never came
// back alive — the route must refuse and report the thin pipe.
g.MarkReachable("ollama", false, false)
_, healthy := g.Route([]string{"ollama"})
if healthy {
t.Fatal("dead provider routed: Route must report the thin pipe")
}
// day-one honesty in the other direction: before ANY probe ran,
// routing passes through (never refuse what we never asked).
fresh := NewGovernor(t.TempDir())
_, healthy = fresh.Route([]string{"ollama"})
if !healthy {
t.Fatal("pre-probe routing must pass through, not refuse")
}
}
func TestGovernorLanesCount(t *testing.T) {
home := t.TempDir()
g := NewGovernor(home)
g.RecordTurn("anthropic", "m", 100, time.Second, 1)
g.AcquireLane("anthropic")
g.AcquireLane("anthropic")
snap := g.Snapshot()
if snap.Providers["anthropic"].LanesActive != 2 || snap.LanesActive != 2 {
t.Fatalf("lanes = %+v, want 2", snap)
}
g.ReleaseLane("anthropic")
if snap := g.Snapshot(); snap.Providers["anthropic"].LanesActive != 1 {
t.Fatalf("lanes after release = %d, want 1", snap.Providers["anthropic"].LanesActive)
}
}
+108 -7
View File
@@ -8,6 +8,7 @@ package runecore
// same endpoint, same envelope, stdlib HTTP.
import (
"bufio"
"bytes"
"context"
"encoding/json"
@@ -22,22 +23,27 @@ import (
type OllamaClient struct {
baseURL string
client *http.Client
// streamClient carries NO whole-call Timeout: the streaming leg is
// governed by the inter-chunk idle watch (Client.Timeout caps body
// reads too — a legitimate long stream would be cut mid-answer).
streamClient *http.Client
}
// DefaultOllamaURL is the Python adapter's default endpoint.
const DefaultOllamaURL = "http://localhost:11434"
// NewOllamaClient builds the client. Empty base URL falls back to the
// default. The chat budget mirrors the chain's law (>=330s — the
// 27B-class answer window plus headroom, the same number the TUI
// completion client and the entry proxy carry).
// NewOllamaClient builds the client. The non-streaming client's chat
// budget mirrors the chain's law (>=330s — the 27B-class answer window
// plus headroom, the same number the TUI completion client and the
// entry proxy carry); the streaming client leans on the idle watch.
func NewOllamaClient(baseURL string) *OllamaClient {
if baseURL == "" {
baseURL = DefaultOllamaURL
}
return &OllamaClient{
baseURL: strings.TrimSuffix(baseURL, "/"),
client: &http.Client{Timeout: 330 * time.Second},
baseURL: strings.TrimSuffix(baseURL, "/"),
client: &http.Client{Timeout: 330 * time.Second},
streamClient: &http.Client{},
}
}
@@ -127,7 +133,7 @@ func (c *OllamaClient) ChatWithTools(ctx context.Context, model string, msgs []C
// server-side for qwen3-family even without think:true).
visible, inline := splitInlineThinking(out.Message.Content)
reply := ChatReply{Content: visible, Model: out.Model,
ContextUsed: out.PromptEvalCount}
ContextUsed: out.PromptEvalCount, OutputTokens: out.EvalCount}
for _, tc := range out.Message.ToolCalls {
call := ToolCall{Name: tc.Function.Name}
if len(tc.Function.Arguments) > 0 {
@@ -233,6 +239,101 @@ func clipStr(s string, n int) string {
return s[:n]
}
// ollamaStreamChunk is one NDJSON line of a streamed /api/chat: a
// message/token chunk (content and/or thinking and/or tool_calls) or
// the terminal done:true chunk carrying the token accounting.
type ollamaStreamChunk struct {
Model string `json:"model"`
Message struct {
Role string `json:"role"`
Content string `json:"content"`
Thinking *string `json:"thinking"`
ToolCalls []ollamaToolCall `json:"tool_calls"`
} `json:"message"`
Done bool `json:"done"`
PromptEvalCount int `json:"prompt_eval_count"`
EvalCount int `json:"eval_count"`
}
// StreamChatWithTools is the streaming leg (Gitea #2556): stream=true
// /api/chat, NDJSON chunks parsed incrementally, every content and
// thinking byte surfaced through sink AS IT ARRIVES; the final reply
// carries the assembled content + thinking + tool_calls exactly like
// the non-streaming leg's. The inter-chunk idle watch replaces the
// flat deadline: no bytes for the idle budget = ErrStreamIdle; the
// 30-minute backstop is the runaway guard.
func (c *OllamaClient) StreamChatWithTools(ctx context.Context, model string, msgs []ChatMessage, tools []ToolDef, sink StreamSink) (ChatReply, error) {
idle := chunkIdleTimeout()
w := watchIdle(ctx, idle, StreamTurnBackstop)
defer w.stop()
body, err := json.Marshal(ollamaChatRequest{Model: model, Messages: msgs, Stream: true, Tools: tools})
if err != nil {
return ChatReply{}, err
}
req, err := http.NewRequestWithContext(w.ctx, http.MethodPost, c.baseURL+"/api/chat", bytes.NewReader(body))
if err != nil {
return ChatReply{}, err
}
req.Header.Set("Content-Type", "application/json")
resp, err := c.streamClient.Do(req)
if err != nil {
return ChatReply{}, w.err(fmt.Errorf("ollama: %w", err), idle)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
raw, _ := io.ReadAll(io.LimitReader(resp.Body, 2048))
return ChatReply{}, fmt.Errorf("ollama /api/chat: HTTP %d: %s", resp.StatusCode, clipStr(string(raw), 300))
}
var content, thinking strings.Builder
var toolCalls []ToolCall
var last ollamaStreamChunk
sc := bufio.NewScanner(resp.Body)
sc.Buffer(make([]byte, 0, 64*1024), 1024*1024)
for sc.Scan() {
w.reset(idle)
line := strings.TrimSpace(sc.Text())
if line == "" {
continue
}
var chunk ollamaStreamChunk
if err := json.Unmarshal([]byte(line), &chunk); err != nil {
return ChatReply{}, fmt.Errorf("ollama /api/chat stream decode: %w", err)
}
last = chunk
if chunk.Message.Content != "" {
content.WriteString(chunk.Message.Content)
emit(sink, StreamDelta{Kind: "text", Text: chunk.Message.Content})
}
if chunk.Message.Thinking != nil && *chunk.Message.Thinking != "" {
thinking.WriteString(*chunk.Message.Thinking)
emit(sink, StreamDelta{Kind: "thinking", Text: *chunk.Message.Thinking})
}
for _, tc := range chunk.Message.ToolCalls {
call := ToolCall{Name: tc.Function.Name}
if len(tc.Function.Arguments) > 0 {
var m map[string]any
if json.Unmarshal(tc.Function.Arguments, &m) == nil {
call.Args = m
}
}
toolCalls = append(toolCalls, call)
}
}
if err := sc.Err(); err != nil {
return ChatReply{}, w.err(fmt.Errorf("ollama stream read: %w", err), idle)
}
visible, inline := splitInlineThinking(content.String())
reply := ChatReply{Content: visible, Model: last.Model, ContextUsed: last.PromptEvalCount, OutputTokens: last.EvalCount}
reply.ToolCalls = toolCalls
reply.ContextLimit = c.contextLimit(ctx, model)
if thinking.Len() > 0 {
reply.Thinking = []string{thinking.String()}
} else if inline != "" {
reply.Thinking = []string{inline}
}
return reply, nil
}
// showCapabilities asks /api/show for the model's capabilities list
// ("tools", "thinking", ...). Fail-soft: error → nil.
func (c *OllamaClient) showCapabilities(ctx context.Context, model string) ([]string, error) {
@@ -0,0 +1,139 @@
package runecore
// refusal.go — the D57 model-refusal authority (Gitea #2590/#2591).
// D49 proved the full tool suite rides the wire, so a model replying
// "I cannot — I lack access to tools" makes a FALSE capability claim,
// and D57 forbids passing it to the owner as truth. The classifier is
// DETERMINISTIC literal-phrase tiers (the ND law: no clever NLP —
// no embeddings, no scoring, no LLM judge; a fixture battery pins the
// exact strings). On a detected capability claim, the runtime verifies
// against its ACTUAL capability state (suite on wire, gates open); a
// false claim composes a correction turn and a typed journal event.
import (
"fmt"
"sort"
"strings"
)
// RefusalVerdict is the classifier's typed answer for one assistant
// reply.
type RefusalVerdict struct {
// Kind: "" (no refusal language), "false_capability_claim",
// "true_refusal" (capability claim and the runtime genuinely
// lacks the suite), "genuine_limit" (limitation language with no
// capability object — "I cannot fit that in one message").
Kind string `json:"kind"`
Phrase string `json:"phrase,omitempty"`
// Correction is the composed correction turn (non-empty only for
// false_capability_claim).
Correction string `json:"correction,omitempty"`
}
// capabilityClaimPhrases are literal, inherently-capability phrases —
// a hit is a claim about tools/access/filesystem by construction.
var capabilityClaimPhrases = []string{
"i don't have access to",
"i do not have access to",
"i don't have access",
"i do not have access",
"i lack access",
"i have no access",
"no access to the file system",
"no access to the filesystem",
"i can't access the filesystem",
"i cannot access the filesystem",
"i can't access files",
"i cannot access files",
"i don't have tools",
"i do not have tools",
"i have no tools",
"i lack tools",
"i don't have any tools",
"no tool access",
"tools are not available",
"tool access is not available",
"i don't have permission to access",
"i do not have permission to access",
"i am unable to access the filesystem",
"i'm unable to access the filesystem",
"i am unable to access files",
"i'm unable to access files",
"i need shell access",
"i would need shell access",
}
// limitPhrases are limitation words that, WITHOUT a capability object,
// classify as a genuine limit — logged, never corrected.
var limitPhrases = []string{
"i cannot", "i can't", "i am unable", "i'm unable", "i won't",
}
// capabilityObjects are the nouns that turn a bare "cannot" sentence
// into a capability claim.
var capabilityObjects = []string{
"tool", "access", "file", "filesystem", "file system",
"shell", "terminal", "permission",
}
// ClassifyRefusal runs the deterministic tiers over one assistant
// reply. suiteOnWire is the runtime's ACTUAL capability state (D49:
// the full suite rides the wire in every pane, so a live core passes
// true here — the parameter exists so the verdict is verified, not
// assumed, and so a future gated build classifies honestly).
func ClassifyRefusal(reply string, suiteOnWire bool, toolNames []string) RefusalVerdict {
lower := strings.ToLower(reply)
for _, p := range capabilityClaimPhrases {
if strings.Contains(lower, p) {
if suiteOnWire {
return RefusalVerdict{
Kind: "false_capability_claim",
Phrase: p,
Correction: composeCorrection(toolNames),
}
}
return RefusalVerdict{Kind: "true_refusal", Phrase: p}
}
}
// Tier 2: a bare limitation word whose SENTENCE also names a
// capability object ("I cannot read files on your machine").
for _, sentence := range strings.Split(lower, ".") {
if !containsAny(sentence, limitPhrases) {
continue
}
if containsAny(sentence, capabilityObjects) {
if suiteOnWire {
return RefusalVerdict{
Kind: "false_capability_claim",
Phrase: strings.TrimSpace(sentence),
Correction: composeCorrection(toolNames),
}
}
return RefusalVerdict{Kind: "true_refusal", Phrase: strings.TrimSpace(sentence)}
}
// Limitation language, no capability object: a genuine limit
// ("I cannot fit that in one message") — never corrected.
return RefusalVerdict{Kind: "genuine_limit"}
}
return RefusalVerdict{}
}
func containsAny(s string, needles []string) bool {
for _, n := range needles {
if strings.Contains(s, n) {
return true
}
}
return false
}
// composeCorrection builds the correction turn: the tool names are
// the runtime's actual, current suite.
func composeCorrection(toolNames []string) string {
if len(toolNames) == 0 {
return "Correction: you DO have tool access in this session."
}
names := append([]string(nil), toolNames...)
sort.Strings(names)
return fmt.Sprintf("Correction: you DO have these tools available right now — %s. Do not claim you lack access; call the tool directly and retry the task.", strings.Join(names, ", "))
}
@@ -0,0 +1,64 @@
package runecore
import (
"strings"
"testing"
)
// D57 red-first fixture battery: the deterministic classifier must
// separate true-refusal / false-capability-claim / genuine-limit with
// literal-phrase tiers (ND law: no clever NLP — every fixture is a
// pinned string, and every verdict is asserted).
func TestClassifyRefusalFixtureBattery(t *testing.T) {
suiteNames := []string{"read_file", "write_file", "run_command"}
cases := []struct {
name string
reply string
suite bool
wantKind string
wantFix bool // a correction turn is composed
}{
// FALSE capability claims — D49 proved the suite rides the
// wire, so these are lies the owner must never receive as truth.
{"no tools claim", "I'm sorry, I don't have tools to do that.", true, "false_capability_claim", true},
{"no access claim", "I cannot do this — I don't have access to your filesystem.", true, "false_capability_claim", true},
{"lack access claim", "I lack access to files on your machine.", true, "false_capability_claim", true},
{"unable to access", "I'm unable to access the filesystem from here.", true, "false_capability_claim", true},
{"needs shell", "I would need shell access to change her code.", true, "false_capability_claim", true},
{"bare cannot + object", "I cannot read files on your machine.", true, "false_capability_claim", true},
{"no tools claim, suite off", "I don't have tools to do that.", false, "true_refusal", false},
// GENUINE limits — limitation language with no capability
// object is real and must NOT be corrected.
{"length limit", "I cannot fit that in one message.", true, "genuine_limit", false},
{"knowledge limit", "I can't know what happened in 1850.", true, "genuine_limit", false},
// NO refusal language at all.
{"plain answer", "I wrote the file to ~/notes.txt.", true, "", false},
}
for _, tc := range cases {
v := ClassifyRefusal(tc.reply, tc.suite, suiteNames)
if v.Kind != tc.wantKind {
t.Errorf("%s: kind=%q want %q", tc.name, v.Kind, tc.wantKind)
}
if tc.wantFix && v.Correction == "" {
t.Errorf("%s: no correction composed", tc.name)
}
if !tc.wantFix && v.Correction != "" {
t.Errorf("%s: unexpected correction %q", tc.name, v.Correction)
}
}
}
// The correction names the ACTUAL suite — the model is told what it
// has, never scolded vaguely.
func TestCorrectionNamesActualSuite(t *testing.T) {
names := []string{"write_file", "read_file", "run_command"}
v := ClassifyRefusal("I don't have tools for that.", true, names)
if !strings.Contains(v.Correction, "you DO have these tools") {
t.Errorf("correction does not affirm capability: %q", v.Correction)
}
for _, n := range names {
if !strings.Contains(v.Correction, n) {
t.Errorf("correction missing tool %q: %q", n, v.Correction)
}
}
}
@@ -38,6 +38,16 @@ type RegistryModel struct {
ContextLimit int // the row's limit.context; 0 = the row records none
}
// BaseURL is the provider's wire base: the migrated /v1-bearing
// SourceBaseURL when present, the raw URL otherwise ("" when the row
// carries none).
func (e ProviderRegistryEntry) BaseURL() string {
if e.SourceBaseURL != "" {
return e.SourceBaseURL
}
return e.URL
}
// DefaultProviderRegistryPath resolves the registry file by the
// migration's own law (the TUI's registry.go mirror): RUNE_REGISTRY_
// CONFIG overrides, the user config dir's rune/providers.json is the
@@ -0,0 +1,161 @@
package runecore
// runtimeenv.go — the D57 runtime environment layer (owner ruling,
// Gitea #2590/#2591): "rune doesnt have a way of reaching the file
// system of a user... adaptive to whatever filesystem and os the user
// has... part of runtime awareness. this is why the tools are not
// functioning." The probe is AWARENESS, never gating: it reports the
// filesystem context the tools actually run in (OS family, home,
// separators, temp dir, case sensitivity, permissions) and resolves
// the deployed tool-executor binary. Nothing here restricts a tool —
// the owner build's dominion is absolute (the production sandbox is a
// later wave, explicitly out of scope here).
import (
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
)
// RuntimeEnv is one boot-time probe of the filesystem context.
type RuntimeEnv struct {
GOOS string `json:"goos"`
GOARCH string `json:"goarch"`
OSFamily string `json:"os_family"`
Home string `json:"home"`
PathSeparator string `json:"path_separator"`
ListSeparator string `json:"list_separator"`
TempDir string `json:"temp_dir"`
CaseSensitive bool `json:"case_sensitive"`
HomeWritable bool `json:"home_writable"`
TempWritable bool `json:"temp_writable"`
Cwd string `json:"cwd"`
ToolExecutor string `json:"tool_executor"`
ExecutorFound bool `json:"executor_found"`
}
var (
envOnce sync.Once
cachedEnv *RuntimeEnv
)
// ProbeRuntimeEnv runs the boot-time probe once per process (the
// filesystem context does not change shape under a running core) and
// returns the cached answer.
func ProbeRuntimeEnv() *RuntimeEnv {
envOnce.Do(func() { cachedEnv = probeRuntimeEnv() })
return cachedEnv
}
func probeRuntimeEnv() *RuntimeEnv {
env := &RuntimeEnv{
GOOS: runtime.GOOS,
GOARCH: runtime.GOARCH,
OSFamily: osFamily(runtime.GOOS),
PathSeparator: string(os.PathSeparator),
ListSeparator: string(os.PathListSeparator),
}
if h, err := os.UserHomeDir(); err == nil {
env.Home = h
env.HomeWritable = dirWritable(h)
}
env.TempDir = os.TempDir()
env.TempWritable = dirWritable(env.TempDir)
env.Cwd, _ = os.Getwd()
env.CaseSensitive = probeCaseSensitive(env.TempDir)
env.ToolExecutor, env.ExecutorFound = ResolveToolExecutorPath()
return env
}
func osFamily(goos string) string {
switch goos {
case "linux", "freebsd", "openbsd", "netbsd", "dragonfly", "solaris", "illumos":
return "unix"
case "darwin":
return "unix"
case "windows":
return "windows"
default:
return goos
}
}
// probeCaseSensitive answers whether the filesystem distinguishes
// case (APFS/Windows default insensitive, most Linux FS sensitive).
// Probe failure (read-only dir) answers false — the honest unknown,
// never an optimistic guess.
func probeCaseSensitive(base string) bool {
dir, err := os.MkdirTemp(base, "rune-case-probe-")
if err != nil {
return false
}
defer os.RemoveAll(dir)
lower := filepath.Join(dir, "rune-case-probe")
if err := os.WriteFile(lower, []byte("x"), 0o644); err != nil {
return false
}
upper := filepath.Join(dir, "RUNE-CASE-PROBE")
_, err = os.Stat(upper)
return err != nil // exists only when the FS distinguishes case
}
func dirWritable(dir string) bool {
if dir == "" {
return false
}
f, err := os.CreateTemp(dir, "rune-write-probe-")
if err != nil {
return false
}
name := f.Name()
_ = f.Close()
_ = os.Remove(name)
return true
}
// ResolveToolExecutorPath resolves the deployed tool-executor binary
// (D57 gap a: the executor must be DEPLOYED to
// ~/.local/share/rune/bin alongside rune-core; anything shelling to it
// resolves the location through this one door). Order: the
// RUNE_TOOL_EXECUTOR override, then the deployed location. found is
// false when the named path does not exist — the honest answer, never
// a guess.
func ResolveToolExecutorPath() (path string, found bool) {
if p := strings.TrimSpace(os.Getenv("RUNE_TOOL_EXECUTOR")); p != "" {
if _, err := os.Stat(p); err == nil {
return p, true
}
return p, false
}
home, err := os.UserHomeDir()
if err != nil {
return "", false
}
p := filepath.Join(home, ".local", "share", "rune", "bin", "tool-executor")
if _, err := os.Stat(p); err != nil {
return p, false
}
return p, true
}
// DeployedBinDir is the directory the deployment wave installs the Go
// binaries into (ensure_core.py's GO_CORE directory — one bin dir,
// all faces).
func DeployedBinDir() string {
home, err := os.UserHomeDir()
if err != nil {
return ""
}
return filepath.Join(home, ".local", "share", "rune", "bin")
}
// ExecutorUnavailableError is the typed error callers surface when the
// deployed executor is missing — actionable, never a silent fallback.
type ExecutorUnavailableError struct{ Path string }
func (e *ExecutorUnavailableError) Error() string {
return fmt.Sprintf("tool-executor not deployed at %s (run scripts/ensure_core.py to deploy)", e.Path)
}
@@ -0,0 +1,94 @@
package runecore
import (
"os"
"path/filepath"
"strings"
"testing"
)
// D57 red-first: the runtime environment layer must report the real
// filesystem context — OS family, home, separators, temp dir, case
// sensitivity, writability, and the deployed executor resolution.
func TestProbeRuntimeEnvReportsFSContext(t *testing.T) {
env := ProbeRuntimeEnv()
if env.GOOS == "" || env.OSFamily == "" {
t.Fatalf("probe missing os identity: %+v", env)
}
home, err := os.UserHomeDir()
if err != nil {
t.Skip("no home dir")
}
if env.Home != home {
t.Fatalf("home mismatch: got %q want %q", env.Home, home)
}
if env.PathSeparator != string(os.PathSeparator) {
t.Fatalf("path separator mismatch: %q", env.PathSeparator)
}
if env.TempDir != os.TempDir() {
t.Fatalf("temp dir mismatch: %q", env.TempDir)
}
if !env.TempWritable {
t.Fatalf("temp dir reported unwritable but it is real")
}
// Probe twice: the cached answer is stable (boot-time probe).
if ProbeRuntimeEnv() != env {
t.Fatalf("probe not cached")
}
}
// D57 gap (b): expandHome is generalized past the POSIX assumption —
// both separators resolve through the native path semantics.
func TestExpandHomeBothSeparators(t *testing.T) {
home, err := os.UserHomeDir()
if err != nil {
t.Skip("no home dir")
}
want := filepath.Join(home, "Desktop", "notes.txt")
if got := expandHome("~/Desktop/notes.txt"); got != want {
t.Fatalf("~/ path: got %q want %q", got, want)
}
if got := expandHome(`~\Desktop\notes.txt`); got != want {
t.Fatalf(`~\ path: got %q want %q`, got, want)
}
if got := expandHome("~"); got != home {
t.Fatalf("bare ~: got %q want %q", got, home)
}
if got := expandHome("/abs/path"); got != "/abs/path" {
t.Fatalf("absolute path must pass through: %q", got)
}
}
// D57 gap (a): the deployed executor location resolves through ONE
// door; the RUNE_TOOL_EXECUTOR override wins and existence is honest.
func TestResolveToolExecutorPath(t *testing.T) {
t.Setenv("RUNE_TOOL_EXECUTOR", "")
path, _ := ResolveToolExecutorPath()
home, err := os.UserHomeDir()
if err != nil {
t.Skip("no home dir")
}
if !strings.HasSuffix(path, filepath.Join(".local", "share", "rune", "bin", "tool-executor")) {
t.Fatalf("deployed location wrong: %q", path)
}
if !strings.HasPrefix(path, home) {
t.Fatalf("deployed path not under home: %q", path)
}
bin := t.TempDir()
p := filepath.Join(bin, "tool-executor")
if err := os.WriteFile(p, []byte("#!/bin/sh\n"), 0o755); err != nil {
t.Fatal(err)
}
t.Setenv("RUNE_TOOL_EXECUTOR", p)
got, found := ResolveToolExecutorPath()
if !found || got != p {
t.Fatalf("override not honored: %q found=%v", got, found)
}
t.Setenv("RUNE_TOOL_EXECUTOR", filepath.Join(bin, "missing"))
_, found = ResolveToolExecutorPath()
if found {
t.Fatalf("missing executor must report found=false (honest, never a guess)")
}
}
+110 -27
View File
@@ -120,9 +120,25 @@ func (s *Server) handleStatus(w http.ResponseWriter, r *http.Request) {
// (used = tokens sent, limit = the model's budget). Absent
// until the first turn — the bar never guesses.
"context": map[string]int{"used": ctxUsed, "limit": ctxLimit},
// D56 dispatch governor: the honest live state — measured tok/s
// (0 = nothing measured yet; the bar renders '—', never a
// guess), lanes in use against the learned cap, and the
// per-provider health rows (#2581).
"governor": s.engine.Governor().Snapshot(),
// D57 runtime environment layer: the boot-time filesystem
// probe (awareness, never gating) — Rune KNOWS her filesystem
// context and it can reach the model's system context.
"runtime_env": ProbeRuntimeEnv(),
// D57 model-authority report: the suite-on-wire facts and the
// refusal-correction accounting this session.
"model_authority": s.engine.AuthoritySnapshot(),
})
}
// Engine exposes the served engine (main.go wires the governor's
// health poll through it).
func (s *Server) Engine() *Engine { return s.engine }
// handleHealth: the core's own liveness + identity.
func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
var model string
@@ -217,6 +233,11 @@ func (s *Server) handleChatCompletions(w http.ResponseWriter, r *http.Request) {
channel = "task" // the operator default; the core never invents a channel
}
if req.Stream {
s.streamCompletion(w, r, req.Model, channel, userText)
return
}
res, err := s.engine.Turn(r.Context(), TurnRequest{
Channel: channel, UserText: userText, Model: req.Model,
})
@@ -255,10 +276,6 @@ func (s *Server) handleChatCompletions(w http.ResponseWriter, r *http.Request) {
}
id := newCompletionID()
created := time.Now().UTC().Unix()
if req.Stream {
s.writeChunks(w, id, created, res.Model, assistantText, runeBlk)
return
}
writeJSON(w, http.StatusOK, map[string]any{
"id": id,
"object": "chat.completion",
@@ -276,39 +293,105 @@ func (s *Server) handleChatCompletions(w http.ResponseWriter, r *http.Request) {
})
}
// writeChunks answers stream=true the Python core's framing: a content
// chunk, then the terminal chunk carrying finish_reason and the rune
// block (the front door flushes each line).
func (s *Server) writeChunks(w http.ResponseWriter, id string, created int64, model, content string, runeBlk map[string]any) {
w.Header().Set("Content-Type", "application/x-ndjson")
w.WriteHeader(http.StatusOK)
contentChunk := map[string]any{
"id": id, "object": "chat.completion.chunk", "created": created, "model": model,
"choices": []any{map[string]any{
"index": 0,
"delta": map[string]any{"role": "assistant", "content": content},
"finish_reason": nil,
}},
// streamCompletion answers stream=true the Python core's framing with
// the model's bytes relayed AS THEY ARRIVE (Gitea #2556 — the turn
// never sits inside one blocking call server-side): a content chunk
// per streamed text delta, then the terminal chunk carrying
// finish_reason and the rune block. An error before the first byte
// answers the honest error envelope; an error mid-stream answers an
// error terminal chunk (success=false — the stream shape stays valid).
func (s *Server) streamCompletion(w http.ResponseWriter, r *http.Request, model, channel, userText string) {
id := newCompletionID()
created := time.Now().UTC().Unix()
header := false
deltas := false
writeHeader := func() {
if !header {
w.Header().Set("Content-Type", "application/x-ndjson")
w.WriteHeader(http.StatusOK)
header = true
}
}
terminal := map[string]any{
"id": id, "object": "chat.completion.chunk", "created": created, "model": model,
"choices": []any{map[string]any{
"index": 0, "delta": map[string]any{}, "finish_reason": "stop",
}},
"rune": runeBlk,
}
for _, chunk := range []map[string]any{contentChunk, terminal} {
writeChunk := func(chunk map[string]any) bool {
line, err := json.Marshal(chunk)
if err != nil {
return
return false
}
if _, err := w.Write(append(line, '\n')); err != nil {
return
return false
}
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
return true
}
chunkFor := func(delta string) map[string]any {
return map[string]any{
"id": id, "object": "chat.completion.chunk", "created": created, "model": model,
"choices": []any{map[string]any{
"index": 0,
"delta": map[string]any{"role": "assistant", "content": delta},
"finish_reason": nil,
}},
}
}
sink := func(d StreamDelta) {
if d.Kind != "text" || d.Text == "" {
return
}
deltas = true
writeHeader()
writeChunk(chunkFor(d.Text))
}
res, err := s.engine.TurnStream(r.Context(), TurnRequest{
Channel: channel, UserText: userText, Model: model,
}, sink)
if res.ContextLimit > 0 {
s.setContext(res.ContextUsed, res.ContextLimit)
}
runeBlk := map[string]any{
"success": err == nil,
"user_text": res.AssistantText,
"model": res.Model,
"error": "",
"channel": channel,
"thinking": res.Thinking,
"messages": map[string]string{"user": res.UserMsgID, "assistant": res.AssistantMsgID},
"core": "go",
}
if err != nil {
runeBlk["error"] = clipStr(err.Error(), 300)
}
if err != nil && !header {
var pe *ProviderError
switch {
case errors.Is(err, ErrChannelBusy):
errorEnvelope(w, http.StatusConflict, "channel_busy", err.Error())
case errors.Is(err, ErrNoProvider):
errorEnvelope(w, http.StatusServiceUnavailable, "no_provider", err.Error())
case errors.Is(err, ErrProviderUnrouted):
errorEnvelope(w, http.StatusServiceUnavailable, "provider_unrouted", clipStr(err.Error(), 300))
case errors.Is(err, ErrProviderKeyMissing):
errorEnvelope(w, http.StatusServiceUnavailable, "provider_key_missing", clipStr(err.Error(), 300))
case errors.As(err, &pe):
errorEnvelope(w, http.StatusBadGateway, "provider_error", clipStr(err.Error(), 300))
default:
errorEnvelope(w, http.StatusInternalServerError, "internal_error", clipStr(err.Error(), 200))
}
return
}
writeHeader()
// the fallback law: a client that couldn't stream (or a non-streaming
// provider leg) still answers the full assembled content in ONE
// chunk — the classic framing, nothing dropped.
if !deltas {
writeChunk(chunkFor(res.AssistantText))
}
terminal := chunkFor("")
terminal["choices"].([]any)[0].(map[string]any)["delta"] = map[string]any{}
terminal["choices"].([]any)[0].(map[string]any)["finish_reason"] = "stop"
terminal["rune"] = runeBlk
writeChunk(terminal)
}
// setContext records the last turn's context accounting (D44 readout).
@@ -0,0 +1,162 @@
package runecore
// stream.go — the streaming root-cause wave (Gitea #2556, owner
// verdict: the flat whole-turn budgets patched the symptom; the root
// cause is ONE long-blocking HTTP call per model turn). A streamed
// turn never hits a deadline because bytes arrive continuously: the
// deadline collapses to an INTER-CHUNK IDLE TIMEOUT (no bytes for X
// seconds = genuinely dead) plus a generous overall backstop. The
// 330s/480s/6x480s budget constants remain only for the non-streaming
// fallback paths (clients that do not stream, the cap-out
// withdrawn-tools call, and legacy callers).
import (
"bufio"
"context"
"errors"
"fmt"
"os"
"strconv"
"strings"
"time"
)
// DefaultChunkIdleTimeout is the inter-chunk idle budget on every
// streamed model call: no bytes for this long = the model is
// genuinely dead (the old deadline's honest replacement).
const DefaultChunkIdleTimeout = 120 * time.Second
// StreamTurnBackstop is the overall cap on one streamed turn — sized
// generously (30 minutes) so only a runaway chain trips it; the idle
// timeout is the real guardian.
const StreamTurnBackstop = 30 * time.Minute
// ChunkIdleTimeoutEnv is the env knob that resizes the inter-chunk
// idle budget (seconds; an unparsable or non-positive value keeps the
// default — the knob never disables the guard).
const ChunkIdleTimeoutEnv = "RUNE_STREAM_IDLE_TIMEOUT"
// ErrStreamIdle is the typed dead-model refusal: the stream produced
// no bytes for the whole idle budget.
var ErrStreamIdle = errors.New("stream idle: no model bytes within the inter-chunk idle budget")
// chunkIdleTimeout resolves the inter-chunk idle budget: the env knob
// wins when it parses to a positive number of seconds; the default
// otherwise.
func chunkIdleTimeout() time.Duration {
if v := strings.TrimSpace(os.Getenv(ChunkIdleTimeoutEnv)); v != "" {
if n, err := strconv.Atoi(v); err == nil && n > 0 {
return time.Duration(n) * time.Second
}
}
return DefaultChunkIdleTimeout
}
// idleWatch enforces the inter-chunk idle timeout on one streamed
// call. The client calls reset() after EVERY chunk read; the watch
// cancels the derived ctx when the budget lapses. The error surfaces
// through the ctx as ErrStreamIdle (not a bare context.Canceled), so
// the honest dead-model reason reaches the caller.
type idleWatch struct {
ctx context.Context
timer *time.Timer
fired bool
cancelAll context.CancelFunc
}
// watchIdle derives an idle-enforced ctx from the caller's — the same
// cancel context carries the backstop deadline; the idle fire is
// distinguished by the fired flag (the honest dead-model error).
func watchIdle(parent context.Context, idle, backstop time.Duration) *idleWatch {
ctx, cancelAll := context.WithTimeout(parent, backstop)
w := &idleWatch{ctx: ctx, cancelAll: cancelAll}
w.timer = time.AfterFunc(idle, func() {
w.fired = true
cancelAll()
})
return w
}
// reset arms the idle budget afresh — called after every chunk.
func (w *idleWatch) reset(idle time.Duration) {
w.timer.Reset(idle)
}
// stop disarms the watch (completion or error) and releases the
// context.
func (w *idleWatch) stop() {
w.timer.Stop()
w.cancelAll()
}
// err maps a stream-read failure onto the honest typed error: the
// idle fire wins (dead model), the backstop's deadline reads as
// itself, everything else passes through.
func (w *idleWatch) err(readErr error, idle time.Duration) error {
if w.fired {
return fmt.Errorf("%w (%s)", ErrStreamIdle, idle)
}
if readErr != nil {
if errors.Is(readErr, context.DeadlineExceeded) {
return fmt.Errorf("stream turn backstop exceeded (%s)", StreamTurnBackstop)
}
return readErr
}
return w.ctx.Err()
}
// StreamDelta is one incremental model byte: visible answer text
// ("text") or surfaced reasoning ("thinking").
type StreamDelta struct {
Kind string // "text" | "thinking"
Text string
}
// StreamSink consumes deltas as they arrive (never after the call
// returns; nil-safe call sites).
type StreamSink func(StreamDelta)
// StreamChatClient is the streaming leg of the model boundary: the
// same ChatWithTools semantics (the engine's bounded tool loop is
// unchanged) with the answer's bytes surfaced through sink as they
// arrive. The final ChatReply carries the assembled content, thinking
// and tool_calls exactly as the non-streaming leg's does. The client
// owns the idle enforcement (it reads the wire); every implementation
// derives its read ctx through watchIdle.
type StreamChatClient interface {
ToolChatClient
StreamChatWithTools(ctx context.Context, model string, msgs []ChatMessage, tools []ToolDef, sink StreamSink) (ChatReply, error)
}
// emit is the nil-safe sink call.
func emit(sink StreamSink, d StreamDelta) {
if sink != nil {
sink(d)
}
}
// scanSSE reads an SSE body line-wise: every "data:" line's payload
// goes to fn; "data: [DONE]" terminates (per the [OI] SSE framing).
// Comment lines (":" prefix) and empty lines are skipped. The caller's
// read failures pass through (the idle watch maps them).
func scanSSE(body *bufio.Reader, fn func(data string) error) error {
for {
line, err := body.ReadString('\n')
trimmed := strings.TrimRight(line, "\r\n")
if trimmed != "" && !strings.HasPrefix(trimmed, ":") {
if payload, ok := strings.CutPrefix(trimmed, "data: "); ok {
if strings.TrimSpace(payload) == "[DONE]" {
return nil
}
if err := fn(payload); err != nil {
return err
}
} else if payload, ok := strings.CutPrefix(trimmed, "data:"); ok && strings.TrimSpace(payload) == "[DONE]" {
return nil
}
}
if err != nil {
return err
}
}
}
@@ -0,0 +1,314 @@
package runecore
// stream_test.go — the streaming root-cause wave's frozen proofs
// (Gitea #2556, red-first): the Ollama NDJSON stream parses
// incrementally and answers the assembled reply; the INTER-CHUNK IDLE
// TIMEOUT replaces the flat deadline (a slow model that keeps sending
// bytes finishes no matter how long it takes; a dead model trips the
// idle error); the engine's bounded tool loop runs over streamed
// replies; the server relays the streamed turn chunk-by-chunk.
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
)
// fakeStreamClient is a StreamChatClient for the engine loop proofs.
type fakeStreamClient struct {
calls int
scripts []struct {
reply ChatReply
deltas []StreamDelta
}
thinks bool
sunk []StreamDelta
}
func (f *fakeStreamClient) add(reply ChatReply, deltas ...StreamDelta) {
f.scripts = append(f.scripts, struct {
reply ChatReply
deltas []StreamDelta
}{reply, deltas})
}
func (f *fakeStreamClient) SupportsTools(context.Context, string) bool { return f.thinks }
func (f *fakeStreamClient) ChatWithTools(ctx context.Context, model string, msgs []ChatMessage, tools []ToolDef) (ChatReply, error) {
return f.Chat(ctx, model, msgs)
}
func (f *fakeStreamClient) Chat(ctx context.Context, model string, msgs []ChatMessage) (ChatReply, error) {
f.calls++
return f.script().reply, nil
}
func (f *fakeStreamClient) script() (out struct {
reply ChatReply
deltas []StreamDelta
}) {
i := f.calls - 1
if i >= len(f.scripts) {
i = len(f.scripts) - 1
}
return f.scripts[i]
}
func (f *fakeStreamClient) StreamChatWithTools(ctx context.Context, model string, msgs []ChatMessage, tools []ToolDef, sink StreamSink) (ChatReply, error) {
f.calls++
s := f.script()
for _, d := range s.deltas {
f.sunk = append(f.sunk, d)
emit(sink, d)
}
return s.reply, nil
}
func (f *fakeStreamClient) Tags(context.Context) ([]ModelInfo, error) { return nil, nil }
// ollamaStreamServer is a hermetic /api/chat double: lines written on
// schedule (delay after each), the final done:true chunk closing.
func ollamaStreamServer(t *testing.T, lines []string, perLine time.Duration) *httptest.Server {
t.Helper()
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.HasSuffix(r.URL.Path, "/api/show") {
_, _ = w.Write([]byte(`{"capabilities":["tools","thinking"]}`))
return
}
var req ollamaChatRequest
_ = json.NewDecoder(r.Body).Decode(&req)
if !req.Stream {
t.Errorf("ollama stream double: request did not carry stream=true")
}
w.Header().Set("Content-Type", "application/x-ndjson")
for i, line := range lines {
_, _ = w.Write([]byte(line + "\n"))
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
if i < len(lines)-1 && perLine > 0 {
time.Sleep(perLine)
}
}
}))
}
func ollamaChunk(content string) string {
return `{"model":"q","message":{"role":"assistant","content":` + jsonString(content) + `},"done":false}`
}
// TestOllamaStreamAssemblesReply — the NDJSON chunks parse
// incrementally; every text byte surfaces through the sink in wire
// order; the final reply carries the assembled content + thinking and
// the token accounting from the done chunk.
func TestOllamaStreamAssemblesReply(t *testing.T) {
srv := ollamaStreamServer(t, []string{
ollamaChunk("hel"),
ollamaChunk("lo "),
`{"model":"q","message":{"role":"assistant","thinking":"pondering"},"done":false}`,
ollamaChunk("world"),
`{"model":"q","message":{"role":"assistant"},"done":true,"prompt_eval_count":42,"eval_count":7}`,
}, 0)
c := NewOllamaClient(srv.URL)
var got []StreamDelta
reply, err := c.StreamChatWithTools(context.Background(), "q", []ChatMessage{{Role: "user", Content: "hi"}}, nil, func(d StreamDelta) { got = append(got, d) })
if err != nil {
t.Fatal(err)
}
if reply.Content != "hello world" {
t.Fatalf("content = %q", reply.Content)
}
if len(reply.Thinking) != 1 || reply.Thinking[0] != "pondering" {
t.Fatalf("thinking = %v", reply.Thinking)
}
if reply.ContextUsed != 42 {
t.Fatalf("context used = %d", reply.ContextUsed)
}
var text strings.Builder
for _, d := range got {
if d.Kind != "text" {
continue
}
text.WriteString(d.Text)
}
if text.String() != "hello world" {
t.Fatalf("deltas = %q", text.String())
}
}
// TestOllamaStreamToolCalls — tool_calls riding the stream parse into
// the reply (the loop's wire shape, streamed).
func TestOllamaStreamToolCalls(t *testing.T) {
srv := ollamaStreamServer(t, []string{
`{"model":"q","message":{"role":"assistant","tool_calls":[{"function":{"name":"read_file","arguments":{"path":"x"}}}]},"done":false}`,
`{"model":"q","message":{"role":"assistant"},"done":true}`,
}, 0)
c := NewOllamaClient(srv.URL)
reply, err := c.StreamChatWithTools(context.Background(), "q", []ChatMessage{{Role: "user", Content: "hi"}}, nil, nil)
if err != nil {
t.Fatal(err)
}
if len(reply.ToolCalls) != 1 || reply.ToolCalls[0].Name != "read_file" {
t.Fatalf("tool calls = %+v", reply.ToolCalls)
}
if reply.ToolCalls[0].Args["path"] != "x" {
t.Fatalf("args = %+v", reply.ToolCalls[0].Args)
}
}
// TestOllamaStreamSlowModelFinishes — the root-cause proof: a model
// that emits a chunk every 200ms across a window LONGER than the old
// flat per-call budgets would ever allow (scaled 1:25 from the live
// 5s-for-10-minutes shape; the mechanism is identical — bytes keep
// arriving, the turn completes, no deadline fires).
func TestOllamaStreamSlowModelFinishes(t *testing.T) {
t.Setenv(ChunkIdleTimeoutEnv, "1") // 1s idle budget; chunks every 200ms
var lines []string
for i := 0; i < 12; i++ {
lines = append(lines, ollamaChunk(fmt.Sprintf("chunk%d ", i)))
}
lines = append(lines, `{"model":"q","message":{"role":"assistant"},"done":true}`)
srv := ollamaStreamServer(t, lines, 200*time.Millisecond)
defer srv.Close()
c := NewOllamaClient(srv.URL)
reply, err := c.StreamChatWithTools(context.Background(), "q", []ChatMessage{{Role: "user", Content: "hi"}}, nil, nil)
if err != nil {
t.Fatalf("slow streamed turn failed: %v", err)
}
if !strings.Contains(reply.Content, "chunk11") {
t.Fatalf("content = %q", reply.Content)
}
}
// TestOllamaStreamIdleTimeout — the dead model: bytes stop; the idle
// budget lapses; the typed ErrStreamIdle fires (never a hang).
func TestOllamaStreamIdleTimeout(t *testing.T) {
t.Setenv(ChunkIdleTimeoutEnv, "1")
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte(ollamaChunk("first") + "\n"))
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
<-r.Context().Done() // then silence forever
}))
defer srv.Close()
c := NewOllamaClient(srv.URL)
start := time.Now()
_, err := c.StreamChatWithTools(context.Background(), "q", []ChatMessage{{Role: "user", Content: "hi"}}, nil, nil)
if !errors.Is(err, ErrStreamIdle) {
t.Fatalf("err = %v, want ErrStreamIdle", err)
}
if time.Since(start) > 5*time.Second {
t.Fatalf("idle timeout took %v — not the 1s budget", time.Since(start))
}
}
// TestEngineStreamedToolLoop — the bounded tool loop runs over
// streamed replies: call 1 asks for a tool, call 2 answers in text;
// the loop's iteration discipline is unchanged and the deltas flowed.
func TestEngineStreamedToolLoop(t *testing.T) {
fc := &fakeStreamClient{thinks: true}
fc.add(ChatReply{Content: "", ToolCalls: []ToolCall{{Name: "list_directory", Args: map[string]any{"path": "."}}}},
StreamDelta{Kind: "text", Text: "thinking..."})
fc.add(ChatReply{Content: "done answering"},
StreamDelta{Kind: "text", Text: "done answering"})
e, _ := newTestEngine(t, fc)
var got []StreamDelta
res, err := e.TurnStream(context.Background(), TurnRequest{Channel: "chat", UserText: "list files"}, func(d StreamDelta) { got = append(got, d) })
if err != nil {
t.Fatal(err)
}
if res.AssistantText != "done answering" {
t.Fatalf("answer = %q", res.AssistantText)
}
if fc.calls != 2 {
t.Fatalf("model calls = %d, want the loop's 2", fc.calls)
}
if len(got) != 2 {
t.Fatalf("deltas = %d, want both legs streamed", len(got))
}
}
// TestServerStreamsChunksLive — stream=true relays the model's bytes
// AS THEY ARRIVE: the first chunk lands while the model is still
// emitting (never one blocking call server-side).
func TestServerStreamsChunksLive(t *testing.T) {
t.Setenv(ChunkIdleTimeoutEnv, "5")
srv := ollamaStreamServer(t, []string{
ollamaChunk("live "),
ollamaChunk("answer"),
`{"model":"q","message":{"role":"assistant"},"done":true}`,
}, 300*time.Millisecond)
defer srv.Close()
oc := NewOllamaClient(srv.URL)
e, _ := newTestEngine(t, oc)
core := httptest.NewServer(NewServer(e, oc, e.home).Handler())
defer core.Close()
raw, _ := json.Marshal(map[string]any{
"channel": "chat", "stream": true,
"messages": []map[string]any{{"role": "user", "content": "hi"}},
})
resp, err := http.Post(core.URL+"/v1/chat/completions", "application/json", strings.NewReader(string(raw)))
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
start := time.Now()
buf := make([]byte, 4096)
var got strings.Builder
firstAt := time.Time{}
for {
n, rerr := resp.Body.Read(buf)
if n > 0 {
got.Write(buf[:n])
if firstAt.IsZero() && strings.Contains(got.String(), "live") {
firstAt = time.Now()
}
}
if rerr != nil {
break
}
}
if firstAt.IsZero() {
t.Fatalf("no chunk arrived incrementally: %q", got.String())
}
if elapsed := firstAt.Sub(start); elapsed > 200*time.Millisecond {
t.Fatalf("first chunk at %v — the core buffered the stream", elapsed)
}
if !strings.Contains(got.String(), `"finish_reason":"stop"`) {
t.Fatalf("terminal chunk missing: %q", got.String())
}
if !strings.Contains(got.String(), `"success":true`) {
t.Fatalf("rune block missing: %q", got.String())
}
}
// TestServerStreamErrorBeforeFirstByte — a refusal (no provider
// record) on a streamed request answers the honest error envelope,
// never a half-open stream.
func TestServerStreamErrorBeforeFirstByte(t *testing.T) {
oc := NewOllamaClient("http://127.0.0.1:1") // dead local wire
e, _ := newTestEngine(t, oc)
core := httptest.NewServer(NewServer(e, oc, e.home).Handler())
defer core.Close()
raw, _ := json.Marshal(map[string]any{
"channel": "chat", "stream": true,
"messages": []map[string]any{{"role": "user", "content": "hi"}},
})
resp, err := http.Post(core.URL+"/v1/chat/completions", "application/json", strings.NewReader(string(raw)))
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if !strings.Contains(string(body), `"error"`) {
t.Fatalf("want the error envelope, got %q", string(body))
}
}
+9 -3
View File
@@ -274,11 +274,17 @@ func executeGoTool(name string, args map[string]any) map[string]any {
}
// expandHome resolves a leading ~ to the user's home (the model speaks
// user paths: "~/Desktop").
// user paths: "~/Desktop"). D57: generalized past the POSIX-only
// assumption — both separators are accepted ("~\Desktop" on Windows
// and "~/Desktop" everywhere), and inner separators are joined with
// the native os.PathSeparator so a model-spoken path adapts to
// whatever filesystem and OS the user has.
func expandHome(p string) string {
if p == "~" || strings.HasPrefix(p, "~/") {
if p == "~" || strings.HasPrefix(p, "~/") || strings.HasPrefix(p, `~\`) {
if h, err := os.UserHomeDir(); err == nil {
return filepath.Join(h, strings.TrimPrefix(strings.TrimPrefix(p, "~"), "/"))
rest := strings.TrimPrefix(strings.TrimPrefix(p, "~"), string(os.PathSeparator))
rest = strings.ReplaceAll(strings.TrimPrefix(rest, "/"), "\\", "/")
return filepath.Join(h, filepath.FromSlash(rest))
}
}
return p
+12
View File
@@ -115,6 +115,18 @@ func main() {
router := runecore.NewProviderRouter(runecore.SecretToolReader())
srv := runecore.NewServer(runecore.NewRoutedEngine(st, client, home, router), client, home)
// D56 connection health (#2581): poll the registry's providers'
// live state so the governor routes only to providers actually
// alive — once at boot, then on the health cadence.
gov := srv.Engine().Governor()
go func() {
registry := runecore.DefaultProviderRegistryPath()
for {
gov.PollRegistryHealth(registry, 5*time.Second)
time.Sleep(runecore.HealthPollInterval)
}
}()
addr := fmt.Sprintf("%s:%d", host, port)
httpSrv := &http.Server{Addr: addr, Handler: srv.Handler()}
+10 -8
View File
@@ -192,17 +192,19 @@ type frontDoor struct {
// lib.CompletionBudget so the two legs of the chain can never drift.
const CompletionBudget = proxyClientBudget
// newFrontDoor builds a front door over one core target with the
// production budget wiring: BOTH proxy clients carry the WHOLE-TURN
// budget (D47 defect 3 — a dispatched completion runs the core's tool
// loop, up to ToolLoopIterations model calls; the per-class deadline
// rides the ctx the TUI binds, the doors never cut beneath the
// longest class).
// newFrontDoor builds a front door over one core target. The dispatch
// route's completion client carries NO whole-call Timeout (the
// streaming root-cause wave, Gitea #2556): a streamed turn makes
// progress continuously and the CORE governs its deadlines — the
// inter-chunk idle timeout plus the 30-minute backstop — so a flat
// client cap here would only patch the symptom again. The generic
// proxy client keeps the WHOLE-TURN budget for the non-streaming
// fallback paths (the 6x480s constants' remaining tenant).
func newFrontDoor(core string) *frontDoor {
return &frontDoor{
core: core,
client: &http.Client{Timeout: TurnBudget},
completionClient: &http.Client{Timeout: TurnBudget},
completionClient: &http.Client{},
}
}
@@ -237,7 +239,7 @@ func (f *frontDoor) proxyOn(out *http.Client) http.HandlerFunc {
}
body = bytes.NewReader(raw)
}
req, err := http.NewRequest(r.Method, f.core+r.URL.Path, body)
req, err := http.NewRequestWithContext(r.Context(), r.Method, f.core+r.URL.Path, body)
if err != nil {
errorEnvelope(w, http.StatusBadRequest, "bad_request", err.Error())
return
@@ -0,0 +1,76 @@
package entry
// streaming_entry_test.go — the streaming root-cause wave's entry-leg
// proof (Gitea #2556): the front door relays a streamed /v1/chat/
// completions response CHUNK BY CHUNK — a chunk the core emits reaches
// the caller before the core emits the next one (no buffering at the
// proxy), so the turn never sits inside one blocking call on the
// entry leg either.
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
)
// TestEntryProxyRelaysStreamChunks — a fake core emits two NDJSON
// chunk lines 300ms apart; the relayed first chunk must arrive while
// the second is still in flight.
func TestEntryProxyRelaysStreamChunks(t *testing.T) {
lines := []string{
`{"id":"c1","object":"chat.completion.chunk","choices":[{"delta":{"content":"hel"}}]}` + "\n",
`{"id":"c1","object":"chat.completion.chunk","choices":[{"delta":{},"finish_reason":"stop"}]}` + "\n",
}
core := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/x-ndjson")
w.WriteHeader(http.StatusOK)
for i, line := range lines {
_, _ = w.Write([]byte(line))
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
if i == 0 {
time.Sleep(300 * time.Millisecond)
}
}
}))
defer core.Close()
f := newFrontDoor(core.URL)
door := httptest.NewServer(f.mux())
defer door.Close()
start := time.Now()
resp, err := http.Post(door.URL+"/v1/chat/completions", "application/json",
strings.NewReader(`{"model":"m","messages":[{"role":"user","content":"hi"}],"stream":true}`))
if err != nil {
t.Fatalf("post: %v", err)
}
defer resp.Body.Close()
buf := make([]byte, 4096)
firstAt := time.Time{}
var got strings.Builder
for {
n, err := resp.Body.Read(buf)
if n > 0 {
got.Write(buf[:n])
if firstAt.IsZero() && strings.Contains(got.String(), "hel") {
firstAt = time.Now()
}
}
if err != nil {
break
}
}
if firstAt.IsZero() {
t.Fatalf("first chunk never arrived incrementally: %q", got.String())
}
if elapsed := firstAt.Sub(start); elapsed >= 250*time.Millisecond {
t.Fatalf("first chunk arrived at %v — the proxy buffered the stream", elapsed)
}
if !strings.Contains(got.String(), "finish_reason") {
t.Fatalf("terminal chunk lost: %q", got.String())
}
}
@@ -1,21 +1,22 @@
package entry
// turnbudget_entry_test.go — the D47 defect 3 law on the ENTRY legs:
// every door that proxies a dispatched completion (the serve proxy
// client + the continuous-runtime door's completion client) must carry
// the WHOLE-TURN budget — the sum of the tool loop's model calls, the
// local-class ceiling — never the old single-call 480s. Red-first.
// turnbudget_entry_test.go — the budget law on the ENTRY legs after
// the streaming root-cause wave (Gitea #2556): the dispatch route's
// completion client carries NO whole-call Timeout (a streamed turn is
// governed by the core's inter-chunk idle timeout + backstop — a flat
// client cap would patch the symptom again); the generic proxy client
// keeps the whole-turn budget for the non-streaming fallback paths.
import (
"testing"
"time"
)
// TestEntryCompletionClientsCarryWholeTurnBudget — both entry doors'
// completion clients ride >= the local whole-turn budget (6 × the
// per-call 480s; the ctx the TUI binds is the per-class deadline, the
// client timeout must never cut beneath the longest class).
func TestEntryCompletionClientsCarryWholeTurnBudget(t *testing.T) {
// TestEntryDispatchClientIsUngatedAndProxyKeepsFallback — the two
// clients split by deadline governance: the completion (dispatch /
// streamed) client must NOT cap the whole call, the generic proxy
// client must carry >= the whole-turn fallback budget.
func TestEntryDispatchClientIsUngatedAndProxyKeepsFallback(t *testing.T) {
if TurnBudget < ToolLoopIterations*PerCallBudget {
t.Fatalf("TurnBudget = %v, want >= %v (the whole-turn sum of %d model calls)",
TurnBudget, ToolLoopIterations*PerCallBudget, ToolLoopIterations)
@@ -25,9 +26,9 @@ func TestEntryCompletionClientsCarryWholeTurnBudget(t *testing.T) {
}
f := newFrontDoor("http://127.0.0.1:8376")
if f.client == nil || f.client.Timeout < TurnBudget {
t.Fatalf("proxy client budget = %+v, want the whole-turn %v", f.client, TurnBudget)
t.Fatalf("proxy client budget = %+v, want the whole-turn %v (the non-streaming fallback)", f.client, TurnBudget)
}
if f.completionClient == nil || f.completionClient.Timeout < TurnBudget {
t.Fatalf("completion client budget = %+v, want the whole-turn %v", f.completionClient, TurnBudget)
if f.completionClient == nil || f.completionClient.Timeout != 0 {
t.Fatalf("completion client timeout = %v, want 0 (the streamed turn is governed by the core's idle watch, never a flat client cap)", f.completionClient.Timeout)
}
}
Loaded 100 of 217 files, more files were not shown because too many files have changed in this diff. Show more