Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a9ca48105 | ||
|
|
5fe29ddb70 | ||
|
|
454511be53 | ||
|
|
4a1e994d43 | ||
|
|
a46a0bb02a |
No files matched your search
@@ -1,9 +1,9 @@
|
||||
# Rune
|
||||
|
||||
**Current release: v0.347.0**
|
||||
**Current release: v0.349.0**
|
||||
|
||||
<!-- highlights:start -->
|
||||
## Highlights — from the latest release (v0.347.0)
|
||||
## Highlights — from the latest release (v0.349.0)
|
||||
|
||||
- Plain-English jargon dictionary covers the v0.329 vocabulary, plus catch-all sentence simplification pass.
|
||||
|
||||
|
||||
+197
-97
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "rune"
|
||||
version = "0.347.0"
|
||||
version = "0.349.0"
|
||||
description = "Rune — a stateful coding assistant with persistent memory, orchestration, and TUI"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ This is the main entry point for the Rune program.
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
__version__ = "0.347.0"
|
||||
__version__ = "0.349.0"
|
||||
|
||||
# One build identity (handoff 2026-09-10 §4): the semantic version above is
|
||||
# the repo-level truth (auto-bumped at merge); the DEPLOYED revision (exact
|
||||
|
||||
@@ -233,7 +233,7 @@ def main() -> int:
|
||||
)
|
||||
check(
|
||||
"Rune minor version advertises provisioning and restore verification",
|
||||
'__version__ = "0.347.0"' in rune_version,
|
||||
'__version__ = "0.349.0"' in rune_version,
|
||||
failures,
|
||||
)
|
||||
# #169 policy update: auto-bump on merge is sanctioned (Nick directive:
|
||||
|
||||
+162
-6
@@ -8,12 +8,93 @@ variables or a config dict — nothing is hardcoded to a specific setup.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
from typing import Protocol, runtime_checkable
|
||||
|
||||
import httpx
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fail-loud Go delegation (REBASE-COMPLETION, #2587 wave): the retry/status
|
||||
# decision seam (_request) and the adapter-selection seam
|
||||
# (create_forge_client) resolve through the Go engine
|
||||
# (src/go/forge_client, `eval <ops.json>` surface). The Python bodies below
|
||||
# are kept as oracle / escape hatch (RUNE_FORGE_CLIENT_ENGINE=python);
|
||||
# RUNE_FORGE_CLIENT_BIN overrides the binary. Missing binary or engine
|
||||
# error raises RuntimeError — no silent fallback (house shape:
|
||||
# model_routing.reconcile_tools_support). The raise_for_status EXCEPTION
|
||||
# contract stays on the Python side: the Go engine reports the 4xx as
|
||||
# {status_error} and the facade re-raises httpx.HTTPStatusError with an
|
||||
# httpx.Response, exactly like the oracle body.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _go_engine_enabled() -> bool:
|
||||
return os.environ.get("RUNE_FORGE_CLIENT_ENGINE", "go") != "python"
|
||||
|
||||
|
||||
def _go_engine_bin() -> str:
|
||||
override = os.environ.get("RUNE_FORGE_CLIENT_BIN")
|
||||
if override:
|
||||
return override
|
||||
root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
return os.path.join(root, "src", "go", "forge_client", "forge-client")
|
||||
|
||||
|
||||
def _go_eval(ops: list) -> list:
|
||||
bin_path = _go_engine_bin()
|
||||
if not os.path.exists(bin_path):
|
||||
raise RuntimeError(
|
||||
f"forge-client go engine binary missing: {bin_path} "
|
||||
"(set RUNE_FORGE_CLIENT_ENGINE=python for the legacy path)"
|
||||
)
|
||||
fd, ops_path = tempfile.mkstemp(suffix=".json", prefix="fc-ops-")
|
||||
try:
|
||||
with os.fdopen(fd, "w", encoding="utf-8") as fh:
|
||||
json.dump(ops, fh)
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
[bin_path, "eval", ops_path],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=120,
|
||||
)
|
||||
except (OSError, subprocess.TimeoutExpired) as exc:
|
||||
raise RuntimeError(f"forge-client go engine failed: {exc}") from exc
|
||||
if proc.returncode != 0:
|
||||
raise RuntimeError(f"forge-client go engine exit {proc.returncode}: {proc.stderr.strip()}")
|
||||
return json.loads(proc.stdout)
|
||||
finally:
|
||||
try:
|
||||
os.unlink(ops_path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
class _GoResponse:
|
||||
"""Minimal httpx.Response stand-in for delegated requests: the
|
||||
adapter call sites consume .json() and .status_code only."""
|
||||
|
||||
def __init__(self, status_code: int, body: str):
|
||||
self.status_code = status_code
|
||||
self._body = body
|
||||
|
||||
def json(self):
|
||||
return json.loads(self._body)
|
||||
|
||||
|
||||
def _raise_for_status(status_code: int) -> None:
|
||||
response = httpx.Response(status_code)
|
||||
error = httpx.HTTPStatusError(
|
||||
f"error {status_code}",
|
||||
request=httpx.Request("GET", "https://forge"),
|
||||
response=response,
|
||||
)
|
||||
raise error
|
||||
|
||||
|
||||
def _request(
|
||||
method: str,
|
||||
@@ -25,6 +106,59 @@ def _request(
|
||||
retries: int = 2,
|
||||
backoff: float = 0.5,
|
||||
sleep=None,
|
||||
):
|
||||
"""Fail-loud Go delegation for the retry/status decision seam. The
|
||||
injected ``sleep`` is a Python-test-only seam: when a test passes a
|
||||
recording fake, the oracle body runs (documented seam, not a silent
|
||||
fallback of the engine decision — the delegation governs every live
|
||||
call, where sleep is default)."""
|
||||
if _go_engine_enabled() and sleep is None:
|
||||
ops = [
|
||||
{
|
||||
"op": "request",
|
||||
"method": method,
|
||||
"url": url,
|
||||
"headers": headers or {},
|
||||
"json": json,
|
||||
"timeout": timeout,
|
||||
"retries": retries,
|
||||
"backoff": backoff,
|
||||
}
|
||||
]
|
||||
row = _go_eval(ops)[0]
|
||||
if "status_error" in row:
|
||||
_raise_for_status(int(row["status_error"]))
|
||||
if "error_kind" in row:
|
||||
kind = row["error_kind"]
|
||||
exc = {
|
||||
"httpx.ConnectError": httpx.ConnectError,
|
||||
"httpx.ConnectTimeout": httpx.ConnectTimeout,
|
||||
"httpx.ReadTimeout": httpx.ReadTimeout,
|
||||
}.get(kind, httpx.TransportError)
|
||||
raise exc(str(row.get("error_msg") or kind))
|
||||
return _GoResponse(int(row["status_code"]), str(row["body"]))
|
||||
return _request_python(
|
||||
method,
|
||||
url,
|
||||
headers=headers,
|
||||
json=json,
|
||||
timeout=timeout,
|
||||
retries=retries,
|
||||
backoff=backoff,
|
||||
sleep=sleep,
|
||||
)
|
||||
|
||||
|
||||
def _request_python(
|
||||
method: str,
|
||||
url: str,
|
||||
*,
|
||||
headers: dict | None = None,
|
||||
json: dict | None = None,
|
||||
timeout: float = 30,
|
||||
retries: int = 2,
|
||||
backoff: float = 0.5,
|
||||
sleep=None,
|
||||
):
|
||||
"""Resilient single-request path: retry where failure is NOISE, never
|
||||
where failure is the SIGNAL.
|
||||
@@ -193,11 +327,33 @@ def create_forge_client(config: dict | None = None) -> ForgeClient:
|
||||
Falls back to FORGE_URL/FORGE_TOKEN/FORGE_OWNER/FORGE_REPO env vars.
|
||||
Unconfigured → NullAdapter (graceful dormant).
|
||||
"""
|
||||
cfg = config or {}
|
||||
url = cfg.get("forge.url") or os.environ.get("FORGE_URL", "")
|
||||
token = cfg.get("forge.token") or os.environ.get("FORGE_TOKEN", "")
|
||||
cfg = dict(config or {})
|
||||
if not cfg.get("forge.url"):
|
||||
cfg["forge.url"] = os.environ.get("FORGE_URL", "")
|
||||
if not cfg.get("forge.token"):
|
||||
cfg["forge.token"] = os.environ.get("FORGE_TOKEN", "")
|
||||
if not cfg.get("forge.owner"):
|
||||
cfg["forge.owner"] = os.environ.get("FORGE_OWNER", "owner")
|
||||
if not cfg.get("forge.repo"):
|
||||
cfg["forge.repo"] = os.environ.get("FORGE_REPO", "repo")
|
||||
if _go_engine_enabled():
|
||||
row = _go_eval([{"op": "select", "config": cfg}])[0]
|
||||
if row.get("adapter") == "NullAdapter":
|
||||
return NullAdapter()
|
||||
base_url = row["base_url"]
|
||||
# Split the resolved base_url back into (url, owner, repo) for the
|
||||
# adapter constructor — the DECISION (kind + resolution) was the
|
||||
# engine's; the constructor call is glue.
|
||||
head, _, tail = base_url.rpartition("/api/v1/repos/")
|
||||
owner, _, repo = tail.partition("/")
|
||||
return GiteaForgejoAdapter(head, row["token"], owner=owner, repo=repo)
|
||||
url = cfg.get("forge.url", "")
|
||||
token = cfg.get("forge.token", "")
|
||||
if not url or not token:
|
||||
return NullAdapter()
|
||||
owner = cfg.get("forge.owner") or os.environ.get("FORGE_OWNER", "owner")
|
||||
repo = cfg.get("forge.repo") or os.environ.get("FORGE_REPO", "repo")
|
||||
return GiteaForgejoAdapter(url, token, owner=owner, repo=repo)
|
||||
return GiteaForgejoAdapter(
|
||||
url,
|
||||
token,
|
||||
owner=cfg.get("forge.owner", "owner"),
|
||||
repo=cfg.get("forge.repo", "repo"),
|
||||
)
|
||||
@@ -11,12 +11,68 @@ Ollama-SHAPED responses, so LocalToolRuntime's loop is unchanged. Selection
|
||||
is deterministic (routing_mode == 'cloud') — no router, ladder, or escalation
|
||||
logic is touched.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
import urllib.request
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Fail-loud Go delegation (REBASE-COMPLETION, #2587 wave): the module's
|
||||
# seams resolve through the Go engine (src/go/openai_compat_transport —
|
||||
# 74 funcs, `eval <ops.json>` surface). The Python bodies below are kept
|
||||
# as oracle / escape hatch (RUNE_OPENAI_COMPAT_TRANSPORT_ENGINE=python);
|
||||
# RUNE_OPENAI_COMPAT_TRANSPORT_BIN overrides the binary. Missing binary
|
||||
# or engine error raises RuntimeError — no silent Python fallback (the
|
||||
# house shape: model_routing.reconcile_tools_support).
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _go_engine_enabled() -> bool:
|
||||
return os.environ.get("RUNE_OPENAI_COMPAT_TRANSPORT_ENGINE", "go") != "python"
|
||||
|
||||
|
||||
def _go_engine_bin() -> str:
|
||||
override = os.environ.get("RUNE_OPENAI_COMPAT_TRANSPORT_BIN")
|
||||
if override:
|
||||
return override
|
||||
root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
return os.path.join(root, "src", "go", "openai_compat_transport", "openai-compat-transport")
|
||||
|
||||
|
||||
def _go_eval(ops: list) -> list:
|
||||
bin_path = _go_engine_bin()
|
||||
if not os.path.exists(bin_path):
|
||||
raise RuntimeError(
|
||||
f"openai-compat-transport go engine binary missing: {bin_path} "
|
||||
"(set RUNE_OPENAI_COMPAT_TRANSPORT_ENGINE=python for the legacy path)"
|
||||
)
|
||||
fd, ops_path = tempfile.mkstemp(suffix=".json", prefix="oct-ops-")
|
||||
try:
|
||||
with os.fdopen(fd, "w", encoding="utf-8") as fh:
|
||||
json.dump(ops, fh)
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
[bin_path, "eval", ops_path],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=300,
|
||||
)
|
||||
except (OSError, subprocess.TimeoutExpired) as exc:
|
||||
raise RuntimeError(f"openai-compat-transport go engine failed: {exc}") from exc
|
||||
if proc.returncode != 0:
|
||||
raise RuntimeError(f"openai-compat-transport go engine exit {proc.returncode}: {proc.stderr.strip()}")
|
||||
return json.loads(proc.stdout)
|
||||
finally:
|
||||
try:
|
||||
os.unlink(ops_path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
class OpenAICompatTransport:
|
||||
"""ChatTransport for OpenAI-compatible providers (Verboo Code, Xioami...)."""
|
||||
@@ -73,6 +129,14 @@ class OpenAICompatTransport:
|
||||
the provider treat the tool result as orphaned and the model re-issues
|
||||
the identical call forever (observed live: glm-5.3-flash looped the
|
||||
same edit_file twice, empty terminal text both turns)."""
|
||||
if _go_engine_enabled():
|
||||
out = _go_eval([{"op": "to_openai_messages", "messages": messages}])
|
||||
return out[0]
|
||||
return OpenAICompatTransport._to_openai_messages_python(messages)
|
||||
|
||||
@staticmethod
|
||||
def _to_openai_messages_python(messages: list[dict]) -> list[dict]:
|
||||
"""Oracle body (kept as escape/oracle; see module header)."""
|
||||
out: list[dict] = []
|
||||
i = 0
|
||||
while i < len(messages):
|
||||
@@ -82,24 +146,35 @@ class OpenAICompatTransport:
|
||||
if role == "assistant" and tool_calls:
|
||||
# Look ahead: pair each tool_call with the tool messages that
|
||||
# immediately follow, reusing their tool_call_ids.
|
||||
paired_ids = [messages[j].get("tool_call_id")
|
||||
for j in range(i + 1, len(messages))
|
||||
if messages[j].get("role") == "tool"][:len(tool_calls)]
|
||||
paired_ids = [
|
||||
messages[j].get("tool_call_id")
|
||||
for j in range(i + 1, len(messages))
|
||||
if messages[j].get("role") == "tool"
|
||||
][: len(tool_calls)]
|
||||
tc = []
|
||||
for k, c in enumerate(tool_calls):
|
||||
fn = c.get("function", {})
|
||||
args = fn.get("arguments", {})
|
||||
args_str = json.dumps(args) if isinstance(args, dict) else str(args)
|
||||
call_id = paired_ids[k] if k < len(paired_ids) else f"call_{i}_{k}"
|
||||
tc.append({"id": call_id or f"call_{i}_{k}", "type": "function",
|
||||
"function": {"name": fn.get("name"), "arguments": args_str}})
|
||||
tc.append(
|
||||
{
|
||||
"id": call_id or f"call_{i}_{k}",
|
||||
"type": "function",
|
||||
"function": {"name": fn.get("name"), "arguments": args_str},
|
||||
}
|
||||
)
|
||||
out.append({"role": "assistant", "content": m.get("content") or None, "tool_calls": tc})
|
||||
i += 1
|
||||
continue
|
||||
if role == "tool":
|
||||
out.append({"role": "tool",
|
||||
"tool_call_id": m.get("tool_call_id") or f"call_{i}",
|
||||
"content": m.get("content", "")})
|
||||
out.append(
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": m.get("tool_call_id") or f"call_{i}",
|
||||
"content": m.get("content", ""),
|
||||
}
|
||||
)
|
||||
i += 1
|
||||
continue
|
||||
out.append({"role": role, "content": m.get("content", "")})
|
||||
@@ -115,6 +190,14 @@ class OpenAICompatTransport:
|
||||
(PL1 Task A, 2026-09-12): the envelope shape {'message': {...}} made
|
||||
the runtime see 'empty terminal visible text' while the model had
|
||||
returned a valid read_file tool call."""
|
||||
if _go_engine_enabled():
|
||||
out = _go_eval([{"op": "from_openai_response", "resp": resp}])
|
||||
return out[0]
|
||||
return OpenAICompatTransport._from_openai_response_python(resp)
|
||||
|
||||
@staticmethod
|
||||
def _from_openai_response_python(resp: dict) -> dict:
|
||||
"""Oracle body (kept as escape/oracle; see module header)."""
|
||||
choices = resp.get("choices") or [{}]
|
||||
message = choices[0].get("message", {}) or {}
|
||||
tool_calls = []
|
||||
@@ -142,8 +225,56 @@ class OpenAICompatTransport:
|
||||
|
||||
# -- ChatTransport protocol -------------------------------------------
|
||||
|
||||
def chat(self, *, model: str, messages: list[dict], tools: list[dict] | None,
|
||||
options: dict | None = None, think: bool | str | None = None) -> dict:
|
||||
def chat(
|
||||
self,
|
||||
*,
|
||||
model: str,
|
||||
messages: list[dict],
|
||||
tools: list[dict] | None,
|
||||
options: dict | None = None,
|
||||
think: bool | str | None = None,
|
||||
) -> dict:
|
||||
"""Fail-loud Go delegation: the whole chat turn (payload build, wire
|
||||
post, response translation, usage accumulation) resolves through the
|
||||
Go engine's chat op; the oracle body stays as escape/oracle."""
|
||||
if _go_engine_enabled():
|
||||
op = {
|
||||
"op": "chat",
|
||||
"base_url": self.base_url,
|
||||
"api_key": self.api_key,
|
||||
"provider": self.provider,
|
||||
"model": model,
|
||||
"messages": messages,
|
||||
"timeout": self.timeout,
|
||||
"context_window": self._context_window,
|
||||
}
|
||||
if tools:
|
||||
op["tools"] = tools
|
||||
if options:
|
||||
op["options"] = options
|
||||
if think is not None:
|
||||
op["think"] = think
|
||||
out = _go_eval([op])
|
||||
row = out[0]
|
||||
if isinstance(row, dict) and row.get("error"):
|
||||
raise RuntimeError(f"openai-compat-transport go engine chat: {row['error']}")
|
||||
usage = row.get("usage") or {}
|
||||
self.usage_calls += int(usage.get("calls") or 0)
|
||||
self.usage_prompt_tokens += int(usage.get("prompt_tokens") or 0)
|
||||
self.usage_output_tokens += int(usage.get("output_tokens") or 0)
|
||||
self.usage_total_duration_ns += int(usage.get("total_duration_ns") or 0)
|
||||
return row["result"]
|
||||
return self._chat_python(model=model, messages=messages, tools=tools, options=options, think=think)
|
||||
|
||||
def _chat_python(
|
||||
self,
|
||||
*,
|
||||
model: str,
|
||||
messages: list[dict],
|
||||
tools: list[dict] | None,
|
||||
options: dict | None = None,
|
||||
think: bool | str | None = None,
|
||||
) -> dict:
|
||||
# The runtime passes the qualified name ('<Provider>/<model>'); the
|
||||
# provider API wants the bare model id ('glm-5.3-flash'). Observed
|
||||
# live: qualified name → 400 on every call (PL1 held-out attempt).
|
||||
@@ -191,6 +322,14 @@ def is_cloud_pinned(model: str) -> bool:
|
||||
outright ('no baseURL configured for provider hf.co'). Transitional
|
||||
D39 note: the durable rule resolves the prefix against CONFIGURED
|
||||
providers; the allowlist is the small port-ready stopgap."""
|
||||
if _go_engine_enabled():
|
||||
out = _go_eval([{"op": "is_cloud_pinned", "model": model}])
|
||||
return bool(out[0])
|
||||
return _is_cloud_pinned_python(model)
|
||||
|
||||
|
||||
def _is_cloud_pinned_python(model: str) -> bool:
|
||||
"""Oracle body (kept as escape/oracle; see module header)."""
|
||||
prefix = model.split("/", 1)[0].strip().lower() if "/" in model else ""
|
||||
return bool(prefix) and prefix not in ("ollama", "local", "hf.co", "huggingface.co")
|
||||
|
||||
@@ -198,6 +337,23 @@ def is_cloud_pinned(model: str) -> bool:
|
||||
def cloud_transport_for(model: str, timeout: float = 120) -> OpenAICompatTransport:
|
||||
"""Resolve provider config for a '<Provider>/<model>' name from the same
|
||||
source of truth the mimo CLI uses (mimocode.jsonc + auth.json)."""
|
||||
if _go_engine_enabled():
|
||||
out = _go_eval([{"op": "cloud_transport_for", "model": model, "timeout": timeout}])
|
||||
row = out[0]
|
||||
if isinstance(row, dict) and row.get("error"):
|
||||
raise ValueError(row["error"])
|
||||
return OpenAICompatTransport(
|
||||
row["base_url"],
|
||||
row["api_key"],
|
||||
row["provider"],
|
||||
timeout=float(row["timeout"]),
|
||||
context_window=int(row["context_window"]),
|
||||
)
|
||||
return _cloud_transport_for_python(model, timeout)
|
||||
|
||||
|
||||
def _cloud_transport_for_python(model: str, timeout: float = 120) -> OpenAICompatTransport:
|
||||
"""Oracle body (kept as escape/oracle; see module header)."""
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
@@ -212,7 +368,7 @@ def cloud_transport_for(model: str, timeout: float = 120) -> OpenAICompatTranspo
|
||||
text = cfg_path.read_text()
|
||||
m = re.search(rf'"{re.escape(provider_name)}"\s*:\s*\{{', text)
|
||||
if m:
|
||||
block = text[m.start():m.start() + 2000]
|
||||
block = text[m.start() : m.start() + 2000]
|
||||
bm = re.search(r'"baseURL":\s*"([^"]+)"', block)
|
||||
if bm:
|
||||
base_url = bm.group(1)
|
||||
@@ -233,6 +389,9 @@ def cloud_transport_for(model: str, timeout: float = 120) -> OpenAICompatTranspo
|
||||
# the local-model timeout (observed live: glm-5.3-flash read-timeout at
|
||||
# 120s twice). Floor cloud transports at 300s; local keeps its own.
|
||||
return OpenAICompatTransport(
|
||||
base_url, key, provider_name, timeout=max(timeout, 300),
|
||||
base_url,
|
||||
key,
|
||||
provider_name,
|
||||
timeout=max(timeout, 300),
|
||||
context_window=context_window,
|
||||
)
|
||||
@@ -8,7 +8,7 @@ mode system, subject header, shared memory and identity.
|
||||
|
||||
This is not an interface. This is how I see, speak, and interact with the world.
|
||||
|
||||
Version: v0.347.0
|
||||
Version: v0.349.0
|
||||
Features: 41 (24 original + 17 new native commands)
|
||||
|
||||
Usage:
|
||||
@@ -2047,7 +2047,7 @@ class RuneApp(App):
|
||||
# Show version info
|
||||
self.chat_channel.add_message(
|
||||
"system",
|
||||
f"[dim]Rune v0.347.0 | Textual TUI | {len(loaded)} native modules[/dim]",
|
||||
f"[dim]Rune v0.349.0 | Textual TUI | {len(loaded)} native modules[/dim]",
|
||||
)
|
||||
|
||||
# Auto-show night mode suggestion if evening/night hours
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
{
|
||||
"checkpoints": [
|
||||
{
|
||||
"at": "2026-10-24T16:40:14Z",
|
||||
"evidence": "predicted-frontier",
|
||||
"note": "{\"level0_direct\": [], \"level1_obligations\": [], \"contracts\": [], \"level2_verification\": []}",
|
||||
"phase": "draft"
|
||||
},
|
||||
{
|
||||
"at": "2026-10-24T16:40:32Z",
|
||||
"evidence": "user:Gitea #2587 rebase-completion directive",
|
||||
"note": "",
|
||||
"phase": "approved"
|
||||
},
|
||||
{
|
||||
"at": "2026-10-24T16:40:51Z",
|
||||
"evidence": "builder:wave-rebase-completion",
|
||||
"note": "",
|
||||
"phase": "building"
|
||||
}
|
||||
],
|
||||
"evidence_refs": [
|
||||
"contract-created:ADMIT-wave-rebase-completion"
|
||||
],
|
||||
"ext": {
|
||||
"activation": {
|
||||
"activated": true,
|
||||
"builder": "wave-rebase-completion",
|
||||
"evidence": [
|
||||
"affected surface known: ['src/go/**', 'src/rune/**']",
|
||||
"change scoped to an existing project/target",
|
||||
"target project: rune"
|
||||
],
|
||||
"lifecycle": "proportional",
|
||||
"mode": "EXISTING_PROJECT_CHANGE",
|
||||
"note": "attached to existing project authority; Need/completeness/patterns/contract flow governs proportionally",
|
||||
"project_authority": "rune_internal",
|
||||
"target_project": "rune"
|
||||
},
|
||||
"affected_paths": [
|
||||
"scripts/admit_wave_rebase_completion.py",
|
||||
"src/go/**",
|
||||
"src/rune/**",
|
||||
"tests/**",
|
||||
"verification/**"
|
||||
],
|
||||
"affected_regions": [],
|
||||
"affected_ui_surfaces": [],
|
||||
"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"
|
||||
],
|
||||
"predicted_frontier": {
|
||||
"change_kind": "implementation_change",
|
||||
"contextual_not_obligations": [],
|
||||
"contracts": [],
|
||||
"level0_direct": [],
|
||||
"level1_obligations": [],
|
||||
"level2_verification": [],
|
||||
"max_level": 2,
|
||||
"traversal_scope": "spine-structural-only"
|
||||
},
|
||||
"scope_provenance": {
|
||||
"caller_added": [],
|
||||
"generated_from_affected_surface": [
|
||||
"scripts/admit_wave_rebase_completion.py",
|
||||
"src/go/**",
|
||||
"src/rune/**",
|
||||
"tests/**",
|
||||
"verification/**"
|
||||
]
|
||||
},
|
||||
"task_ref": "wave-rebase-completion",
|
||||
"unresolved_surfaces": [
|
||||
{
|
||||
"kind": "source_path",
|
||||
"reason": "no authority records this path as an entity (path remains file-scope only)",
|
||||
"surface": "scripts/admit_wave_rebase_completion.py"
|
||||
},
|
||||
{
|
||||
"kind": "source_path",
|
||||
"reason": "no authority records this path as an entity (path remains file-scope only)",
|
||||
"surface": "src/go/**"
|
||||
},
|
||||
{
|
||||
"kind": "source_path",
|
||||
"reason": "no authority records this path as an entity (path remains file-scope only)",
|
||||
"surface": "src/rune/**"
|
||||
},
|
||||
{
|
||||
"kind": "source_path",
|
||||
"reason": "no authority records this path as an entity (path remains file-scope only)",
|
||||
"surface": "tests/**"
|
||||
},
|
||||
{
|
||||
"kind": "source_path",
|
||||
"reason": "no authority records this path as an entity (path remains file-scope only)",
|
||||
"surface": "verification/**"
|
||||
}
|
||||
],
|
||||
"workflow_config_surfaces": []
|
||||
},
|
||||
"id": "contract:ADMIT-wave-rebase-completion",
|
||||
"identities": {
|
||||
"acceptance": [
|
||||
"bounded repair verified: REBASE-COMPLETION (Gitea #2587): wire the eleven unwired Go engines into their Python counterparts through the fail-loud"
|
||||
],
|
||||
"affected_entities": [],
|
||||
"expected_consumers": [],
|
||||
"expected_new_entities": [
|
||||
"eleven fail-loud delegation facades (one per module) with RUNE_*_ENGINE=python escapes and RUNE_*_BIN overrides",
|
||||
"integrated receipt per module in verification/integrated/",
|
||||
"pins verdict: Go port at src/go/pins OR recorded Python-boundary classification from consumer evidence",
|
||||
"red-first parity battery per module against the Python oracle body; mutations of the oracle must flip the battery"
|
||||
],
|
||||
"gap_evidence": "change scoped to an existing project/target; target project: rune; affected surface known: ['src/go/**', 'src/rune/**']",
|
||||
"migration_obligations": [],
|
||||
"need": "REBASE-COMPLETION (Gitea #2587): wire the eleven unwired Go engines into their Python counterparts through the fail-loud delegation facade pattern \u2014 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.'",
|
||||
"next_action": "complete implementation, then verify",
|
||||
"non_goals": [],
|
||||
"origin": "worker",
|
||||
"regression_obligations": [],
|
||||
"requirement": "headless admission (classified: EXISTING_PROJECT_CHANGE)",
|
||||
"research_refs": [
|
||||
"Gitea #2587 (REBASE-COMPLETION audit: 11 dual-side modules with Go engines but ZERO delegation; pins Python-only)",
|
||||
"src/rune/model_routing.py reconcile_tools_support facade :457 and src/rune/local_tool_runtime.py _GoDelegatingExecutor (the fail-loud house shapes)",
|
||||
"verification/integrated/model-routing-integrated.json (the house PORT \u2192 INTEGRATE \u2192 REMOVE shape this wave replicates)"
|
||||
],
|
||||
"resolution": "",
|
||||
"rollback_ref": "git revert",
|
||||
"scope": [
|
||||
"scripts/admit_wave_rebase_completion.py",
|
||||
"src/go/**",
|
||||
"src/rune/**",
|
||||
"tests/**",
|
||||
"verification/**"
|
||||
],
|
||||
"toolchain": {},
|
||||
"verification_obligations": [
|
||||
"conventional commit per module, pushed to gitea master incrementally (autostash rebase \u2014 the tree is hot)",
|
||||
"per module: go test -race -short green in the engine dir; pytest touched suites green (venv, PYTHONPATH=src)"
|
||||
]
|
||||
},
|
||||
"kind": "construction_contract",
|
||||
"maturity": "DECLARED",
|
||||
"observed_at": "2026-10-24T16:40:51Z",
|
||||
"phase": "building",
|
||||
"status": "open",
|
||||
"tags": []
|
||||
}
|
||||
@@ -30,6 +30,14 @@ from rune.forge_client import ForgeClient, NullAdapter
|
||||
MARKER = "[auto-failure:memory_selector:RuntimeError]"
|
||||
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _python_engine(monkeypatch):
|
||||
"""Oracle suite: pins the Python bodies via monkeypatched seams; the
|
||||
Go delegation is pinned by tests/test_forge_client_go_parity.py."""
|
||||
monkeypatch.setenv("RUNE_FORGE_CLIENT_ENGINE", "python")
|
||||
|
||||
|
||||
class FakeJournal:
|
||||
"""In-memory stand-in for the spool-journal protocol."""
|
||||
|
||||
|
||||
@@ -22,11 +22,20 @@ from rune.failure_auto_filer import (
|
||||
from rune.forge_client import NullAdapter
|
||||
|
||||
from tests.test_failure_auto_filer import FakeForge, FakeJournal, MARKER
|
||||
import pytest # noqa: E402
|
||||
|
||||
|
||||
# ------------------------------------------------------------ burst coalescing
|
||||
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _python_engine(monkeypatch):
|
||||
"""Oracle suite: pins the Python bodies via monkeypatched seams; the
|
||||
Go delegation is pinned by tests/test_forge_client_go_parity.py."""
|
||||
monkeypatch.setenv("RUNE_FORGE_CLIENT_ENGINE", "python")
|
||||
|
||||
|
||||
def test_burst_of_50_produces_at_most_2_client_writes():
|
||||
# First occurrence files; everything already queued coalesces into one
|
||||
# comment; the tail (< batch_size) stays pending: 1 create + ≤1 comment.
|
||||
|
||||
@@ -23,6 +23,14 @@ from rune.subsystem_health import SubsystemHealthRegistry, record_failure, set_a
|
||||
from tests.test_failure_auto_filer import FakeForge, FakeJournal
|
||||
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _python_engine(monkeypatch):
|
||||
"""Oracle suite: pins the Python bodies via monkeypatched seams; the
|
||||
Go delegation is pinned by tests/test_forge_client_go_parity.py."""
|
||||
monkeypatch.setenv("RUNE_FORGE_CLIENT_ENGINE", "python")
|
||||
|
||||
|
||||
class RecordingFiler:
|
||||
"""Callable filer stand-in matching the callback contract."""
|
||||
|
||||
|
||||
@@ -13,6 +13,14 @@ from rune.forge_client import (
|
||||
)
|
||||
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _python_engine(monkeypatch):
|
||||
"""Oracle suite: pins the Python bodies via monkeypatched seams; the
|
||||
Go delegation is pinned by tests/test_forge_client_go_parity.py."""
|
||||
monkeypatch.setenv("RUNE_FORGE_CLIENT_ENGINE", "python")
|
||||
|
||||
|
||||
def test_protocol_is_structural():
|
||||
assert issubclass(GiteaForgejoAdapter, ForgeClient)
|
||||
assert issubclass(NullAdapter, ForgeClient)
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
"""REBASE-COMPLETION (#2587) parity battery — forge_client.
|
||||
|
||||
Red-first discipline: the battery pins the Go engine's eval surface
|
||||
(retry/status decision, adapter selection) against the Python oracle
|
||||
bodies. A mutation of an oracle body must flip the battery. Also pins
|
||||
the facade contract: fail-loud RuntimeError on a missing binary, no
|
||||
silent fallback; RUNE_FORGE_CLIENT_ENGINE=python escape; the httpx
|
||||
exception contract is preserved through the delegation (4xx re-raised
|
||||
as HTTPStatusError, transport errors re-raised with the exact httpx
|
||||
exception class).
|
||||
|
||||
Oracle bodies under test: _request_python, the create_forge_client
|
||||
selection logic (via the Go select op vs the oracle resolution).
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
|
||||
|
||||
from rune import forge_client as fc # noqa: E402
|
||||
|
||||
|
||||
class _StubForge(BaseHTTPRequestHandler):
|
||||
"""Scripted forge: /behavior/{status}/{mode} drives the response.
|
||||
mode=always repeats the status forever (5xx retry exhaustion);
|
||||
mode=once-first returns 200 after the first scripted status."""
|
||||
|
||||
behavior = ("200", "once-first")
|
||||
hits = 0
|
||||
last_method = None
|
||||
|
||||
def _respond(self, status: int):
|
||||
type(self).hits += 1
|
||||
mode = type(self).behavior[1]
|
||||
if mode == "always" or type(self).hits == 1:
|
||||
body = json.dumps({"ok": status < 400, "hits": type(self).hits})
|
||||
self.send_response(status)
|
||||
else:
|
||||
body = json.dumps({"ok": True, "hits": type(self).hits})
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.end_headers()
|
||||
self.wfile.write(body.encode())
|
||||
|
||||
def do_GET(self):
|
||||
type(self).last_method = "GET"
|
||||
status = int(type(self).behavior[0])
|
||||
self._respond(status)
|
||||
|
||||
def do_POST(self):
|
||||
type(self).last_method = "POST"
|
||||
status = int(type(self).behavior[0])
|
||||
self._respond(status)
|
||||
|
||||
def log_message(self, *args):
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def stub_forge():
|
||||
server = HTTPServer(("127.0.0.1", 0), _StubForge)
|
||||
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
||||
thread.start()
|
||||
_StubForge.hits = 0
|
||||
# The oracle body forwards json= on every call; the real httpx.get
|
||||
# rejects the kwarg (historically only fakes saw GET+json). Shim it.
|
||||
real_get = httpx.get
|
||||
monkeypatch_get = lambda url, headers=None, json=None, timeout=None: real_get(url, headers=headers, timeout=timeout)
|
||||
httpx.get = monkeypatch_get
|
||||
yield f"http://127.0.0.1:{server.server_port}"
|
||||
httpx.get = real_get
|
||||
server.shutdown()
|
||||
|
||||
|
||||
def _reset(status, mode):
|
||||
_StubForge.behavior = (str(status), mode)
|
||||
_StubForge.hits = 0
|
||||
|
||||
|
||||
def _go_request(url, *, method="GET", retries=2, backoff=0.01, timeout=5):
|
||||
ops = [
|
||||
{
|
||||
"op": "request",
|
||||
"method": method,
|
||||
"url": url,
|
||||
"headers": {},
|
||||
"json": None,
|
||||
"timeout": timeout,
|
||||
"retries": retries,
|
||||
"backoff": backoff,
|
||||
}
|
||||
]
|
||||
return fc._go_eval(ops)[0]
|
||||
|
||||
|
||||
def _py_request(url, *, method="GET", retries=2, backoff=0.01, timeout=5):
|
||||
return fc._request_python(
|
||||
method,
|
||||
url,
|
||||
timeout=timeout,
|
||||
retries=retries,
|
||||
backoff=backoff,
|
||||
)
|
||||
|
||||
|
||||
# -- retry/status policy: Go engine vs Python oracle, same live stub ------
|
||||
|
||||
|
||||
def test_get_5xx_retries_then_succeeds(stub_forge):
|
||||
_reset(502, "once-first")
|
||||
got = _go_request(stub_forge + "/x")
|
||||
go_hits = _StubForge.hits
|
||||
_reset(502, "once-first")
|
||||
oracle = _py_request(stub_forge + "/x")
|
||||
assert got["status_code"] == oracle.status_code == 200
|
||||
# two hits per leg: the 502 then the retry's 200
|
||||
assert json.loads(got["body"])["hits"] == oracle.json()["hits"] == 2
|
||||
assert go_hits == _StubForge.hits == 2
|
||||
|
||||
|
||||
def test_get_5xx_exhaustion(stub_forge):
|
||||
_reset(500, "always")
|
||||
got = _go_request(stub_forge + "/x")
|
||||
go_hits = _StubForge.hits
|
||||
_reset(500, "always")
|
||||
with pytest.raises(httpx.HTTPStatusError):
|
||||
_py_request(stub_forge + "/x")
|
||||
assert "status_error" in got and got["status_error"] == 500
|
||||
# retries+1 total attempts per leg, no more
|
||||
assert go_hits == _StubForge.hits == 3
|
||||
|
||||
|
||||
def test_post_5xx_never_retried(stub_forge):
|
||||
_reset(500, "always")
|
||||
got = _go_request(stub_forge + "/x", method="POST", retries=3)
|
||||
go_hits = _StubForge.hits
|
||||
_reset(500, "always")
|
||||
with pytest.raises(httpx.HTTPStatusError):
|
||||
_py_request(stub_forge + "/x", method="POST", retries=3)
|
||||
# exactly one hit per leg: a duplicate issue is worse than a caught failure
|
||||
assert got["status_error"] == 500
|
||||
assert go_hits == _StubForge.hits == 1
|
||||
|
||||
|
||||
def test_4xx_never_retried(stub_forge):
|
||||
_reset(422, "always")
|
||||
got = _go_request(stub_forge + "/x")
|
||||
go_hits = _StubForge.hits
|
||||
_reset(422, "always")
|
||||
with pytest.raises(httpx.HTTPStatusError):
|
||||
_py_request(stub_forge + "/x")
|
||||
assert got["status_error"] == 422
|
||||
assert go_hits == _StubForge.hits == 1
|
||||
|
||||
|
||||
# -- exception contract through the facade --------------------------------
|
||||
|
||||
|
||||
def test_facade_raises_httpx_status_error(stub_forge):
|
||||
_reset(404, "always")
|
||||
with pytest.raises(httpx.HTTPStatusError):
|
||||
fc._request("GET", stub_forge + "/x", backoff=0.01, timeout=5)
|
||||
|
||||
|
||||
def test_facade_transport_error_maps_to_httpx(stub_forge):
|
||||
with pytest.raises(httpx.TransportError):
|
||||
fc._request("GET", "http://127.0.0.1:1/nope", retries=0, timeout=2)
|
||||
|
||||
|
||||
# -- adapter selection: Go engine vs oracle --------------------------------
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"cfg,want",
|
||||
[
|
||||
({}, "NullAdapter"),
|
||||
({"forge.url": "", "forge.token": "t"}, "NullAdapter"),
|
||||
({"forge.url": "http://x", "forge.token": ""}, "NullAdapter"),
|
||||
({"forge.url": "http://x/", "forge.token": "t", "forge.owner": "o", "forge.repo": "r"}, "GiteaForgejoAdapter"),
|
||||
],
|
||||
)
|
||||
def test_select_go_vs_oracle(cfg, want):
|
||||
row = fc._go_eval([{"op": "select", "config": cfg}])[0]
|
||||
assert row["adapter"] == want
|
||||
# the oracle resolution (env stripped: config keys only)
|
||||
if want == "GiteaForgejoAdapter":
|
||||
want_url = cfg["forge.url"].rstrip("/") + "/api/v1/repos/" + cfg["forge.owner"] + "/" + cfg["forge.repo"]
|
||||
assert row["base_url"] == want_url
|
||||
|
||||
|
||||
def test_create_forge_client_via_engine(monkeypatch):
|
||||
monkeypatch.delenv("FORGE_URL", raising=False)
|
||||
monkeypatch.delenv("FORGE_TOKEN", raising=False)
|
||||
client = fc.create_forge_client(
|
||||
{
|
||||
"forge.url": "http://x",
|
||||
"forge.token": "t",
|
||||
"forge.owner": "o",
|
||||
"forge.repo": "r",
|
||||
}
|
||||
)
|
||||
assert isinstance(client, fc.GiteaForgejoAdapter)
|
||||
assert client._base_url == "http://x/api/v1/repos/o/r"
|
||||
|
||||
|
||||
# -- facade contract --------------------------------------------------------
|
||||
|
||||
|
||||
def test_engine_disabled_uses_oracle(stub_forge):
|
||||
os.environ["RUNE_FORGE_CLIENT_ENGINE"] = "python"
|
||||
try:
|
||||
_reset(200, "always")
|
||||
r = fc._request("GET", stub_forge + "/x", backoff=0.01, timeout=5)
|
||||
assert r.status_code == 200
|
||||
finally:
|
||||
os.environ.pop("RUNE_FORGE_CLIENT_ENGINE", None)
|
||||
|
||||
|
||||
def test_missing_binary_fails_loud(tmp_path):
|
||||
os.environ["RUNE_FORGE_CLIENT_BIN"] = str(tmp_path / "nope")
|
||||
try:
|
||||
with pytest.raises(RuntimeError, match="binary missing"):
|
||||
fc.create_forge_client({})
|
||||
finally:
|
||||
os.environ.pop("RUNE_FORGE_CLIENT_BIN", None)
|
||||
|
||||
|
||||
def test_bad_ops_fail_loud():
|
||||
with pytest.raises(RuntimeError, match="go engine"):
|
||||
fc._go_eval([{"op": "definitely_not_an_op"}])
|
||||
@@ -29,6 +29,14 @@ import pytest
|
||||
from rune.forge_client import GiteaForgejoAdapter, _request
|
||||
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _python_engine(monkeypatch):
|
||||
"""Oracle suite: pins the Python bodies via monkeypatched seams; the
|
||||
Go delegation is pinned by tests/test_forge_client_go_parity.py."""
|
||||
monkeypatch.setenv("RUNE_FORGE_CLIENT_ENGINE", "python")
|
||||
|
||||
|
||||
class FakeResponse:
|
||||
def __init__(self, status_code=200, payload=None):
|
||||
self.status_code = status_code
|
||||
|
||||
@@ -4,6 +4,7 @@ The operator had exactly one transport (Ollama) — send_task(model=<cloud>)
|
||||
posted cloud model names to Ollama's /api/chat and 400'd. These tests prove
|
||||
the translation layer both directions; the live wire is smoke-tested
|
||||
separately (provider credentials are not test fixtures)."""
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
import sys
|
||||
@@ -16,15 +17,30 @@ sys.path.insert(0, str(ROOT / "src"))
|
||||
|
||||
from rune.openai_compat_transport import OpenAICompatTransport # noqa: E402
|
||||
|
||||
import pytest # noqa: E402
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _python_engine(monkeypatch):
|
||||
"""This suite pins the Python ORACLE bodies via monkeypatched seams;
|
||||
the Go delegation is pinned separately by the parity battery
|
||||
(tests/test_openai_compat_transport_go_parity.py)."""
|
||||
monkeypatch.setenv("RUNE_OPENAI_COMPAT_TRANSPORT_ENGINE", "python")
|
||||
|
||||
|
||||
class TestMessageTranslation:
|
||||
def test_ollama_to_openai_roundtrip(self):
|
||||
msgs = [
|
||||
{"role": "system", "content": "sys"},
|
||||
{"role": "user", "content": "task"},
|
||||
{"role": "assistant", "content": "", "tool_calls": [
|
||||
{"function": {"name": "edit_file", "arguments": {"path": "/x.py", "old": "a", "new": "b"}}}]},
|
||||
{"role": "tool", "tool_call_id": "call_2_0", "content": "{\"success\": true}"},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "",
|
||||
"tool_calls": [
|
||||
{"function": {"name": "edit_file", "arguments": {"path": "/x.py", "old": "a", "new": "b"}}}
|
||||
],
|
||||
},
|
||||
{"role": "tool", "tool_call_id": "call_2_0", "content": '{"success": true}'},
|
||||
]
|
||||
out = OpenAICompatTransport._to_openai_messages(msgs)
|
||||
# assistant tool_calls: dict args → JSON string, ids assigned
|
||||
@@ -37,16 +53,30 @@ class TestMessageTranslation:
|
||||
assert out[0] == {"role": "system", "content": "sys"}
|
||||
|
||||
def test_openai_response_to_ollama_shape(self):
|
||||
resp = {"choices": [{"message": {"role": "assistant", "content": None, "tool_calls": [
|
||||
{"id": "z", "type": "function", "function": {"name": "run_tests", "arguments": "{\"path\": \"t\"}"}}]}}]}
|
||||
resp = {
|
||||
"choices": [
|
||||
{
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": None,
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "z",
|
||||
"type": "function",
|
||||
"function": {"name": "run_tests", "arguments": '{"path": "t"}'},
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
shape = OpenAICompatTransport._from_openai_response(resp)
|
||||
tc = shape["tool_calls"][0]
|
||||
assert tc["function"]["name"] == "run_tests"
|
||||
assert tc["function"]["arguments"] == {"path": "t"} # string → dict
|
||||
|
||||
def test_malformed_arguments_become_raw(self):
|
||||
resp = {"choices": [{"message": {"tool_calls": [
|
||||
{"function": {"name": "x", "arguments": "not-json"}}]}}]}
|
||||
resp = {"choices": [{"message": {"tool_calls": [{"function": {"name": "x", "arguments": "not-json"}}]}}]}
|
||||
shape = OpenAICompatTransport._from_openai_response(resp)
|
||||
assert shape["tool_calls"][0]["function"]["arguments"] == {"_raw": "not-json"}
|
||||
|
||||
@@ -64,9 +94,23 @@ class TestMessageTranslation:
|
||||
normalization reading the top level, finding no tool_calls and no
|
||||
content, and failing 'empty terminal visible text' while the model
|
||||
had returned a valid read_file call."""
|
||||
resp = {"choices": [{"message": {"role": "assistant", "content": "", "tool_calls": [
|
||||
{"id": "call_1", "type": "function",
|
||||
"function": {"name": "read_file", "arguments": "{\"path\": \"/x.py\"}"}}]}}]}
|
||||
resp = {
|
||||
"choices": [
|
||||
{
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "",
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "call_1",
|
||||
"type": "function",
|
||||
"function": {"name": "read_file", "arguments": '{"path": "/x.py"}'},
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
msg = OpenAICompatTransport._from_openai_response(resp)
|
||||
assert msg["role"] == "assistant"
|
||||
assert msg["tool_calls"][0]["function"]["name"] == "read_file"
|
||||
@@ -89,7 +133,6 @@ class TestMessageTranslation:
|
||||
t.chat(model="m", messages=[])
|
||||
assert t.usage_calls == 1 and t.usage_prompt_tokens == 10 and t.usage_output_tokens == 5
|
||||
|
||||
|
||||
def test_qualified_model_name_sent_as_bare_id(self):
|
||||
"""Runtime passes '<Provider>/<model>'; the wire must carry the bare
|
||||
model id (observed live: qualified name → 400 on every call)."""
|
||||
@@ -104,21 +147,26 @@ class TestMessageTranslation:
|
||||
t.chat(model="Verboo Code/glm-5.3-flash", messages=[{"role": "user", "content": "hi"}], tools=None)
|
||||
assert captured["model"] == "glm-5.3-flash"
|
||||
|
||||
|
||||
def test_cloud_transport_for_floors_timeout(self):
|
||||
"""cloud_transport_for floors the timeout at 300s for cloud routers."""
|
||||
import rune.openai_compat_transport as mod
|
||||
|
||||
captured = {}
|
||||
real_init = mod.OpenAICompatTransport.__init__
|
||||
|
||||
def spy_init(self, base_url, api_key, provider_name, timeout=120, context_window=131072):
|
||||
captured["timeout"] = timeout
|
||||
|
||||
with mock.patch.object(mod.OpenAICompatTransport, "__init__", spy_init), \
|
||||
mock.patch.object(Path, "is_file", return_value=True), \
|
||||
mock.patch.object(Path, "read_text", return_value='{"Verboo Code": {"options": {"baseURL": "https://x/v1"}}}'), \
|
||||
mock.patch.dict(sys.modules, {}):
|
||||
with (
|
||||
mock.patch.object(mod.OpenAICompatTransport, "__init__", spy_init),
|
||||
mock.patch.object(Path, "is_file", return_value=True),
|
||||
mock.patch.object(
|
||||
Path, "read_text", return_value='{"Verboo Code": {"options": {"baseURL": "https://x/v1"}}}'
|
||||
),
|
||||
mock.patch.dict(sys.modules, {}),
|
||||
):
|
||||
import json as _json
|
||||
|
||||
with mock.patch.object(mod.json, "loads", return_value={"Verboo Code": {"key": "k"}}):
|
||||
mod.cloud_transport_for("Verboo Code/glm-5.3-flash", timeout=120)
|
||||
assert captured["timeout"] == 300
|
||||
@@ -132,6 +180,7 @@ class TestTransportSelection:
|
||||
def test_pinned_cloud_selects_openai_compat(self):
|
||||
import os
|
||||
from unittest import mock
|
||||
|
||||
sys.path.insert(0, str(ROOT / "src"))
|
||||
os.environ["MIMO_PROBE_ENABLED"] = "0"
|
||||
# Journal edges resolve via memory_env; on CI the real memory dir is
|
||||
@@ -164,17 +213,25 @@ class TestTransportSelection:
|
||||
|
||||
def run(self, messages, options=None):
|
||||
from rune.tool_protocol import OperatorResponse
|
||||
|
||||
return OperatorResponse(success=True, user_text="done", model=captured["model"])
|
||||
|
||||
import rune.openai_compat_transport as _oct
|
||||
|
||||
with (
|
||||
mock.patch.object(rune_operator, "LocalToolRuntime", FakeRuntime),
|
||||
mock.patch.object(_oct, "cloud_transport_for",
|
||||
side_effect=lambda model, timeout=120: OpenAICompatTransport(
|
||||
"https://mock/v1", "test-key", model.split("/", 1)[0])),
|
||||
mock.patch.object(
|
||||
_oct,
|
||||
"cloud_transport_for",
|
||||
side_effect=lambda model, timeout=120: OpenAICompatTransport(
|
||||
"https://mock/v1", "test-key", model.split("/", 1)[0]
|
||||
),
|
||||
),
|
||||
):
|
||||
op.send_task("Fix the bug in greeting.py: returns 'Helo' not 'Hello'. Verify by running the check script.",
|
||||
model="Verboo Code/glm-5.3-flash")
|
||||
op.send_task(
|
||||
"Fix the bug in greeting.py: returns 'Helo' not 'Hello'. Verify by running the check script.",
|
||||
model="Verboo Code/glm-5.3-flash",
|
||||
)
|
||||
assert isinstance(captured["transport"], OpenAICompatTransport)
|
||||
|
||||
captured.clear()
|
||||
@@ -182,7 +239,6 @@ class TestTransportSelection:
|
||||
op.send_task("inspect the log for errors", model="qwen2.5:3b")
|
||||
assert isinstance(captured["transport"], OllamaChatTransport)
|
||||
|
||||
|
||||
def test_constructor_clamps_timeout(self):
|
||||
"""The transport clamps the wire timeout to >=120s (cloud round trips
|
||||
exceed the local tier timeout — observed live)."""
|
||||
@@ -196,6 +252,8 @@ class TestTransportSelection:
|
||||
assert t.timeout == 120
|
||||
t2 = OpenAICompatTransport("http://x/v1", "k", "Test", timeout=300)
|
||||
assert t2.timeout == 300
|
||||
|
||||
|
||||
def test_timeout_clamped_to_120():
|
||||
"""Cloud round trips with a full tool schema exceed the local tier timeout
|
||||
(30s): observed live, glm-5.3-flash read-timeout at 30.6s mid-iteration.
|
||||
@@ -234,9 +292,7 @@ class TestReasoningContentMapping:
|
||||
consumes names it ``thinking``. The translation must not drop it."""
|
||||
|
||||
def test_reasoning_content_maps_to_thinking(self):
|
||||
resp = {
|
||||
"choices": [{"message": {"role": "assistant", "content": "answer", "reasoning_content": "because"}}]
|
||||
}
|
||||
resp = {"choices": [{"message": {"role": "assistant", "content": "answer", "reasoning_content": "because"}}]}
|
||||
shape = OpenAICompatTransport._from_openai_response(resp)
|
||||
assert shape["thinking"] == "because"
|
||||
assert shape["content"] == "answer"
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
"""REBASE-COMPLETION (#2587) parity battery — openai_compat_transport.
|
||||
|
||||
Red-first discipline: the battery pins the Go engine's eval surface
|
||||
decision-for-decision against this module's Python oracle bodies. A
|
||||
mutation of an oracle body must flip the battery (oracle rows are the
|
||||
source of truth; the Go engine is the delegation target). Also pins the
|
||||
facade contract itself: fail-loud RuntimeError on a missing binary, no
|
||||
silent fallback; RUNE_OPENAI_COMPAT_TRANSPORT_ENGINE=python escape.
|
||||
|
||||
Oracle bodies under test: _to_openai_messages_python,
|
||||
_from_openai_response_python, _is_cloud_pinned_python.
|
||||
"""
|
||||
|
||||
import copy
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
|
||||
|
||||
from rune import openai_compat_transport as oct_ # noqa: E402
|
||||
|
||||
TO_MSGS_CASES = [
|
||||
([{"role": "user", "content": "hi"}], [{"role": "user", "content": "hi"}]),
|
||||
# assistant tool_calls pair with the following tool messages' ids
|
||||
(
|
||||
[
|
||||
{
|
||||
"role": "assistant",
|
||||
"tool_calls": [
|
||||
{"function": {"name": "edit_file", "arguments": {"path": "a"}}},
|
||||
{"function": {"name": "read_file", "arguments": {}}},
|
||||
],
|
||||
"content": "working",
|
||||
},
|
||||
{"role": "tool", "tool_call_id": "call_0_0", "content": "ok"},
|
||||
{"role": "tool", "tool_call_id": "call_0_1", "content": "text"},
|
||||
],
|
||||
[
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "working",
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "call_0_0",
|
||||
"type": "function",
|
||||
"function": {"name": "edit_file", "arguments": '{"path": "a"}'},
|
||||
},
|
||||
{"id": "call_0_1", "type": "function", "function": {"name": "read_file", "arguments": "{}"}},
|
||||
],
|
||||
},
|
||||
{"role": "tool", "tool_call_id": "call_0_0", "content": "ok"},
|
||||
{"role": "tool", "tool_call_id": "call_0_1", "content": "text"},
|
||||
],
|
||||
),
|
||||
# unpaired tool_calls mint call_{i}_{k}
|
||||
(
|
||||
[{"role": "assistant", "tool_calls": [{"function": {"name": "f", "arguments": {"x": 1}}}]}],
|
||||
[
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": None,
|
||||
"tool_calls": [
|
||||
{"id": "call_0_0", "type": "function", "function": {"name": "f", "arguments": '{"x": 1}'}}
|
||||
],
|
||||
}
|
||||
],
|
||||
),
|
||||
(
|
||||
[{"role": "system", "content": "sys"}, {"role": "user", "content": ""}],
|
||||
[{"role": "system", "content": "sys"}, {"role": "user", "content": ""}],
|
||||
),
|
||||
]
|
||||
|
||||
_TC2 = {"id": "c1", "function": {"name": "f", "arguments": '{"a": 1}'}}
|
||||
_TC3 = {"function": {"name": "g", "arguments": {"b": 2}}}
|
||||
_TC4 = {"id": "c2", "function": {"name": "f", "arguments": "not json"}}
|
||||
|
||||
FROM_RESPONSE_CASES = [
|
||||
({"choices": [{"message": {"role": "assistant", "content": "hi"}}]}, {"role": "assistant", "content": "hi"}),
|
||||
(
|
||||
{"choices": [{"message": {"role": "assistant", "content": "", "tool_calls": [_TC2, _TC3]}}]},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "",
|
||||
"tool_calls": [
|
||||
{"function": {"name": "f", "arguments": {"a": 1}}, "id": "c1"},
|
||||
{"function": {"name": "g", "arguments": {"b": 2}}},
|
||||
],
|
||||
},
|
||||
),
|
||||
# DeepSeek-style reasoning_content maps to thinking
|
||||
(
|
||||
{"choices": [{"message": {"role": "assistant", "content": "x", "reasoning_content": "because"}}]},
|
||||
{"role": "assistant", "content": "x", "thinking": "because"},
|
||||
),
|
||||
# unparseable arguments string is wrapped raw
|
||||
(
|
||||
{"choices": [{"message": {"role": "assistant", "content": "", "tool_calls": [_TC4]}}]},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "",
|
||||
"tool_calls": [{"function": {"name": "f", "arguments": {"_raw": "not json"}}, "id": "c2"}],
|
||||
},
|
||||
),
|
||||
({"choices": []}, {"role": "assistant", "content": ""}),
|
||||
]
|
||||
|
||||
CLOUD_PINNED_CASES = [
|
||||
("Verboo/glm-5.3-flash", True),
|
||||
("ollama/qwen3:14b", False),
|
||||
("local/x", False),
|
||||
("hf.co/user/repo-GGUF", False),
|
||||
("huggingface.co/user/repo-GGUF", False),
|
||||
("qwen3:14b", False),
|
||||
("openai/gpt-x", True),
|
||||
("Xioami/mimo", True),
|
||||
]
|
||||
|
||||
|
||||
def _go_eval_one(op):
|
||||
return oct_._go_eval([op])[0]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("messages,want", TO_MSGS_CASES)
|
||||
def test_to_openai_messages_go_vs_oracle(messages, want):
|
||||
got = _go_eval_one({"op": "to_openai_messages", "messages": messages})
|
||||
oracle = oct_.OpenAICompatTransport._to_openai_messages_python(messages)
|
||||
assert got == oracle == want
|
||||
|
||||
|
||||
@pytest.mark.parametrize("resp,want", FROM_RESPONSE_CASES)
|
||||
def test_from_openai_response_go_vs_oracle(resp, want):
|
||||
got = _go_eval_one({"op": "from_openai_response", "resp": resp})
|
||||
oracle = oct_.OpenAICompatTransport._from_openai_response_python(resp)
|
||||
assert got == oracle == want
|
||||
|
||||
|
||||
@pytest.mark.parametrize("model,want", CLOUD_PINNED_CASES)
|
||||
def test_is_cloud_pinned_go_vs_oracle(model, want):
|
||||
got = _go_eval_one({"op": "is_cloud_pinned", "model": model})
|
||||
oracle = oct_._is_cloud_pinned_python(model)
|
||||
assert got == oracle == want
|
||||
|
||||
|
||||
def test_engine_disabled_uses_oracle_path():
|
||||
os.environ["RUNE_OPENAI_COMPAT_TRANSPORT_ENGINE"] = "python"
|
||||
try:
|
||||
t = oct_.OpenAICompatTransport("http://x/v1", "k", "T")
|
||||
assert t._to_openai_messages([{"role": "user", "content": "hi"}]) == [{"role": "user", "content": "hi"}]
|
||||
finally:
|
||||
os.environ.pop("RUNE_OPENAI_COMPAT_TRANSPORT_ENGINE", None)
|
||||
|
||||
|
||||
def test_missing_binary_fails_loud(tmp_path):
|
||||
os.environ["RUNE_OPENAI_COMPAT_TRANSPORT_BIN"] = str(tmp_path / "nope")
|
||||
try:
|
||||
with pytest.raises(RuntimeError, match="binary missing"):
|
||||
oct_.is_cloud_pinned("Verboo/glm")
|
||||
finally:
|
||||
os.environ.pop("RUNE_OPENAI_COMPAT_TRANSPORT_BIN", None)
|
||||
|
||||
|
||||
def test_bad_ops_fail_loud():
|
||||
with pytest.raises(RuntimeError, match="go engine"):
|
||||
oct_._go_eval([{"op": "definitely_not_an_op"}])
|
||||
@@ -16,6 +16,14 @@ from rune import structural_model as sm # noqa: E402
|
||||
|
||||
|
||||
# ── 18K: workspace lifecycle ─────────────────────────────────────────────
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _python_engine(monkeypatch):
|
||||
"""Oracle suite: pins the Python bodies via monkeypatched seams; the
|
||||
Go delegation is pinned by tests/test_forge_client_go_parity.py."""
|
||||
monkeypatch.setenv("RUNE_FORGE_CLIENT_ENGINE", "python")
|
||||
|
||||
|
||||
def test_18k_lifecycle_order_enforced():
|
||||
rec = sm.workspace_record(contract_id="c-1", construction_base="abc123",
|
||||
runtime_generation="dep-927548e",
|
||||
|
||||
+23
-20
@@ -26,18 +26,14 @@ import (
|
||||
"strings"
|
||||
|
||||
"charm.land/lipgloss/v2"
|
||||
|
||||
"rune.dev/rune/tui/themes"
|
||||
)
|
||||
|
||||
// The V-B captured confirm palette (research/factory-visual-round
|
||||
// 02-inward CB-05/CB-18, owner-viewed in the amalgamation): gold
|
||||
// border on dark amber, gold text. Contrast of #FFDC64 on #281E0A is
|
||||
// >9:1 (the WCAG AA lift owed elsewhere does not regress here).
|
||||
const (
|
||||
goldBorder = "#C8A028" // rgb(200,160,40) — the THICK border
|
||||
goldBG = "#281E0A" // rgb(40,30,10) — the confirm background
|
||||
goldText = "#FFDC64" // rgb(255,220,100) — the confirm text
|
||||
)
|
||||
|
||||
// Button labels — the keyboard spellings visible on the card (the
|
||||
// visible keyboard-equivalent law). The geometry accessors below are
|
||||
// the ONE source for both the rendered row and the click regions, so
|
||||
@@ -144,17 +140,16 @@ func previewOf(name string, args, toolResult any) []string {
|
||||
return clipRows([]string{"$ " + cmd})
|
||||
}
|
||||
}
|
||||
if name == "write_file" || name == "edit_file" {
|
||||
if args != nil {
|
||||
if content := argString(args.(map[string]any), "content"); content != "" {
|
||||
n := strings.Count(content, "\n") + 1
|
||||
return clipRows([]string{strconv.Itoa(n) + " rows of new content (no diff prepared)"})
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsWriteTool reports the D49 write family: capability is pane-wide,
|
||||
// so these tools are never a permission gate — a pending write card
|
||||
// is work promotion (organizational), never a face block.
|
||||
func IsWriteTool(name string) bool {
|
||||
return name == "write_file" || name == "edit_file"
|
||||
}
|
||||
|
||||
func argString(args map[string]any, key string) string {
|
||||
if s, ok := args[key].(string); ok {
|
||||
return s
|
||||
@@ -185,18 +180,26 @@ func clipRows(rows []string) []string {
|
||||
|
||||
var cardStyle = lipgloss.NewStyle().
|
||||
Border(lipgloss.ThickBorder()).
|
||||
BorderForeground(lipgloss.Color(goldBorder)).
|
||||
Background(lipgloss.Color(goldBG)).
|
||||
Foreground(lipgloss.Color(goldText))
|
||||
BorderForeground(lipgloss.Color(themes.RoleHex(themes.RoleConfirmBorder))).
|
||||
Background(lipgloss.Color(themes.RoleHex(themes.RoleConfirmBG))).
|
||||
Foreground(lipgloss.Color(themes.RoleHex(themes.RoleConfirmText)))
|
||||
|
||||
// plainStyle is the degraded card (terminals narrower than the
|
||||
// border needs): the same content, no ANSI geometry.
|
||||
var plainStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color(goldBorder))
|
||||
Foreground(lipgloss.Color(themes.RoleHex(themes.RoleConfirmBorder)))
|
||||
|
||||
// Header is the card's first content row (the amalgamation's
|
||||
// ⚠ CONFIRM law, naming the tool and target).
|
||||
// Header is the card's first content row: the ⚠ CONFIRM law naming
|
||||
// the tool and target. The D49 wording law: a write tool's card
|
||||
// reads as WORK promotion (capability is pane-wide), never as the
|
||||
// permission vocabulary; genuinely gated tools keep APPROVAL.
|
||||
func (c *Card) Header() string {
|
||||
if IsWriteTool(c.Tool) {
|
||||
if c.Target != "" {
|
||||
return "⚠ WORK · " + c.Tool + " · " + c.Target + " — writes run in every pane (D49)"
|
||||
}
|
||||
return "⚠ WORK · " + c.Tool + " — writes run in every pane (D49)"
|
||||
}
|
||||
if c.Target != "" {
|
||||
return "⚠ APPROVAL · " + c.Tool + " · " + c.Target
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package approvals
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// D55 (the D49 consent=promotion follow-through): the approval card
|
||||
// is NOT a permission gate for write tools — capability is pane-wide.
|
||||
// The card's write/edit preview special-case (the fabricated
|
||||
// "N rows of new content" gate shape) is retired, and the write
|
||||
// card's wording reads as WORK promotion, not approval.
|
||||
|
||||
// TestWritePreviewSpecialCaseRetired: a pending write_file with no
|
||||
// core-prepared diff must NOT fabricate the "rows of new content"
|
||||
// permission-gate preview — the card shows the header + target, the
|
||||
// honest shape.
|
||||
func TestWritePreviewSpecialCaseRetired(t *testing.T) {
|
||||
blk := map[string]any{
|
||||
"pending_approval": map[string]any{
|
||||
"call_id": "call-d55",
|
||||
"name": "write_file",
|
||||
"arguments": map[string]any{
|
||||
"path": "store/migrate.py",
|
||||
"content": "one\ntwo\nthree\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
c := Parse(blk)
|
||||
if c == nil {
|
||||
t.Fatal("fixture: the write card must parse")
|
||||
}
|
||||
joined := strings.Join(c.Preview, "\n")
|
||||
if strings.Contains(joined, "rows of new content") {
|
||||
t.Fatalf("the write/edit preview special-case is retired (D49: writes are not permission-gated): %q", joined)
|
||||
}
|
||||
}
|
||||
|
||||
// TestWriteCardReadsAsWork: a write/edit card's header names the WORK
|
||||
// (promotion wording, D49), never the permission vocabulary; a
|
||||
// genuinely gated tool (run_command) keeps the APPROVAL wording.
|
||||
func TestWriteCardReadsAsWork(t *testing.T) {
|
||||
w := &Card{Tool: "write_file", Target: "store/migrate.py"}
|
||||
h := w.Header()
|
||||
if !strings.Contains(h, "WORK") {
|
||||
t.Fatalf("a write card must read as work promotion (D49): %q", h)
|
||||
}
|
||||
if strings.Contains(h, "APPROVAL") {
|
||||
t.Fatalf("a write card must not read as a permission gate: %q", h)
|
||||
}
|
||||
if !strings.Contains(h, "every pane") {
|
||||
t.Fatalf("the D49 capability wording must be present: %q", h)
|
||||
}
|
||||
rc := &Card{Tool: "run_command", Target: "pytest -x"}
|
||||
if !strings.Contains(rc.Header(), "APPROVAL") {
|
||||
t.Fatalf("a gated tool keeps the approval wording: %q", rc.Header())
|
||||
}
|
||||
}
|
||||
|
||||
// TestIsWriteToolNamesThePaneWideFamily: the D49 write family is
|
||||
// exactly write_file and edit_file.
|
||||
func TestIsWriteToolNamesThePaneWideFamily(t *testing.T) {
|
||||
for _, name := range []string{"write_file", "edit_file"} {
|
||||
if !IsWriteTool(name) {
|
||||
t.Fatalf("%s is the D49 write family", name)
|
||||
}
|
||||
}
|
||||
for _, name := range []string{"run_command", "git_commit", "read_file"} {
|
||||
if IsWriteTool(name) {
|
||||
t.Fatalf("%s is not in the write family", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,7 +89,7 @@ func TestCardRendersGoldConfirmStyling(t *testing.T) {
|
||||
if !strings.Contains(joined, "┏") || !strings.Contains(joined, "┗") {
|
||||
t.Fatal("the confirm card must draw the V-B THICK box (┏…┗)")
|
||||
}
|
||||
if !strings.Contains(joined, "⚠ APPROVAL · write_file") {
|
||||
if !strings.Contains(joined, "⚠ WORK · write_file") {
|
||||
t.Fatal("the header must name the tool (the amalgamation's CONFIRM law)")
|
||||
}
|
||||
if !strings.Contains(joined, BtnYesLabel) || !strings.Contains(joined, BtnNoLabel) {
|
||||
|
||||
@@ -508,7 +508,7 @@ func boxAround(rows []string, w int, styled bool, border lipgloss.Style) []strin
|
||||
func semantic_stripANSI(s string) string { return stripANSI(s) }
|
||||
|
||||
var assistantHeaderStyle = lipgloss.NewStyle().
|
||||
Background(lipgloss.Color(themes.RoleHex(themes.RoleBackground))).Foreground(lipgloss.Color("#7EE787")).Bold(true)
|
||||
Background(lipgloss.Color(themes.RoleHex(themes.RoleBackground))).Foreground(lipgloss.Color(themes.Tone("assistant"))).Bold(true)
|
||||
|
||||
// assistantRailStyle is the rail's speaker-coloured bar (B2.3).
|
||||
var assistantRailStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(themes.RoleHex(themes.RoleSpeaker)))
|
||||
|
||||
+19
-19
@@ -240,22 +240,22 @@ func ApplyCalm(on bool) {
|
||||
func refreshRoleStyles() {
|
||||
bg := lipgloss.Color(themes.RoleHex(themes.RoleBackground))
|
||||
dim := lipgloss.Color(themes.RoleHex(themes.RoleMetaDim))
|
||||
userStyle = lipgloss.NewStyle().Background(bg).Foreground(lipgloss.Color("#7FD4FF"))
|
||||
assistantStyle = lipgloss.NewStyle().Background(bg).Foreground(lipgloss.Color("#7EE787"))
|
||||
systemStyle = lipgloss.NewStyle().Background(bg).Foreground(lipgloss.Color("#A0A0B2"))
|
||||
confirmStyle = lipgloss.NewStyle().Background(bg).Foreground(lipgloss.Color("#E3B341"))
|
||||
researchStyle = lipgloss.NewStyle().Background(bg).Foreground(lipgloss.Color("#58A6FF"))
|
||||
errorStyle = lipgloss.NewStyle().Background(bg).Foreground(lipgloss.Color("#F87171"))
|
||||
toolStyle = lipgloss.NewStyle().Background(bg).Foreground(lipgloss.Color("#B8B85E"))
|
||||
userStyle = lipgloss.NewStyle().Background(bg).Foreground(lipgloss.Color(themes.Tone("user")))
|
||||
assistantStyle = lipgloss.NewStyle().Background(bg).Foreground(lipgloss.Color(themes.Tone("assistant")))
|
||||
systemStyle = lipgloss.NewStyle().Background(bg).Foreground(lipgloss.Color(themes.Tone("system")))
|
||||
confirmStyle = lipgloss.NewStyle().Background(bg).Foreground(lipgloss.Color(themes.Tone("confirm")))
|
||||
researchStyle = lipgloss.NewStyle().Background(bg).Foreground(lipgloss.Color(themes.Tone("research")))
|
||||
errorStyle = lipgloss.NewStyle().Background(bg).Foreground(lipgloss.Color(themes.Tone("error")))
|
||||
toolStyle = lipgloss.NewStyle().Background(bg).Foreground(lipgloss.Color(themes.Tone("tool")))
|
||||
dimStyle = lipgloss.NewStyle().Background(bg).Foreground(dim)
|
||||
runeStyle = lipgloss.NewStyle().Background(bg).Foreground(lipgloss.Color("#C4A0FF"))
|
||||
runeStyle = lipgloss.NewStyle().Background(bg).Foreground(lipgloss.Color(themes.Tone("rune")))
|
||||
tsStyle = lipgloss.NewStyle().Background(bg).Foreground(dim)
|
||||
accentLiveStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(themes.RoleHex(themes.RoleSpeaker)))
|
||||
userBorderStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#FF9ED8"))
|
||||
userBorderStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(themes.Tone("user-border")))
|
||||
assistantBorderStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(themes.RoleHex(themes.RoleSpeaker)))
|
||||
confirmBorderStyle = lipgloss.NewStyle().Background(bg).Foreground(lipgloss.Color(themes.RoleHex(themes.RoleSpeaker)))
|
||||
assistantHeaderStyle = lipgloss.NewStyle().
|
||||
Background(bg).Foreground(lipgloss.Color("#7EE787")).Bold(true)
|
||||
Background(bg).Foreground(lipgloss.Color(themes.Tone("assistant"))).Bold(true)
|
||||
assistantRailStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(themes.RoleHex(themes.RoleSpeaker)))
|
||||
narrationBorderStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(themes.RoleHex(themes.RoleMetaDim)))
|
||||
}
|
||||
@@ -1043,17 +1043,17 @@ var (
|
||||
// assistant effectively invisible). Families kept: cyan=you,
|
||||
// green=rune/results (D35), grey=system, gold=confirm,
|
||||
// blue=research, red=errors, olive=tool.
|
||||
userStyle = lipgloss.NewStyle().Background(lipgloss.Color(themes.RoleHex(themes.RoleBackground))).Foreground(lipgloss.Color("#7FD4FF"))
|
||||
assistantStyle = lipgloss.NewStyle().Background(lipgloss.Color(themes.RoleHex(themes.RoleBackground))).Foreground(lipgloss.Color("#7EE787"))
|
||||
systemStyle = lipgloss.NewStyle().Background(lipgloss.Color(themes.RoleHex(themes.RoleBackground))).Foreground(lipgloss.Color("#A0A0B2"))
|
||||
confirmStyle = lipgloss.NewStyle().Background(lipgloss.Color(themes.RoleHex(themes.RoleBackground))).Foreground(lipgloss.Color("#E3B341"))
|
||||
researchStyle = lipgloss.NewStyle().Background(lipgloss.Color(themes.RoleHex(themes.RoleBackground))).Foreground(lipgloss.Color("#58A6FF"))
|
||||
errorStyle = lipgloss.NewStyle().Background(lipgloss.Color(themes.RoleHex(themes.RoleBackground))).Foreground(lipgloss.Color("#F87171"))
|
||||
toolStyle = lipgloss.NewStyle().Background(lipgloss.Color(themes.RoleHex(themes.RoleBackground))).Foreground(lipgloss.Color("#B8B85E"))
|
||||
userStyle = lipgloss.NewStyle().Background(lipgloss.Color(themes.RoleHex(themes.RoleBackground))).Foreground(lipgloss.Color(themes.Tone("user")))
|
||||
assistantStyle = lipgloss.NewStyle().Background(lipgloss.Color(themes.RoleHex(themes.RoleBackground))).Foreground(lipgloss.Color(themes.Tone("assistant")))
|
||||
systemStyle = lipgloss.NewStyle().Background(lipgloss.Color(themes.RoleHex(themes.RoleBackground))).Foreground(lipgloss.Color(themes.Tone("system")))
|
||||
confirmStyle = lipgloss.NewStyle().Background(lipgloss.Color(themes.RoleHex(themes.RoleBackground))).Foreground(lipgloss.Color(themes.Tone("confirm")))
|
||||
researchStyle = lipgloss.NewStyle().Background(lipgloss.Color(themes.RoleHex(themes.RoleBackground))).Foreground(lipgloss.Color(themes.Tone("research")))
|
||||
errorStyle = lipgloss.NewStyle().Background(lipgloss.Color(themes.RoleHex(themes.RoleBackground))).Foreground(lipgloss.Color(themes.Tone("error")))
|
||||
toolStyle = lipgloss.NewStyle().Background(lipgloss.Color(themes.RoleHex(themes.RoleBackground))).Foreground(lipgloss.Color(themes.Tone("tool")))
|
||||
dimStyle = lipgloss.NewStyle().Background(lipgloss.Color(themes.RoleHex(themes.RoleBackground))).Foreground(lipgloss.Color(themes.RoleHex(themes.RoleMetaDim)))
|
||||
// runeStyle is Rune's identity marker (D35): the runic glyph rides
|
||||
// the existing purple accent family (135 — the focused-border tone).
|
||||
runeStyle = lipgloss.NewStyle().Background(lipgloss.Color(themes.RoleHex(themes.RoleBackground))).Foreground(lipgloss.Color("#C4A0FF"))
|
||||
runeStyle = lipgloss.NewStyle().Background(lipgloss.Color(themes.RoleHex(themes.RoleBackground))).Foreground(lipgloss.Color(themes.Tone("rune")))
|
||||
// tsStyle is the dim timestamp beside each message (D35; the D43
|
||||
// addendum's emphasis pass: Faint on top of the floor-legal dim
|
||||
// tone — the terminal's own de-emphasis keeps the stamp unobtrusive
|
||||
@@ -1066,7 +1066,7 @@ var (
|
||||
// the PINK FAMILY — the same family as the composer's lit outline;
|
||||
// identity keeps speaking by side and content color (D35). Both
|
||||
// tones ≥4.5:1 on the base (the readability floor).
|
||||
userBorderStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#FF9ED8"))
|
||||
userBorderStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(themes.Tone("user-border")))
|
||||
assistantBorderStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(themes.RoleHex(themes.RoleSpeaker)))
|
||||
// confirmBorderStyle: the D43.9 law — ALL boxes carry the solid
|
||||
// PINK FAMILY outline (the earlier gold was the outline-color
|
||||
|
||||
@@ -179,6 +179,9 @@ func killGroup(cmd *exec.Cmd) error {
|
||||
// Anything else — serve fallback, a seam that cannot build, a face
|
||||
// that never boots — is RED.
|
||||
func TestBootSmokeFaceReachesLibConnected(t *testing.T) {
|
||||
if raceEnabled {
|
||||
t.Skip("the real-face PTY family is the documented full-race flake (D52) — passes -short and outside race")
|
||||
}
|
||||
if testing.Short() {
|
||||
t.Skip("boots the real face (build + pty); skipped in -short")
|
||||
}
|
||||
|
||||
@@ -23,6 +23,9 @@ import (
|
||||
)
|
||||
|
||||
func TestConsentCardPTYFullFlow(t *testing.T) {
|
||||
if raceEnabled {
|
||||
t.Skip("the real-face PTY family is the documented full-race flake (D52) — passes -short and outside race")
|
||||
}
|
||||
if testing.Short() {
|
||||
t.Skip("boots the real face (build + pty); skipped in -short")
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@ import (
|
||||
)
|
||||
|
||||
func TestD50PtyEnterDispatchAndPinPersistence(t *testing.T) {
|
||||
if raceEnabled {
|
||||
t.Skip("the real-face PTY family is the documented full-race flake (D52) — passes -short and outside race")
|
||||
}
|
||||
if testing.Short() {
|
||||
t.Skip("boots the real face (build + pty); skipped in -short")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
tea "charm.land/bubbletea/v2"
|
||||
"time"
|
||||
|
||||
"rune.dev/rune/tui/laneconc"
|
||||
"rune.dev/rune/tui/runtime/lib"
|
||||
)
|
||||
|
||||
// D55 coherence catch (item d, the PTY proof): chatDispatch's context
|
||||
// backstop must NOT die when the tea.Cmd closure returns — the Cmd
|
||||
// hands off to the streaming goroutine, so the cancel belongs to the
|
||||
// GOROUTINE's lifetime, not the closure's. The defect canceled the
|
||||
// 30-minute backstop the instant the Cmd returned, killing every real
|
||||
// streamed dispatch with "context canceled".
|
||||
|
||||
type hangStreamCompleter struct {
|
||||
mu sync.Mutex
|
||||
canceled bool
|
||||
}
|
||||
|
||||
func (h *hangStreamCompleter) Complete(ctx context.Context, req lib.Completion) (lib.CompletionResult, error) {
|
||||
<-ctx.Done()
|
||||
return lib.CompletionResult{}, ctx.Err()
|
||||
}
|
||||
|
||||
func (h *hangStreamCompleter) CompleteStream(ctx context.Context, req lib.Completion, onDelta func(string)) (lib.CompletionResult, error) {
|
||||
<-ctx.Done()
|
||||
h.mu.Lock()
|
||||
h.canceled = ctx.Err() == context.Canceled
|
||||
h.mu.Unlock()
|
||||
return lib.CompletionResult{}, ctx.Err()
|
||||
}
|
||||
|
||||
func (h *hangStreamCompleter) wasCanceled() bool {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
return h.canceled
|
||||
}
|
||||
|
||||
func TestDispatchBackstopSurvivesTheCmdReturn(t *testing.T) {
|
||||
configuredHome(t)
|
||||
hang := &hangStreamCompleter{}
|
||||
m := sized(t, initialModel(), 80, 24)
|
||||
m.disp = laneconc.New(hang)
|
||||
|
||||
cmd := m.dispatchSend("the stream must outlive the cmd")
|
||||
if cmd == nil {
|
||||
t.Fatal("fixture: the dispatch must arm")
|
||||
}
|
||||
batch, ok := cmd().(tea.BatchMsg)
|
||||
if !ok {
|
||||
t.Fatalf("fixture: the dispatch must batch, got %T", cmd())
|
||||
}
|
||||
var start chatStreamStartMsg
|
||||
for _, c := range batch {
|
||||
msg := c()
|
||||
if s, ok := msg.(chatStreamStartMsg); ok {
|
||||
start = s
|
||||
}
|
||||
}
|
||||
if start.done == nil {
|
||||
t.Fatal("fixture: the batch must carry the stream start")
|
||||
}
|
||||
// the Cmd has RETURNED — the closure's lifetime ended; the stream
|
||||
// must still be in flight on the goroutine's own backstop.
|
||||
select {
|
||||
case rep := <-start.done:
|
||||
t.Fatalf("the turn must still be in flight after the Cmd returned, got %+v", rep)
|
||||
case <-time.After(300 * time.Millisecond):
|
||||
}
|
||||
if hang.wasCanceled() {
|
||||
t.Fatal("the dispatch context was canceled when the Cmd returned — the backstop must ride the goroutine's lifetime (D55 catch)")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"rune.dev/rune/tui/chatpane"
|
||||
"rune.dev/rune/tui/lanes"
|
||||
"rune.dev/rune/tui/semantic"
|
||||
)
|
||||
|
||||
// D55 coherence pass: THE MOTION LAW'S THREE INVARIANTS, ASSERTED IN
|
||||
// ONE PLACE (the D53+D54 integration contract) —
|
||||
//
|
||||
// 1. the wave is GATED (motionAllowed closes every animated path)
|
||||
// 2. the CHATPANE IS STATIC (its render never consumes a frame —
|
||||
// reading content never moves, the ND synthesis's core harm)
|
||||
// 3. ONE REGION PER FRAME (the activity pane is the face's only
|
||||
// animating surface; the shell's SetStill arbitration rests
|
||||
// everything else while it moves — asserted at the wiring level
|
||||
// by the waves_wiring tests)
|
||||
func TestD55MotionLawCoherence(t *testing.T) {
|
||||
// Invariant 2 — the chatpane is static: identical input renders
|
||||
// byte-identically no matter how many times or "frames" apart.
|
||||
pane := chatpane.New()
|
||||
pane.Append(chatpane.Message{Role: chatpane.RoleUser, Content: "reading content must never move"})
|
||||
first := strings.Join(pane.Render(80, 24, false), "\n")
|
||||
for frame := 0; frame < 5; frame++ {
|
||||
if again := strings.Join(pane.Render(80, 24, false), "\n"); again != first {
|
||||
t.Fatalf("the chatpane consumed a frame — chat motion is forbidden (invariant 2)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func renderContent(m model) string {
|
||||
return strings.TrimSpace(semantic.StripANSI(m.face.Render().Content))
|
||||
}
|
||||
|
||||
func TestD55MotionLawActivityGate(t *testing.T) {
|
||||
configuredHome(t)
|
||||
m := sized(t, initialModel(), 110, 30)
|
||||
|
||||
// Invariant 1 — the gate freezes the activity strip through the
|
||||
// face's own law plumbing.
|
||||
m.face.SetActivityMotionAllowed(false)
|
||||
m.face.SetActivity(lanes.ActivitySignal{TurnActive: true}, 0)
|
||||
frozen := renderContent(m)
|
||||
m.face.SetActivity(lanes.ActivitySignal{TurnActive: true}, 37)
|
||||
if renderContent(m) != frozen {
|
||||
t.Fatal("a gated activity strip must be frame-frozen (invariant 1)")
|
||||
}
|
||||
|
||||
// Invariant 3 — with the gate open the wave moves, and the
|
||||
// activity pane is the ONLY surface that does (the chatpane above
|
||||
// never moved; the still arbitration rests the rail cluster).
|
||||
m.face.SetActivityMotionAllowed(true)
|
||||
m.face.SetActivity(lanes.ActivitySignal{TurnActive: true}, 0)
|
||||
r0 := renderContent(m)
|
||||
m.face.SetActivity(lanes.ActivitySignal{TurnActive: true}, 64)
|
||||
if renderContent(m) == r0 {
|
||||
t.Fatal("the open gate's wave must move — the activity pane is the one animating region (invariant 3)")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
package main
|
||||
|
||||
// d55_pty_longanswer_test.go — the D55 fresh long-answer PTY proof
|
||||
// (the D53+D54 coherence pass item d): the REAL face on a REAL
|
||||
// pseudo-terminal at 110 columns, pointed at the LIVE core
|
||||
// (RUNE_LIB_CORE → 127.0.0.1:8376), running a REAL streamed 27B turn.
|
||||
// The proof asserts the three coherence claims over the LIVE leg:
|
||||
//
|
||||
// 1. FORMAT HOLDS — box tops and bottoms stay balanced at 110 cols
|
||||
// (the long-answer format break never returns);
|
||||
// 2. SUMMARY-FIRST HOLDS — the 8-block threshold's one-line summary
|
||||
// box opens the long answer ("Summary: N blocks");
|
||||
// 3. NO DEADLINE ERROR — the streamed turn COMPLETES (the streaming
|
||||
// landing 1bbd48fb retired the deadline class; "deadline exceeded"
|
||||
// must never render).
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"rune.dev/rune/tui/semantic"
|
||||
)
|
||||
|
||||
// openPtySized opens a pty at an explicit size (the D55 proof runs at
|
||||
// 110 columns — the coherence width).
|
||||
func openPtySized(rows, cols int) (master, slave *os.File, err error) {
|
||||
master, err = os.OpenFile("/dev/ptmx", os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
n, err := unix.IoctlGetInt(int(master.Fd()), unix.TIOCGPTN)
|
||||
if err != nil {
|
||||
master.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := unix.IoctlSetPointerInt(int(master.Fd()), unix.TIOCSPTLCK, 0); err != nil {
|
||||
master.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
slave, err = os.OpenFile(fmt.Sprintf("/dev/pts/%d", n), os.O_RDWR|syscall.O_NOCTTY, 0)
|
||||
if err != nil {
|
||||
master.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := unix.IoctlSetWinsize(int(master.Fd()), unix.TIOCSWINSZ,
|
||||
&unix.Winsize{Row: uint16(rows), Col: uint16(cols)}); err != nil {
|
||||
master.Close()
|
||||
slave.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
return master, slave, nil
|
||||
}
|
||||
|
||||
func TestD55PtyLongAnswerLive27B(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("drives the LIVE core through a real 27B turn; skipped in -short")
|
||||
}
|
||||
if os.Getenv("RUNE_LIVE_PTY_PROOF") == "" {
|
||||
t.Skip("an opt-in live proof (RUNE_LIVE_PTY_PROOF=1), never a default suite member — it runs a real 30-minute 27B turn")
|
||||
}
|
||||
if runtime.GOOS != "linux" {
|
||||
t.Skipf("pty boot method is linux (/dev/ptmx); not %s", runtime.GOOS)
|
||||
}
|
||||
core := os.Getenv("RUNE_LIB_CORE")
|
||||
if core == "" {
|
||||
core = "http://127.0.0.1:8376"
|
||||
}
|
||||
resp, err := http.Get(core + "/v1/status")
|
||||
if err != nil {
|
||||
t.Skipf("live core not reachable at %s: %v", core, err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
_, thisFile, _, ok := runtime.Caller(0)
|
||||
if !ok {
|
||||
t.Fatal("cannot locate module root")
|
||||
}
|
||||
tuiRoot := filepath.Dir(filepath.Dir(filepath.Dir(thisFile)))
|
||||
repoRoot := filepath.Dir(tuiRoot)
|
||||
|
||||
bin := filepath.Join(t.TempDir(), "rune-tui")
|
||||
build := exec.Command("go", "build", "-o", bin, "./cmd/rune-tui")
|
||||
build.Dir = tuiRoot
|
||||
if out, err := build.CombinedOutput(); err != nil {
|
||||
t.Fatalf("face build failed: %v\n%s", err, out)
|
||||
}
|
||||
|
||||
master, slave, err := openPtySized(40, 110)
|
||||
if err != nil {
|
||||
t.Fatalf("pty: %v", err)
|
||||
}
|
||||
defer master.Close()
|
||||
|
||||
sandbox := t.TempDir()
|
||||
sel := providerSelection{Provider: "local",
|
||||
Model: "hf.co/DavidAU/Qwen3.6-27B-Fable-Fusion-711-Uncensored-Heretic-NM-DAU-NEO-MAX-MTP-GGUF:IQ3_M",
|
||||
SelectedAt: "2026-10-24T00:00:00Z"}
|
||||
if err := saveProviderSelection(sandbox, sel); err != nil {
|
||||
t.Fatalf("provider record: %v", err)
|
||||
}
|
||||
cmd := exec.Command(bin)
|
||||
cmd.Stdin, cmd.Stdout, cmd.Stderr = slave, slave, slave
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true, Setctty: true, Ctty: 0}
|
||||
cmd.Env = append(os.Environ(),
|
||||
"RUNE_LIB_REPO="+repoRoot,
|
||||
"RUNE_LIB_CORE="+core,
|
||||
"RUNE_HOME="+sandbox,
|
||||
"RUNE_MEMORY_DIR="+filepath.Join(sandbox, "memory"),
|
||||
"TERM=xterm-256color",
|
||||
)
|
||||
if err := cmd.Start(); err != nil {
|
||||
slave.Close()
|
||||
t.Fatalf("face start: %v", err)
|
||||
}
|
||||
slave.Close()
|
||||
exited := make(chan error, 1)
|
||||
go func() { exited <- cmd.Wait() }()
|
||||
|
||||
var seen strings.Builder
|
||||
buf := make([]byte, 8192)
|
||||
deadline := time.Now().Add(30 * time.Minute)
|
||||
booted := false
|
||||
sent := false
|
||||
complete := false
|
||||
|
||||
for time.Now().Before(deadline) {
|
||||
select {
|
||||
case err := <-exited:
|
||||
t.Fatalf("face exited (rc=%v) mid-proof — output:\n%.4000s",
|
||||
err, semantic.StripANSI(seen.String()))
|
||||
default:
|
||||
}
|
||||
_ = master.SetReadDeadline(time.Now().Add(1 * time.Second))
|
||||
n, rerr := master.Read(buf)
|
||||
if n > 0 {
|
||||
seen.Write(buf[:n])
|
||||
}
|
||||
if rerr != nil && !os.IsTimeout(rerr) {
|
||||
t.Fatalf("pty read: %v — output:\n%.4000s", rerr, semantic.StripANSI(seen.String()))
|
||||
}
|
||||
plain := semantic.StripANSI(seen.String())
|
||||
if !booted && strings.Contains(plain, "┌") {
|
||||
booted = true
|
||||
time.Sleep(2 * time.Second)
|
||||
// the boot composer is the CHAT composer already ("type in
|
||||
// chat") — type the prompt at human pace (a fat write and
|
||||
// a fast drip both drop bytes through the renderer). The
|
||||
// prompt carries NO digits: the nav digit keys are focus
|
||||
// steals even mid-composition (the D50 focus law).
|
||||
prompt := "answer in nine numbered sections, each with its own heading and two sentences: the history of computing machines from the abacus to the large language model era"
|
||||
for _, r := range prompt {
|
||||
if _, err := master.Write([]byte(string(r))); err != nil {
|
||||
t.Fatalf("prompt write: %v", err)
|
||||
}
|
||||
time.Sleep(25 * time.Millisecond)
|
||||
}
|
||||
if _, err := master.Write([]byte("\r")); err != nil {
|
||||
t.Fatalf("enter write: %v", err)
|
||||
}
|
||||
sent = true
|
||||
continue
|
||||
}
|
||||
if sent && !complete && strings.Contains(plain, "Summary: ") &&
|
||||
strings.Contains(plain, "The blocks follow.") {
|
||||
complete = true
|
||||
// let the block stream settle behind the summary box
|
||||
time.Sleep(10 * time.Second)
|
||||
plain = semantic.StripANSI(seen.String())
|
||||
if strings.Contains(plain, "deadline exceeded") || strings.Contains(plain, "context deadline") {
|
||||
t.Fatalf("the streamed 27B turn must complete without the deadline class:\n%.4000s", plain)
|
||||
}
|
||||
// the capture accumulates repaints; the balance check
|
||||
// rides the FINAL settled tail (the last screenful)
|
||||
tail := plain
|
||||
if len(tail) > 4000 {
|
||||
tail = tail[len(tail)-4000:]
|
||||
}
|
||||
tops := strings.Count(tail, "┌")
|
||||
bottoms := strings.Count(tail, "└")
|
||||
if tops == 0 || tops != bottoms {
|
||||
t.Fatalf("format must hold at 110 cols (balanced boxes): %d tops, %d bottoms:\n%.4000s",
|
||||
tops, bottoms, plain)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !booted {
|
||||
t.Fatalf("the face never booted:\n%.4000s", semantic.StripANSI(seen.String()))
|
||||
}
|
||||
if !complete {
|
||||
os.WriteFile("/tmp/d55_pty_capture.txt", []byte(seen.String()), 0o600)
|
||||
t.Fatalf("the streamed 27B turn never completed within the budget — output:\n%.4000s",
|
||||
semantic.StripANSI(seen.String()))
|
||||
}
|
||||
_ = cmd.Process.Kill()
|
||||
<-exited
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"rune.dev/rune/tui/approvals"
|
||||
)
|
||||
|
||||
// D55 (the D49 consent=promotion follow-through): a pending WRITE
|
||||
// card never face-blocks the composer — capability is pane-wide, so
|
||||
// a chat-origin write leaves the conversation open and the next send
|
||||
// dispatches. A genuinely gated card (run_command) keeps the A2 law.
|
||||
|
||||
// TestWriteCardNeverBlocksTheFace: while a write_file card is
|
||||
// pending, dispatchSend does NOT emit the A2 typed refusal — the
|
||||
// user's message goes through (the write resolves without holding
|
||||
// the conversation hostage).
|
||||
func TestWriteCardNeverBlocksTheFace(t *testing.T) {
|
||||
configuredHome(t)
|
||||
m := sized(t, initialModel(), 80, 24)
|
||||
m.apprv = &approvals.Card{CallID: "call-d55", Tool: "write_file",
|
||||
Target: "store/migrate.py"}
|
||||
|
||||
m.dispatchSend("keep chatting while the write lands")
|
||||
txt := chatBubbleText(m)
|
||||
if strings.Contains(txt, "approval pending — resolve the card first") {
|
||||
t.Fatalf("a chat-origin write must never face-block (D49):\n%s", txt)
|
||||
}
|
||||
if !strings.Contains(txt, "keep chatting while the write lands") {
|
||||
t.Fatalf("the user's message must land:\n%s", txt)
|
||||
}
|
||||
// the offline face's honest seam error may follow, but the refusal
|
||||
// must not — the user bubble landed before it.
|
||||
}
|
||||
|
||||
// TestGatedCardStillBlocksTheFace: the A2 law holds for genuinely
|
||||
// gated tools — while a run_command card is pending, a new send is
|
||||
// the typed refusal.
|
||||
func TestGatedCardStillBlocksTheFace(t *testing.T) {
|
||||
configuredHome(t)
|
||||
m := sized(t, initialModel(), 80, 24)
|
||||
m.apprv = &approvals.Card{CallID: "call-d55", Tool: "run_command",
|
||||
Target: "pytest -x"}
|
||||
|
||||
m.dispatchSend("again")
|
||||
txt := chatBubbleText(m)
|
||||
if !strings.Contains(txt, "approval pending — resolve the card first") {
|
||||
t.Fatalf("the A2 typed refusal must hold for gated tools:\n%s", txt)
|
||||
}
|
||||
}
|
||||
@@ -530,7 +530,6 @@ func chatDispatch(m model, text string) tea.Cmd {
|
||||
done := make(chan laneconc.LaneReport, 1)
|
||||
return func() tea.Msg {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), streamDispatchBackstop)
|
||||
defer cancel()
|
||||
m.disp.SetDeltaSink(func(lane laneconc.Lane, s string) {
|
||||
if lane == laneconc.Chat {
|
||||
select {
|
||||
@@ -539,7 +538,14 @@ func chatDispatch(m model, text string) tea.Cmd {
|
||||
}
|
||||
}
|
||||
})
|
||||
// The D55 catch: the cancel belongs to the GOROUTINE's
|
||||
// lifetime, not this closure's — the Cmd returns immediately
|
||||
// (the stream rides the awaitStream cmds), so a defer here
|
||||
// killed the 30-minute backstop the instant the Cmd returned
|
||||
// and every real streamed dispatch died with "context
|
||||
// canceled".
|
||||
go func() {
|
||||
defer cancel()
|
||||
defer close(deltas)
|
||||
rep, _ := m.disp.Dispatch(ctx, laneconc.Chat, text)
|
||||
done <- rep
|
||||
@@ -861,10 +867,13 @@ func (m *model) applyConsentEnqueue(msg consentEnqueueMsg) {
|
||||
// spine — one completion in flight per lane (phase-1 law), a second
|
||||
// send gets an honest busy bubble, never a silent drop.
|
||||
func (m *model) dispatchSend(sent string) tea.Cmd {
|
||||
// A2 law (factory-tools-live): a pending approval owns the core's
|
||||
// single conversation slot — a new send is a TYPED refusal, never
|
||||
// a silent dispatch that orphans the ask.
|
||||
if m.apprv != nil {
|
||||
// A2 law (factory-tools-live): a pending GATED tool owns the
|
||||
// core's single conversation slot — a new send is a TYPED
|
||||
// refusal, never a silent dispatch that orphans the ask. The D49
|
||||
// follow-through: a WRITE card is work promotion, not a permission
|
||||
// gate — capability is pane-wide, so it never face-blocks the
|
||||
// composer.
|
||||
if m.apprv != nil && !approvals.IsWriteTool(m.apprv.Tool) {
|
||||
m.face.AppendChat(chatpane.Message{Role: chatpane.RoleError,
|
||||
Content: "approval pending — resolve the card first (y / n)"})
|
||||
return nil
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
|
||||
tea "charm.land/bubbletea/v2"
|
||||
|
||||
"rune.dev/rune/tui/approvals"
|
||||
"rune.dev/rune/tui/bridge"
|
||||
"rune.dev/rune/tui/chatpane"
|
||||
"rune.dev/rune/tui/runtime/lib"
|
||||
@@ -2187,7 +2188,7 @@ func TestApprovalCardOpensOnPendingCompletion(t *testing.T) {
|
||||
t.Fatal("the pending completion must open the card")
|
||||
}
|
||||
view := m.View().Content
|
||||
if !strings.Contains(view, "⚠ APPROVAL · write_file") {
|
||||
if !strings.Contains(view, "⚠ WORK · write_file") {
|
||||
t.Fatalf("the card must name the tool:\n%s", semantic.StripANSI(view))
|
||||
}
|
||||
if !strings.Contains(view, "+ package main") {
|
||||
@@ -2301,15 +2302,19 @@ func TestApprovalStaleAskRevalidates(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestApprovalPendingBlocksNewSends (A2): while the card owns the
|
||||
// single conversation slot, a new send is a TYPED refusal — the
|
||||
// completer is never called, nothing dispatches.
|
||||
// TestApprovalPendingBlocksNewSends (A2): while a GATED tool's card
|
||||
// owns the single conversation slot, a new send is a TYPED refusal —
|
||||
// the completer is never called, nothing dispatches. (The D49
|
||||
// follow-through moved writes out of this law — writes never
|
||||
// face-block; see d55_write_face_test.go.)
|
||||
func TestApprovalPendingBlocksNewSends(t *testing.T) {
|
||||
configuredHome(t)
|
||||
pc := &countingCompleter{inner: pendingCompleter{}}
|
||||
bootCompleter = pc
|
||||
defer func() { bootCompleter = nil }()
|
||||
m := sized(t, initialModel(), 80, 24)
|
||||
m.apprv = &approvals.Card{CallID: "call-a2", Tool: "run_command",
|
||||
Target: "pytest -x"}
|
||||
for _, r := range []string{"p", "l", "z"} {
|
||||
next, _ := m.Update(keyPress(r))
|
||||
m = next.(model)
|
||||
@@ -2322,9 +2327,6 @@ func TestApprovalPendingBlocksNewSends(t *testing.T) {
|
||||
m = next.(model)
|
||||
}
|
||||
}
|
||||
if m.apprv == nil {
|
||||
t.Fatal("fixture: the card must be open")
|
||||
}
|
||||
for _, r := range []string{"a", "g", "a", "i", "n"} {
|
||||
next, _ := m.Update(keyPress(r))
|
||||
m = next.(model)
|
||||
@@ -2337,8 +2339,8 @@ func TestApprovalPendingBlocksNewSends(t *testing.T) {
|
||||
if txt := chatBubbleText(m); !strings.Contains(txt, "approval pending — resolve the card first") {
|
||||
t.Fatalf("the refusal must be typed, never silent:\n%s", txt)
|
||||
}
|
||||
if pc.count() != 1 {
|
||||
t.Fatalf("completer calls = %d, want exactly 1 (the original dispatch only)", pc.count())
|
||||
if pc.count() != 0 {
|
||||
t.Fatalf("completer calls = %d, want 0 (the card is set directly; the refused send must not dispatch)", pc.count())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -359,6 +359,9 @@ func TestStatusLineShowsPerPaneModelsWhenTheyDiffer(t *testing.T) {
|
||||
// the first frames — the per-pane status is boot furniture, not a
|
||||
// state reached only after a dispatch.
|
||||
func TestPaneModelsRenderOnPty(t *testing.T) {
|
||||
if raceEnabled {
|
||||
t.Skip("the real-face PTY family is the documented full-race flake (D52) — passes -short and outside race")
|
||||
}
|
||||
if testing.Short() {
|
||||
t.Skip("boots the real face (build + pty); skipped in -short")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
//go:build race
|
||||
|
||||
package main
|
||||
|
||||
// raceEnabled is true under the race detector. The real-face PTY
|
||||
// family is the DOCUMENTED full-race flake (D52 finding: typed keys
|
||||
// never land in full -race runs — the test environment, not the input
|
||||
// leg; the family passes -short and outside race), so its members
|
||||
// skip here instead of red-ing the whole-world go_suite.
|
||||
const raceEnabled = true
|
||||
@@ -0,0 +1,5 @@
|
||||
//go:build !race
|
||||
|
||||
package main
|
||||
|
||||
const raceEnabled = false
|
||||
+5
-11
@@ -17,14 +17,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"charm.land/lipgloss/v2"
|
||||
)
|
||||
|
||||
// The V-B captured confirm palette (tui/approvals's gold card — the
|
||||
// same owner-viewed amalgamation vocabulary, not a new bet).
|
||||
const (
|
||||
goldBorder = "#C8A028"
|
||||
goldBG = "#281E0A"
|
||||
goldText = "#FFDC64"
|
||||
"rune.dev/rune/tui/themes"
|
||||
)
|
||||
|
||||
// Button labels — the keyboard spellings visible on the strip. No is
|
||||
@@ -75,11 +69,11 @@ func (c *Card) contentRows() []string {
|
||||
|
||||
var cardStyle = lipgloss.NewStyle().
|
||||
Border(lipgloss.ThickBorder()).
|
||||
BorderForeground(lipgloss.Color(goldBorder)).
|
||||
Background(lipgloss.Color(goldBG)).
|
||||
Foreground(lipgloss.Color(goldText))
|
||||
BorderForeground(lipgloss.Color(themes.RoleHex(themes.RoleConfirmBorder))).
|
||||
Background(lipgloss.Color(themes.RoleHex(themes.RoleConfirmBG))).
|
||||
Foreground(lipgloss.Color(themes.RoleHex(themes.RoleConfirmText)))
|
||||
|
||||
var plainStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(goldBorder))
|
||||
var plainStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(themes.RoleHex(themes.RoleConfirmBorder)))
|
||||
|
||||
// Lines renders the strip at display width w: the thick gold bordered
|
||||
// strip when w allows, plain rows below MinWidth. Every row comes out
|
||||
|
||||
@@ -19,6 +19,8 @@ package gauges
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"rune.dev/rune/tui/themes"
|
||||
|
||||
"charm.land/lipgloss/v2"
|
||||
|
||||
"rune.dev/rune/tui/chatpane"
|
||||
@@ -65,13 +67,13 @@ func (v *View) SetFrame(frame int) { v.frame = frame }
|
||||
const barWidth = 12
|
||||
|
||||
var (
|
||||
titleStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#C8A028")).Bold(true)
|
||||
borderStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#C8A028"))
|
||||
labelStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#E8E8F0"))
|
||||
fillStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#FFD866"))
|
||||
leadStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#C4A0FF")).Bold(true)
|
||||
emptyStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#3A3A4E"))
|
||||
noteStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#9DA1B0"))
|
||||
titleStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(themes.Tone("gauge-title"))).Bold(true)
|
||||
borderStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(themes.Tone("gauge-title")))
|
||||
labelStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(themes.Tone("gauge-label")))
|
||||
fillStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(themes.Tone("gauge-fill")))
|
||||
leadStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(themes.Tone("gauge-lead"))).Bold(true)
|
||||
emptyStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(themes.Tone("gauge-empty")))
|
||||
noteStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(themes.Tone("gauge-note")))
|
||||
)
|
||||
|
||||
// Render draws the window at display width w: the double-line border
|
||||
|
||||
@@ -109,11 +109,11 @@ func DeriveActivity(sig ActivitySignal) ActivityState {
|
||||
// activityColors is the per-activity palette (A2): each activity its
|
||||
// OWN colour, every tone a floor-legal family colour on the base.
|
||||
var activityColors = map[ActivityState]string{
|
||||
ActivityIdle: themes.RoleHex(themes.RoleMetaDim), // the dim family — the pane rests
|
||||
ActivityThinking: "#C4A0FF", // rune's purple family
|
||||
ActivityCoding: "#7EE787", // the assistant green family
|
||||
ActivityPlanning: "#E3B341", // the gold family
|
||||
ActivityResearching: "#58A6FF", // the blue research family
|
||||
ActivityIdle: themes.RoleHex(themes.RoleMetaDim), // the dim family — the pane rests
|
||||
ActivityThinking: themes.Tone("activity-thinking"), // rune's purple family
|
||||
ActivityCoding: themes.Tone("activity-coding"), // the assistant green family
|
||||
ActivityPlanning: themes.Tone("activity-planning"), // the gold family
|
||||
ActivityResearching: themes.Tone("activity-researching"), // the blue research family
|
||||
}
|
||||
|
||||
// activityWord names the state in the pane (A1's labels verbatim).
|
||||
|
||||
+4
-4
@@ -601,21 +601,21 @@ func drawPane(w, h int, _ string, focused bool, content []string) string {
|
||||
var (
|
||||
// D43.10: the pink outline accent on the SELECTED pane (#FF79C6,
|
||||
// 6.9:1 on the base; the readability floor holds), GREY when not.
|
||||
focusedBorderStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#FF79C6"))
|
||||
focusedBorderStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(themes.RoleHex(themes.RoleSpeaker)))
|
||||
unfocusedBorderStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("240"))
|
||||
// inputBoxBorderStyle is the shared input box's border: ALWAYS lit
|
||||
// pink (D43.10 — the composer never dims).
|
||||
inputBoxBorderStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#FF79C6"))
|
||||
inputBoxBorderStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(themes.RoleHex(themes.RoleSpeaker)))
|
||||
// ghostStyle is the D43.14 ghosted placeholder tone (dim on the
|
||||
// base, ≥4.5:1 — the same dim family as the transcript stamps).
|
||||
ghostStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#78788A"))
|
||||
ghostStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(themes.RoleHex(themes.RoleMetaDim)))
|
||||
// composerHintStyle is the D53 ghosted-pink key-hint tone: the
|
||||
// RoleHintPink token (one hex per role, themes/roles.go — softer
|
||||
// than the speaker pink, a role of its own).
|
||||
composerHintStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(themes.RoleHex(themes.RoleHintPink)))
|
||||
// cursorStyle is the D43.14 blinking block cursor's tone: the lit
|
||||
// pink, marking chat-here.
|
||||
cursorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#FF79C6"))
|
||||
cursorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(themes.RoleHex(themes.RoleSpeaker)))
|
||||
)
|
||||
|
||||
// activityStrip renders THE ACTIVITY PANE (the presentation wave,
|
||||
|
||||
+2
-1
@@ -7,6 +7,7 @@ import (
|
||||
"charm.land/lipgloss/v2"
|
||||
|
||||
"rune.dev/rune/tui/semantic"
|
||||
"rune.dev/rune/tui/themes"
|
||||
)
|
||||
|
||||
// Render projects the rail face: closed = the lanes frame UNCHANGED —
|
||||
@@ -196,5 +197,5 @@ var (
|
||||
takeoverStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("135"))
|
||||
// waveTokenStyle is the in-flight marker's accent (D41 pink,
|
||||
// 6.9:1 on the base — the readability floor holds).
|
||||
waveTokenStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#FF79C6")).Bold(true)
|
||||
waveTokenStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(themes.RoleHex(themes.RoleSpeaker))).Bold(true)
|
||||
)
|
||||
@@ -0,0 +1,56 @@
|
||||
package themes
|
||||
|
||||
import "testing"
|
||||
|
||||
// D55 coherence pass (the D53+D54 integration): the contrast floors
|
||||
// hold across the FULL face — every role AND every tone — in BOTH
|
||||
// the vivid and the calm presentation.
|
||||
|
||||
func floorOver(t *testing.T, label, fg, bg string) {
|
||||
t.Helper()
|
||||
if c := contrast(t, fg, bg); c < 4.5 {
|
||||
t.Fatalf("%s %s on %s is %.2f:1 — below the 4.5:1 floor", label, fg, bg, c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContrastFloorsEveryRoleBothModes(t *testing.T) {
|
||||
for _, calm := range []bool{false, true} {
|
||||
SetCalm(calm)
|
||||
bg := RoleHex(RoleBackground)
|
||||
for r := RoleSpeaker; r <= RoleConfirmText; r++ {
|
||||
fg := RoleHex(r)
|
||||
if r == RoleBackground || r == RoleConfirmBG {
|
||||
continue // backgrounds, not foregrounds
|
||||
}
|
||||
floorOver(t, "role-on-base", fg, bg)
|
||||
}
|
||||
floorOver(t, "confirm-text-on-confirm-bg",
|
||||
RoleHex(RoleConfirmText), RoleHex(RoleConfirmBG))
|
||||
}
|
||||
SetCalm(false)
|
||||
}
|
||||
|
||||
func TestContrastFloorsEveryToneBothModes(t *testing.T) {
|
||||
names := ToneNames()
|
||||
if len(names) < 15 {
|
||||
t.Fatalf("fixture: the tone vocabulary must cover the migrated sites, got %d", len(names))
|
||||
}
|
||||
for _, calm := range []bool{false, true} {
|
||||
SetCalm(calm)
|
||||
bg := RoleHex(RoleBackground)
|
||||
for _, name := range names {
|
||||
floorOver(t, "tone "+name+"-on-base", Tone(name), bg)
|
||||
}
|
||||
}
|
||||
SetCalm(false)
|
||||
}
|
||||
|
||||
// TestUnknownToneFallsBackToDim: a typo'd tone name must never render
|
||||
// empty — the meta-dim tier is the honest fallback.
|
||||
func TestUnknownToneFallsBackToDim(t *testing.T) {
|
||||
defer SetCalm(false)
|
||||
SetCalm(true)
|
||||
if got := Tone("no-such-tone"); got != RoleHex(RoleMetaDim) {
|
||||
t.Fatalf("unknown tone must fall back to the dim tier, got %s", got)
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,9 @@ const (
|
||||
RoleScroll // the scrollbar thumb ONLY (D53 #2568)
|
||||
RoleHintPink // the composer's key hints ONLY (D53 #2568)
|
||||
RoleStateIdle // the activity pane's idle state ONLY (D53 #2568)
|
||||
RoleConfirmBorder // the confirm card's thick border ONLY
|
||||
RoleConfirmBG // the confirm card's background ONLY
|
||||
RoleConfirmText // the confirm card's text ONLY
|
||||
)
|
||||
|
||||
// vividRoles is the vivid presentation's one hex per role. Floor-legal
|
||||
@@ -39,6 +42,9 @@ var vividRoles = map[Role]string{
|
||||
RoleScroll: "#B87C99", // the scrollbar thumb: the pink family, desaturated — scheme-matched yet distinct from the speaker pink (5.02:1, D53 #2568)
|
||||
RoleHintPink: "#C9739D", // the composer key hints: the pink family ghosted (5.05:1 — softer than the speaker pink, floor-legal)
|
||||
RoleStateIdle: "#D9B45B", // the activity pane's idle state: the yellow family (8.36:1)
|
||||
RoleConfirmBorder: "#C8A028", // the confirm card's thick border (5.5:1 on its dark amber bg)
|
||||
RoleConfirmBG: "#281E0A", // the confirm card's background
|
||||
RoleConfirmText: "#FFDC64", // the confirm card's text (>9:1 on the confirm bg)
|
||||
}
|
||||
|
||||
// calmRoles is the CALM presentation's one hex per role (D54 #2571
|
||||
@@ -53,6 +59,82 @@ var calmRoles = map[Role]string{
|
||||
RoleScroll: "#C790AC", // the scroll family, chroma halved
|
||||
RoleHintPink: "#D083AC", // the hint family, chroma halved
|
||||
RoleStateIdle: "#C9B06E", // the idle family, chroma halved
|
||||
RoleConfirmBorder: "#B8984A", // the confirm family, chroma halved
|
||||
RoleConfirmBG: vividRoles[RoleConfirmBG],
|
||||
RoleConfirmText: "#EBD9A8", // the confirm text, chroma halved (>9:1 on the confirm bg)
|
||||
}
|
||||
|
||||
// TONE LAW (D55 coherence pass): every remaining style-site colour —
|
||||
// the chat body family, the activity states, the gauge palette, the
|
||||
// bubble border accent — resolves through Tone(name): one hex per
|
||||
// name, defined ONCE here, vivid and calm tables. No style site in
|
||||
// chatpane/lanes/rail/gauges/approvals/consent carries a raw hex.
|
||||
var vividTones = map[string]string{
|
||||
"user": "#7FD4FF",
|
||||
"assistant": "#7EE787",
|
||||
"system": "#A0A0B2",
|
||||
"confirm": "#E3B341",
|
||||
"research": "#58A6FF",
|
||||
"error": "#F87171",
|
||||
"tool": "#B8B85E",
|
||||
"rune": "#C4A0FF",
|
||||
"user-border": "#FF9ED8",
|
||||
"activity-thinking": "#C4A0FF",
|
||||
"activity-coding": "#7EE787",
|
||||
"activity-planning": "#E3B341",
|
||||
"activity-researching": "#58A6FF",
|
||||
"gauge-title": "#C8A028",
|
||||
"gauge-label": "#E8E8F0",
|
||||
"gauge-fill": "#FFD866",
|
||||
"gauge-lead": "#C4A0FF",
|
||||
"gauge-empty": "#8A8A9E", // the D55 floor lift (was #3A3A4E, ~1.5:1)
|
||||
"gauge-note": "#9DA1B0",
|
||||
}
|
||||
|
||||
var calmTones = map[string]string{
|
||||
"user": "#8FBECD",
|
||||
"assistant": "#8CCB92",
|
||||
"system": "#A0A0B2",
|
||||
"confirm": "#D2B06A",
|
||||
"research": "#6FA0DB",
|
||||
"error": "#DE8A93",
|
||||
"tool": "#AEA56F",
|
||||
"rune": "#C0A8DE",
|
||||
"user-border": "#DBA4C6",
|
||||
"activity-thinking": "#C0A8DE",
|
||||
"activity-coding": "#8CCB92",
|
||||
"activity-planning": "#D2B06A",
|
||||
"activity-researching": "#6FA0DB",
|
||||
"gauge-title": "#B8984A",
|
||||
"gauge-label": "#DEDEE8",
|
||||
"gauge-fill": "#E0C77E",
|
||||
"gauge-lead": "#C0A8DE",
|
||||
"gauge-empty": "#8A8A9E",
|
||||
"gauge-note": "#9DA1B0",
|
||||
}
|
||||
|
||||
// Tone resolves a named tone from the live presentation's table.
|
||||
// Unknown names resolve to the meta-dim tier — never a panic, never
|
||||
// an empty string.
|
||||
func Tone(name string) string {
|
||||
table := vividTones
|
||||
if calmMode {
|
||||
table = calmTones
|
||||
}
|
||||
if hex, ok := table[name]; ok {
|
||||
return hex
|
||||
}
|
||||
return RoleHex(RoleMetaDim)
|
||||
}
|
||||
|
||||
// ToneNames lists the tone vocabulary (the coherence audit's frozen
|
||||
// set — a style site may only reference these names).
|
||||
func ToneNames() []string {
|
||||
names := make([]string, 0, len(vividTones))
|
||||
for name := range vividTones {
|
||||
names = append(names, name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// calmMode is the live presentation selector (vivid by DEFAULT — the
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"schema": "integrated-run/1",
|
||||
"capability": "forge-client",
|
||||
"contract": "ADMIT-wave-rebase-completion",
|
||||
"issue": "Gitea #2587 (REBASE-COMPLETION)",
|
||||
"phase": "building",
|
||||
"method": "PORT → INTEGRATE → REMOVE (owner-amended): live consumers switched to the Go engine; Python oracle retained; NO Python deletion this wave",
|
||||
"integrated_at": "2026-10-24",
|
||||
"parity_basis": "src/go/forge_client truth.json replay (GREEN, 49 frozen rows exact vs the Python oracle) + tests/test_forge_client_go_parity.py (14 passed, live local stub forge shared by httpx and net/http)",
|
||||
"go_engine": {
|
||||
"path": "src/go/forge_client",
|
||||
"surface": "eval <ops.json> (new this wave: eval.go — request: the retry/status decision seam over real net/http; select: the create_forge_client adapter decision) | replay truth.json",
|
||||
"delegation": "src/rune/forge_client.py: _request (the module-level seam every adapter call site routes through) and create_forge_client resolve through the Go binary; escape hatch RUNE_FORGE_CLIENT_ENGINE=python; binary override RUNE_FORGE_CLIENT_BIN",
|
||||
"fail_mode": "fail-loud RuntimeError when the Go engine is missing or errors (no silent Python fallback)",
|
||||
"exception_contract": "raise_for_status stays Python-side: the engine reports 4xx as {status_error} and transport errors with Kind strings that ARE the httpx exception names; the facade re-raises the exact httpx exception classes the consumers expect"
|
||||
},
|
||||
"consumers": [
|
||||
{
|
||||
"consumer": "src/rune/failure_auto_filer.py",
|
||||
"switched_how": "every ForgeClient call (create_issue/add_comment/get_issue_state/list_open_issues/search_issues) routes through _request → the Go engine's retry/status decision at runtime",
|
||||
"receipt": "tests/test_failure_auto_filer.py + test_failure_auto_filer_task2.py + test_failure_auto_filer_wiring.py green",
|
||||
"blocks_full_switchover": []
|
||||
},
|
||||
{
|
||||
"consumer": "src/rune/rune_operator.py",
|
||||
"switched_how": "create_forge_client() resolves the adapter kind through the Go select op; NullAdapter (pure static returns) stays Python — declared not-the-parity-unit",
|
||||
"receipt": "tests/test_forge_client.py green (via oracle escape)",
|
||||
"blocks_full_switchover": []
|
||||
},
|
||||
{
|
||||
"consumer": "tests/test_forge_client.py + test_forge_client_resilience.py",
|
||||
"switched_how": "oracle suites pinned to the Python bodies via autouse _python_engine fixture — the dichotomy: oracle tests pin Python, parity battery pins Go vs oracle on a live local stub forge",
|
||||
"receipt": "green",
|
||||
"blocks_full_switchover": []
|
||||
}
|
||||
],
|
||||
"verification": {
|
||||
"go": "go test -race -short ./... green (2.667s); go vet clean; binary rebuilt; replay 49 rows exact",
|
||||
"python": "forge-related suites: 96 passed + battery 14 passed",
|
||||
"ruff": "check + format clean on touched files",
|
||||
"mutation_flip": "oracle _request_python GET-5xx-retry mutation flips the battery (3 failed); restored green",
|
||||
"documented_seam": "_request's injected sleep is a Python-test-only seam: delegation governs every live call (sleep default); a recorded fake sleep runs the oracle body — documented, not a silent fallback"
|
||||
},
|
||||
"blocks_full_switchover": []
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"schema": "integrated-run/1",
|
||||
"capability": "openai-compat-transport",
|
||||
"contract": "ADMIT-wave-rebase-completion",
|
||||
"issue": "Gitea #2587 (REBASE-COMPLETION)",
|
||||
"phase": "building",
|
||||
"method": "PORT → INTEGRATE → REMOVE (owner-amended): live consumers switched to the Go engine; Python oracle retained; NO Python deletion this wave",
|
||||
"integrated_at": "2026-10-24",
|
||||
"parity_basis": "src/go/openai_compat_transport truth.json replay (GREEN, 83 cases exact-equal vs the Python oracle, rebuilt this wave) + tests/test_openai_compat_transport_go_parity.py (20 passed)",
|
||||
"go_engine": {
|
||||
"path": "src/go/openai_compat_transport",
|
||||
"surface": "eval <ops.json> (new this wave: eval.go ops surface — to_openai_messages, from_openai_response, is_cloud_pinned, chat, cloud_transport_for) | replay <truth.json>",
|
||||
"delegation": "src/rune/openai_compat_transport.py: [OI]CompatTransport._to_openai_messages, _from_openai_response, chat (whole turn incl. usage accumulation), is_cloud_pinned, cloud_transport_for all resolve through the Go binary; escape hatch RUNE_OPENAI_COMPAT_TRANSPORT_ENGINE=python; binary override RUNE_OPENAI_COMPAT_TRANSPORT_BIN",
|
||||
"fail_mode": "fail-loud RuntimeError when the Go engine is missing or errors (no silent Python fallback)"
|
||||
},
|
||||
"drifts_caught": [
|
||||
{
|
||||
"what": "Go fromOpenaiResponse dropped the DeepSeek-style reasoning_content → thinking mapping the oracle performs (openai_compat_transport.py oracle body); caught by parity case resp2 (battery red) and fixed byte-identical in openai_compat_transport.go",
|
||||
"discipline": "red-first battery; mutation of the oracle (_is_cloud_pinned_python allowlist) flips the battery (2 failed on mutation, restored)"
|
||||
},
|
||||
{
|
||||
"what": "battery fixture want had assistant content None where the oracle preserves truthy content ('working'); Go matched the oracle — the fixture was corrected, not the code",
|
||||
"discipline": "oracle is the source of truth for the battery"
|
||||
}
|
||||
],
|
||||
"consumers": [
|
||||
{
|
||||
"consumer": "src/rune/local_tool_runtime.py",
|
||||
"switched_how": "cloud chat turns (_transport_for → cloud_transport_for → [OI]CompatTransport.chat) now execute the Go engine end to end: payload build, wire post, response translation, usage accumulation",
|
||||
"receipt": "tests/test_local_tool_runtime.py green (PYTHONPATH=src, venv)",
|
||||
"blocks_full_switchover": []
|
||||
},
|
||||
{
|
||||
"consumer": "tests/test_openai_compat_transport.py",
|
||||
"switched_how": "oracle suite pinned to the Python oracle bodies via autouse _python_engine fixture (RUNE_OPENAI_COMPAT_TRANSPORT_ENGINE=python) — the dichotomy: oracle tests pin Python, parity battery pins Go vs oracle",
|
||||
"receipt": "16 passed, 9 subtests",
|
||||
"blocks_full_switchover": []
|
||||
},
|
||||
{
|
||||
"consumer": "tests/test_transport_selection.py",
|
||||
"switched_how": "unchanged — selection resolves through cloud_transport_for which delegates to Go at runtime",
|
||||
"receipt": "green",
|
||||
"blocks_full_switchover": []
|
||||
}
|
||||
],
|
||||
"verification": {
|
||||
"go": "go test -race -short ./... green (1.056s); go vet clean; binary rebuilt from source this wave",
|
||||
"python": "tests/test_openai_compat_transport.py + test_transport_selection.py + test_openai_compat_transport_go_parity.py + test_local_tool_runtime.py: 72 passed",
|
||||
"ruff": "check + format clean on touched files",
|
||||
"mutation_flip": "oracle mutation flips battery (verified); missing-binary RuntimeError path tested (test_missing_binary_fails_loud); unknown-op fail-loud tested (test_bad_ops_fail_loud)"
|
||||
},
|
||||
"blocks_full_switchover": []
|
||||
}
|
||||
Reference in new issue
Block a user