Make TUI v3 the default and add index0 foundations

This commit is contained in:
thePR0M3TH3AN committed 2026-03-05 21:52:39 -05:00
1 parent 0fa8aebc1b
commit e8a2b81e7b
17 files changed
+3005 -76

No files matched your search

+130
View File
@@ -0,0 +1,130 @@
from pathlib import Path
from tempfile import TemporaryDirectory
from helpers import create_vault
from nostr.backup_models import ChunkMeta, Manifest
from seedpass.core.index0 import (
compute_event_hash,
compute_event_id,
ensure_index0_payload,
merge_system_index0,
normalize_index0,
)
def test_ensure_index0_payload_initializes_reserved_namespace():
payload = ensure_index0_payload({"schema_version": 4, "entries": {}})
assert payload["_system"]["index0"]["schema_version"] == 1
assert payload["_system"]["index0"]["events"] == {}
assert payload["_system"]["index0"]["canonical_views"] == {}
assert payload["_system"]["index0"]["stats"]["event_count"] == 0
def test_merge_system_index0_is_order_independent_for_events_and_heads():
base_event = {
"event_type": "entry_modified",
"subject_type": "entry",
"subject_id": "1",
"subject_kind": "password",
"scope_path": "seed/root",
"actor_type": "user",
"actor_id": "fp1",
"writer_id": "writer:profile:fp1",
"modified_ts": 100,
"payload_ref": {"entry_id": "1"},
"links": [],
"tags": ["b", "a"],
"visibility": "private",
"classification": "internal",
"partition": "standard",
}
event_a = dict(base_event)
event_a["integrity_hash"] = compute_event_hash(event_a)
event_a["event_id"] = compute_event_id(event_a)
event_b = dict(base_event)
event_b["modified_ts"] = 200
event_b["prev_hash"] = "prev"
event_b["integrity_hash"] = compute_event_hash(event_b)
event_b["event_id"] = compute_event_id(event_b)
current = normalize_index0(
{
"events": {event_a["event_id"]: event_a},
"heads": {
"writer:profile:fp1": {
"event_id": event_a["event_id"],
"head_hash": "aaa",
"modified_ts": 100,
}
},
}
)
incoming = normalize_index0(
{
"events": {event_b["event_id"]: event_b},
"heads": {
"writer:profile:fp1": {
"event_id": event_b["event_id"],
"head_hash": "bbb",
"modified_ts": 200,
}
},
}
)
ab = merge_system_index0(current, incoming)
ba = merge_system_index0(incoming, current)
assert ab == ba
assert ab["stats"]["event_count"] == 2
assert ab["heads"]["writer:profile:fp1"]["head_hash"] == "bbb"
def test_vault_save_and_load_index_normalizes_index0_namespace():
with TemporaryDirectory() as tmpdir:
base = Path(tmpdir)
vault, _ = create_vault(base)
vault.save_index({"schema_version": 4, "entries": {}})
loaded = vault.load_index()
assert "_system" in loaded
assert "index0" in loaded["_system"]
assert loaded["_system"]["index0"]["stats"]["writer_count"] == 0
def test_manifest_supports_optional_index0_metadata_round_trip():
manifest = Manifest(
ver=1,
algo="gzip",
chunks=[ChunkMeta(id="c1", size=10, hash="abc", event_id="evt1")],
delta_since=123,
nonce="nonce",
index0={
"schema_version": 1,
"checkpoint_ids": ["cp:day:2026-03-05:writer:profile:fp1"],
"checkpoint_hashes": {"cp:day:2026-03-05:writer:profile:fp1": "abc123"},
"stream_heads": {"writer:profile:fp1": "head123"},
},
)
payload = {
"ver": manifest.ver,
"algo": manifest.algo,
"chunks": [meta.__dict__ for meta in manifest.chunks],
"delta_since": manifest.delta_since,
"nonce": manifest.nonce,
"index0": manifest.index0,
}
parsed = Manifest(
ver=payload["ver"],
algo=payload["algo"],
chunks=[ChunkMeta(**chunk) for chunk in payload["chunks"]],
delta_since=payload["delta_since"],
nonce=payload["nonce"],
index0=payload["index0"],
)
assert parsed.index0 is not None
assert parsed.index0["stream_heads"]["writer:profile:fp1"] == "head123"
+252 -1
View File
@@ -5,9 +5,17 @@ from types import SimpleNamespace
import pytest
pytest.importorskip("textual")
from textual.widgets import DataTable
from textual.widgets import DataTable, Input
from seedpass.tui_v3 import launch_tui3
from seedpass.tui_v3.app import (
CreateProfileScreen,
RecoverProfileScreen,
SeedPassTuiV3,
SeedWordsScreen,
SeedWordsReviewScreen,
StartupScreen,
)
class V3EntryService:
@@ -126,6 +134,7 @@ async def test_tui3_palette_lock_unlock_and_session_status() -> None:
await pilot.pause()
assert app.session_locked is True
assert vault.locked is True
assert isinstance(app.screen, StartupScreen)
app.processor.execute("unlock hunter2")
await pilot.pause()
assert app.session_locked is False
@@ -169,3 +178,245 @@ async def test_tui3_grid_focus_refresh_enables_selection_after_runtime_add() ->
table._refresh_data()
await pilot.pause()
assert table.row_count >= 4
@pytest.mark.anyio
async def test_tui3_starts_with_unlock_screen_when_services_are_not_preloaded() -> None:
app = SeedPassTuiV3()
app._list_boot_profiles = lambda: [
{"fingerprint": "EFBE51E70ED1B53A", "label": "Primary (EFBE51E70ED1B53A)"}
]
captured: dict[str, str] = {}
def _bootstrap(fingerprint: str, password: str) -> None:
captured["fingerprint"] = fingerprint
captured["password"] = password
app._bootstrap_profile_session = _bootstrap
async with app.run_test() as pilot:
await pilot.pause()
assert isinstance(app.screen, StartupScreen)
assert app.screen.query_one("#startup-profile-choice", Input).value == "1"
app.screen.query_one("#startup-password", Input).value = "hunter2"
await pilot.press("enter")
await pilot.pause()
assert captured == {
"fingerprint": "EFBE51E70ED1B53A",
"password": "hunter2",
}
@pytest.mark.anyio
async def test_tui3_create_profile_screen_uses_in_app_bootstrap_helpers() -> None:
app = SeedPassTuiV3()
app._list_boot_profiles = lambda: []
created: dict[str, str] = {}
booted: dict[str, str] = {}
def _create_existing_profile(*, seed: str, password: str) -> str:
created["seed"] = seed
created["password"] = password
return "NEWFP123"
def _bootstrap(fingerprint: str, password: str) -> None:
booted["fingerprint"] = fingerprint
booted["password"] = password
app._create_existing_profile = _create_existing_profile
app._bootstrap_profile_session = _bootstrap
async with app.run_test() as pilot:
await pilot.pause()
app.push_screen(CreateProfileScreen())
await pilot.pause()
screen = app.screen
screen.query_one("#create-mode", Input).value = "existing"
screen.query_one("#create-seed", Input).value = (
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
)
screen.query_one("#create-password", Input).value = "hunter2"
await pilot.press("enter")
await pilot.pause()
assert created["password"] == "hunter2"
assert booted == {"fingerprint": "NEWFP123", "password": "hunter2"}
@pytest.mark.anyio
async def test_tui3_recover_profile_screen_uses_in_app_bootstrap_helpers() -> None:
app = SeedPassTuiV3()
app._list_boot_profiles = lambda: [
{"fingerprint": "EFBE51E70ED1B53A", "label": "Primary (EFBE51E70ED1B53A)"}
]
recovered: dict[str, str] = {}
booted: dict[str, str] = {}
def _recover_profile(*, fingerprint: str, seed: str, password: str) -> None:
recovered["fingerprint"] = fingerprint
recovered["seed"] = seed
recovered["password"] = password
def _bootstrap(fingerprint: str, password: str) -> None:
booted["fingerprint"] = fingerprint
booted["password"] = password
app._recover_profile = _recover_profile
app._bootstrap_profile_session = _bootstrap
async with app.run_test() as pilot:
await pilot.pause()
app.push_screen(RecoverProfileScreen())
await pilot.pause()
screen = app.screen
screen.query_one("#recover-choice", Input).value = "1"
screen.query_one("#recover-seed", Input).value = (
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
)
screen.query_one("#recover-password", Input).value = "hunter2"
await pilot.press("enter")
await pilot.pause()
assert recovered["fingerprint"] == "EFBE51E70ED1B53A"
assert booted == {
"fingerprint": "EFBE51E70ED1B53A",
"password": "hunter2",
}
@pytest.mark.anyio
async def test_tui3_create_profile_screen_supports_nostr_restore_mode() -> None:
app = SeedPassTuiV3()
app._list_boot_profiles = lambda: []
restored: dict[str, object] = {}
booted: dict[str, str] = {}
def _restore_from_nostr_profile(
*, seed: str, password: str, continue_without_backup: bool
) -> str:
restored["seed"] = seed
restored["password"] = password
restored["continue_without_backup"] = continue_without_backup
return "NOSTRFP1"
def _bootstrap(fingerprint: str, password: str) -> None:
booted["fingerprint"] = fingerprint
booted["password"] = password
app._restore_from_nostr_profile = _restore_from_nostr_profile
app._bootstrap_profile_session = _bootstrap
async with app.run_test() as pilot:
await pilot.pause()
app.push_screen(CreateProfileScreen())
await pilot.pause()
screen = app.screen
screen.query_one("#create-mode", Input).value = "nostr"
screen.query_one("#create-seed", Input).value = (
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
)
screen.query_one("#create-nostr-empty-ok", Input).value = "yes"
screen.query_one("#create-password", Input).value = "hunter2"
await pilot.press("enter")
await pilot.pause()
assert restored["continue_without_backup"] is True
assert booted == {"fingerprint": "NOSTRFP1", "password": "hunter2"}
@pytest.mark.anyio
async def test_tui3_create_profile_screen_supports_backup_restore_mode() -> None:
app = SeedPassTuiV3()
app._list_boot_profiles = lambda: []
restored: dict[str, str] = {}
booted: dict[str, str] = {}
def _restore_from_backup_profile(
*, seed: str, password: str, backup_path: str
) -> str:
restored["seed"] = seed
restored["password"] = password
restored["backup_path"] = backup_path
return "BACKUPFP1"
def _bootstrap(fingerprint: str, password: str) -> None:
booted["fingerprint"] = fingerprint
booted["password"] = password
app._restore_from_backup_profile = _restore_from_backup_profile
app._bootstrap_profile_session = _bootstrap
async with app.run_test() as pilot:
await pilot.pause()
app.push_screen(CreateProfileScreen())
await pilot.pause()
screen = app.screen
screen.query_one("#create-mode", Input).value = "backup"
screen.query_one("#create-seed", Input).value = (
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
)
screen.query_one("#create-backup-path", Input).value = "/tmp/profile-backup.enc"
screen.query_one("#create-password", Input).value = "hunter2"
await pilot.press("enter")
await pilot.pause()
assert restored["backup_path"] == "/tmp/profile-backup.enc"
assert booted == {"fingerprint": "BACKUPFP1", "password": "hunter2"}
@pytest.mark.anyio
async def test_tui3_word_by_word_seed_entry_feeds_create_profile_screen() -> None:
app = SeedPassTuiV3()
app._list_boot_profiles = lambda: []
async with app.run_test() as pilot:
await pilot.pause()
app.push_screen(CreateProfileScreen())
await pilot.pause()
create_screen = app.screen
assert isinstance(create_screen, CreateProfileScreen)
create_screen.query_one("#create-words").press()
await pilot.pause()
assert isinstance(app.screen, SeedWordsScreen)
for idx, word in enumerate(
[
"abandon",
"abandon",
"abandon",
"abandon",
"abandon",
"abandon",
"abandon",
"abandon",
"abandon",
"abandon",
"abandon",
"about",
],
start=1,
):
app.screen.query_one(f"#seed-word-{idx}", Input).value = word
await pilot.press("enter")
await pilot.pause()
assert isinstance(app.screen, SeedWordsReviewScreen)
app.screen.query_one("#seed-review-confirm").press()
await pilot.pause()
assert isinstance(app.screen, CreateProfileScreen)
assert app.screen.query_one("#create-mode", Input).value == "words"
assert (
app.screen.query_one("#create-seed", Input).value
== "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
)
@pytest.mark.anyio
async def test_tui3_word_entry_flags_invalid_bip39_word() -> None:
app = SeedPassTuiV3()
app._list_boot_profiles = lambda: []
async with app.run_test() as pilot:
await pilot.pause()
app.push_screen(SeedWordsScreen(on_done=lambda _phrase: None))
await pilot.pause()
app.screen.query_one("#seed-word-1", Input).value = "notaword"
await pilot.pause()
status = str(app.screen.query_one("#seed-words-status").render())
progress = str(app.screen.query_one("#seed-words-progress").render())
assert "not in the BIP-39 wordlist" in status
assert "invalid: 1" in progress
+33 -17
View File
@@ -684,6 +684,7 @@ def test_capabilities_json():
payload = json.loads(result.stdout)
assert payload["schema_version"] == 1
assert "cli" in payload["interfaces"]
assert payload["interfaces"]["cli"]["default_interface"] == "tui3"
assert "agent" in payload["interfaces"]["cli"]["root_commands"]
assert "tui3" in payload["interfaces"]["cli"]["root_commands"]
assert "legacy" in payload["interfaces"]["cli"]["root_commands"]
@@ -1011,8 +1012,23 @@ def test_update_checksum_command(monkeypatch):
assert called.get("called") is True
def test_root_tui2_forward_fingerprint(monkeypatch):
"""Ensure --fingerprint is forwarded when launching default TUI v2."""
def test_root_tui3_forward_fingerprint(monkeypatch):
"""Ensure --fingerprint is forwarded when launching default TUI v3."""
called = {}
def fake_launch(**kwargs):
called["fp"] = kwargs.get("fingerprint")
called["factory"] = kwargs.get("entry_service_factory")
return True
monkeypatch.setattr(cli, "launch_tui3", fake_launch)
result = runner.invoke(app, ["--fingerprint", "abc"])
assert result.exit_code == 0
assert called.get("fp") == "abc"
assert called.get("factory") is None
def test_root_explicit_tui2_forward_fingerprint(monkeypatch):
called = {}
def fake_launch(**kwargs):
@@ -1022,7 +1038,7 @@ def test_root_tui2_forward_fingerprint(monkeypatch):
monkeypatch.setattr(cli, "_get_entry_service", lambda _ctx: object())
monkeypatch.setattr(cli, "launch_tui2", fake_launch)
result = runner.invoke(app, ["--fingerprint", "abc"])
result = runner.invoke(app, ["--tui", "v2", "--fingerprint", "abc"])
assert result.exit_code == 0
assert called.get("fp") == "abc"
assert callable(called.get("factory"))
@@ -1095,8 +1111,8 @@ def test_tui2_check(monkeypatch):
def test_tui3_check(monkeypatch):
monkeypatch.setattr(
cli,
"check_tui2_runtime",
lambda: {"status": "ok", "backend": "textual", "textual_available": True},
"check_tui3_runtime",
lambda: {"status": "ok", "backend": "textual-v3", "textual_available": True},
)
result = runner.invoke(app, ["tui3", "--check"])
assert result.exit_code == 0
@@ -1153,24 +1169,24 @@ def test_tui2_fallback_legacy_strips_subcommand_argv(monkeypatch):
assert called.get("argv") == []
def test_root_fallback_legacy_when_tui2_unavailable(monkeypatch):
called = {}
monkeypatch.setattr(cli, "_get_entry_service", lambda _ctx: object())
monkeypatch.setattr(cli, "launch_tui2", lambda **_: False)
def test_root_tui3_unavailable_returns_error(monkeypatch):
monkeypatch.setattr(cli, "launch_tui3", lambda **_: False)
result = runner.invoke(app, ["--fingerprint", "abc"])
assert result.exit_code == 1
assert "TUI v3 runtime unavailable" in result.stderr
assert "seedpass tui2" in result.stderr
assert "seedpass legacy" in result.stderr
def fake_main(*, fingerprint=None):
called["fp"] = fingerprint
return 0
def test_root_tui3_does_not_preflight_entry_service(monkeypatch):
monkeypatch.setattr(
cli,
"importlib",
SimpleNamespace(import_module=lambda _: SimpleNamespace(main=fake_main)),
"_get_entry_service",
lambda _ctx: (_ for _ in ()).throw(RuntimeError("should not run")),
)
result = runner.invoke(app, ["--fingerprint", "abc"])
monkeypatch.setattr(cli, "launch_tui3", lambda **_: True)
result = runner.invoke(app, [])
assert result.exit_code == 0
assert called.get("fp") == "abc"
assert "falling back to legacy TUI" in result.stdout
def test_tui2_preflight_failure_without_fallback(monkeypatch):