Files
rune/scripts/admit_wave_apikey_migration.py
T
Nick 1e85064944 feat(tui+runtime): chat-live P1 — the face talks through the real core
PTY PROOF: clean RUNE_HOME -> gate card (no provider configured) ->
model list -> digit selects (record persisted) -> typed message -> real
Ollama reply lands complete in the lane -> checkpoint holds user+
assistant msg_ids. The face talks.

F1-F4 + mutex: channel honoring (chat lands in chat, not task), stale
canary stub killed + supervised real core on 8376 (ensure_core.py,
identity sanity), timeout budget >=330s all three legs, bridge polls
channel chat beside task. Thread-safety: dispatch_lock mutex (canary
overlap probe was RED pre-fix, 35/0 post, 4-way concurrency pytest,
rogue-mutant teeth).

8-role bubble structure per the design-intent contracts: user right/
assistant left, thinking collapsed, dark bg. Fresh-state: gate card
with real model list, no default, record persisted, chat gated until
selection. Full module green (32 pkgs + 4 with new content), race-
clean. Identity line: runtime lib:connected.

KNOWN: chatpane contrast fails WCAG on the dark bg (1.35:1 assistant
invisible) — visual round build wave lifts every role to >=4.5:1 per
direction; identity line clips chat token at 100 cols; ordinary
sentences can't be typed on the chat pane while chain keys r/t/p/y/
route to chrome — the selection law's pending-input surfaces need
unbinding in the visual round. These are DESIGN findings, not defects.
2026-10-18 01:41:10 +01:00

137 lines
6.0 KiB
Python

"""Admission dispatch for the wave-apikey-migration owner-critical wave
(docs/factory/spawn-queue.md#B-APIKEY-MIGRATION; approval_ref
'user:wave-apikey-migration dispatch').
classify + activate + admit (the headless scaffolding entry law,
tests/test_construction_admission.py pattern): lossless port of ALL API
keys and saved connections from mimo code into rune — the mimo/opencode
config stores (~/.config/mimocode/mimocode.jsonc,
~/.local/share/mimocode/auth.json, ~/.config/opencode/opencode.json|c +
secrets/, ~/.local/share/opencode/auth.json, ~/.rune/model_state.json)
are scanned, mapped onto rune's provider format, and landed in the
provider store with credentials in the secret store per the security law
(OS keyring / Secret Service / encrypted fallback; never plaintext in
config, UI, logs). Frozen battery tests/test_apikey_migration.py +
tests/test_secret_store.py + tests/test_provider_registry_secrets.py
(red-first: all three ran RED against the empty tree before
implementation). Verification report: every distinct key found in mimo
present in rune, counts match, no plaintext in config/report.
Research exemption: owner-directive (owner-critical migration), declared
in docs/factory/research-registry.json slug apikey-migration.
"""
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 = (
"API key migration (wave-apikey-migration): port ALL API keys and saved "
"connections from mimo code into rune, lossless, same semantics, "
"respected exactly — scripts/migrate_api_keys.py scans the mimo/opencode "
"config stores, extracts provider/key/model/endpoint pairs, maps them to "
"rune's provider format, and lands credentials in the secret store per "
"the security law (OS keyring via python-keyring or the freedesktop "
"Secret Service via secret-tool, AES-GCM-class encrypted file fallback "
"in rune/secret_store.py; config carries refs only, never plaintext; "
"report speaks in fingerprints); provider_registry resolves api_key_ref "
"at load and persists new keys into the store on connect; frozen "
"round-trip battery (fake mimo config -> rune store -> verify: counts "
"match, every fingerprint present, no plaintext) red-first"
)
AFFECTED = [
"scripts/migrate_api_keys.py",
"src/rune/secret_store.py",
"src/rune/provider_registry.py",
"tests/test_apikey_migration.py",
"tests/test_secret_store.py",
"tests/test_provider_registry_secrets.py",
"docs/factory/research-registry.json",
"scripts/admit_wave_apikey_migration.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-apikey-migration",
"objective": OBJECTIVE,
"origin": "worker",
"project": "rune",
"task_ref": "wave-apikey-migration",
"affected_surface": AFFECTED,
"evidence_refs": [
"docs/factory/spawn-queue.md",
"docs/factory/research-registry.json",
"docs/openspec/changes/add-native-rune-tui/specs/alerts-and-remote-control/spec.md",
"src/rune/provider_registry.py",
"src/go/device-pairing/internal/pairing/secrets.go",
],
"exemption": (
"owner-directive: owner-critical migration (keys Nick cannot "
"re-enter from memory), per owner dispatch (see "
"docs/factory/research-registry.json slug apikey-migration)"),
}
act = cact.activate(md, need=need, scaffold=scaffold,
builder="wave-apikey-migration")
print("activated:", act.get("activated"), act.get("mode"))
env = cadm.admit(
act,
objective=OBJECTIVE,
builder="wave-apikey-migration",
affected_surface=AFFECTED,
evidence_refs=need["evidence_refs"],
origin="worker",
task_ref="wave-apikey-migration",
expected_new_entities=[
"scripts/migrate_api_keys.py (scan/map/apply/verify + report)",
"src/rune/secret_store.py (keyring / secret-tool / encrypted file)",
"tests/test_apikey_migration.py (frozen round-trip battery, red-first)",
"tests/test_secret_store.py (frozen secret-store battery)",
"tests/test_provider_registry_secrets.py (frozen ref-resolution battery)",
"research-registry.json entry apikey-migration (exemption)",
],
expected_runtime_effects=[
"rune's provider store (~/.config/rune/providers.json) carries "
"every mimo provider connection with api_key_ref pointers; every "
"distinct credential from the mimo/opencode stores is retrievable "
"from the OS secret store; no plaintext key material appears in "
"the config file, the verification report, or TUI/log surfaces",
],
verification_obligations=[
"PYTHONPATH=src python -m pytest tests/test_apikey_migration.py "
"tests/test_secret_store.py tests/test_provider_registry_secrets.py green",
"python scripts/migrate_api_keys.py --apply --report "
"reports/APIKEY-MIGRATION-2026-10-17.md exits 0 with verification GREEN",
"report + providers.json contain no plaintext key material "
"(fingerprints only)",
],
approval_ref="user:wave-apikey-migration dispatch",
contract_id="ADMIT-wave-apikey-migration",
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())