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.
137 lines
6.0 KiB
Python
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())
|