gabriel / muse public
feat patch phase-2 task/phase-2-remaining #1 / 1
gabriel · 157 days ago · Apr 15, 2026 · Diff

feat(phase-2): muse agent keygen/list/register + agent_slots registry + MUSE_AGENT_HD_SEED

- muse/core/agent_slots.py: new module — ~/.muse/agent-slots.toml registry with get_next_account (atomic increment), register_slot, list_slots, peek_next_account; fcntl advisory locking + 0o600 file mode + symlink guard

- muse/cli/config.py: MUSE_AGENT_HD_SEED added as step 1 in get_signing_identity resolution order; base64url-decodes 64-byte sub-seed, derives Ed25519 identity key via derive_identity_key; MUSE_AGENT_KEY demoted to step 2 (legacy)

- muse/cli/commands/agent.py: new muse agent namespace — keygen (derive + display sub-seed at account N), list (registry read), register (name→account binding); JSON schema + human-readable output; docstrings on all callables

- muse/cli/app.py: import + register agent_cmd in alphabetical position

- tests/test_cmd_agent.py: 58 tests across all 8 categories for the CLI - tests/test_core_agent_slots.py: 52 tests for core/agent_slots.py - tests/test_signing_hd_seed.py: 28 tests for MUSE_AGENT_HD_SEED in config.py

sha256:b8e1a516c20fe1dd22df9b5c13ac1e47b5cac26f7f1d780513193fd0bc58df86 sha
+262 ~116 symbols
sha256:e7c1fd06eeb3871041db9ab4c479293a634446e688367fe3f41e3dddc7eae936 snapshot
+262
symbols added
~116
symbols modified
0
dead code introduced
Semantic Changes 378 symbols
~ muse/cli/commands/agent.py .py 24 symbols added
+ _KeygenJson class class _KeygenJson L108–118
+ _RegisterJson class class _RegisterJson L121–128
+ _derive_agent_seed function function _derive_agent_seed L136–164
+ _fingerprint function function _fingerprint L181–183
+ _require_mnemonic function function _require_mnemonic L186–218
+ _resolve_hub_url function function _resolve_hub_url L221–246
+ _sub_seed_to_public function function _sub_seed_to_public L167–178
+ ExitCode import import ExitCode L96–96
+ TypedDict import import TypedDict L94–94
+ annotations import import annotations L86–86
+ argparse import import argparse L88–88
+ base64 import import base64 L89–89
+ hashlib import import hashlib L90–90
+ hostname_from_url import import hostname_from_url L97–97
+ json import import json L91–91
+ load_identity import import load_identity L97–97
+ logging import import logging L92–92
+ sanitize_display import import sanitize_display L98–98
+ sys import import sys L93–93
+ logger variable variable logger L100–100
+ register function function register L408–519
+ run_keygen function function run_keygen L254–320
+ run_list function function run_list L323–354
+ run_register function function run_register L357–400
~ muse/core/agent_slots.py .py 22 symbols added
+ AgentSlot class class AgentSlot L49–55
+ _SLOTS_DIR variable variable _SLOTS_DIR L46–46
+ _SLOTS_FILE variable variable _SLOTS_FILE L45–45
+ _dump function function _dump L78–94
+ _load_raw function function _load_raw L67–75
+ _save function function _save L115–130
+ _toml_escape function function _toml_escape L62–64
+ _write_lock function function _write_lock L98–112
+ get_next_account function function get_next_account L137–158
+ Generator import import Generator L36–36
+ TypedDict import import TypedDict L36–36
+ annotations import import annotations L28–28
+ contextlib import import contextlib L30–30
+ fcntl import import fcntl L31–31
+ hostname_from_url import import hostname_from_url L43–43
+ os import import os L32–32
+ pathlib import import pathlib L33–33
+ stat import import stat L34–34
+ tempfile import import tempfile L35–35
+ list_slots function function list_slots L194–218
+ peek_next_account function function peek_next_account L221–234
+ register_slot function function register_slot L161–191
~ tests/test_cmd_agent.py .py 93 symbols added
+ TestAgentKeygenE2E class class TestAgentKeygenE2E L420–486
+ test_keygen_human_readable method method test_keygen_human_readable L436–445
+ test_keygen_json_exit_0 method method test_keygen_json_exit_0 L423–434
+ test_keygen_no_hub_and_no_config_fails method method test_keygen_no_hub_and_no_config_fails L447–452
+ test_keygen_no_identity_fails method method test_keygen_no_identity_fails L454–462
+ test_keygen_requires_account method method test_keygen_requires_account L464–472
+ test_keygen_with_name_includes_name_in_json method method test_keygen_with_name_includes_name_in_json L474–486
+ TestAgentListE2E class class TestAgentListE2E L489–527
+ test_list_after_register method method test_list_after_register L503–518
+ test_list_empty_json method method test_list_empty_json L492–501
+ test_list_human_readable_empty method method test_list_human_readable_empty L520–527
+ TestAgentRegisterE2E class class TestAgentRegisterE2E L530–568
+ test_register_json_ok method method test_register_json_ok L533–546
+ test_register_requires_account method method test_register_requires_account L548–557
+ test_register_requires_name method method test_register_requires_name L559–568
+ TestDataIntegrity class class TestDataIntegrity L611–688
+ test_fingerprint_matches_sha256_of_public_key method method test_fingerprint_matches_sha256_of_public_key L635–647
+ test_hd_seed_b64_decodes_to_64_bytes method method test_hd_seed_b64_decodes_to_64_bytes L622–633
+ test_keygen_account_0_and_1_produce_different_seeds method method test_keygen_account_0_and_1_produce_different_seeds L614–620
+ test_msign_path_contains_account_index method method test_msign_path_contains_account_index L678–688
+ test_same_account_produces_same_output_in_separate_invocations method method test_same_account_produces_same_output_in_separate_invocations L649–663
+ test_slot_overwrite_updates_account method method test_slot_overwrite_updates_account L665–676
+ TestDeriveAgentSeed class class TestDeriveAgentSeed L119–151
+ test_different_accounts_produce_different_seeds method method test_different_accounts_produce_different_seeds L132–136
+ test_different_mnemonics_produce_different_seeds method method test_different_mnemonics_produce_different_seeds L144–151
+ test_is_bytes method method test_is_bytes L127–130
+ test_returns_64_bytes method method test_returns_64_bytes L122–125
+ test_same_account_is_deterministic method method test_same_account_is_deterministic L138–142
+ TestDocstrings class class TestDocstrings L814–840
+ _public_names method method _public_names L817–827
+ test_all_public_functions_have_docstrings method method test_all_public_functions_have_docstrings L829–831
+ test_module_has_docstring method method test_module_has_docstring L833–835
+ test_typed_dicts_have_docstrings method method test_typed_dicts_have_docstrings L837–840
+ TestFingerprint class class TestFingerprint L175–192
+ test_length_is_64 method method test_length_is_64 L189–192
+ test_returns_hex_string method method test_returns_hex_string L178–182
+ test_sha256_of_input method method test_sha256_of_input L184–187
+ TestPerformance class class TestPerformance L696–722
+ test_10_sequential_keygens_under_5_seconds method method test_10_sequential_keygens_under_5_seconds L711–722
+ test_keygen_under_2_seconds method method test_keygen_under_2_seconds L699–709
+ TestRequireMnemonic class class TestRequireMnemonic L195–220
+ test_raises_when_identity_has_no_mnemonic method method test_raises_when_identity_has_no_mnemonic L211–220
+ test_raises_when_no_identity method method test_raises_when_no_identity L205–209
+ test_returns_mnemonic_from_identity method method test_returns_mnemonic_from_identity L198–203
+ TestResolveHubUrl class class TestResolveHubUrl L223–242
+ test_raises_when_no_hub method method test_raises_when_no_hub L230–235
+ test_reads_from_repo_config method method test_reads_from_repo_config L237–242
+ test_returns_args_hub_when_provided method method test_returns_args_hub_when_provided L226–228
+ TestRunKeygen class class TestRunKeygen L250–301
+ test_keygen_msign_path_format method method test_keygen_msign_path_format L275–290
+ test_keygen_negative_account_rejected method method test_keygen_negative_account_rejected L292–301
+ test_keygen_produces_valid_output method method test_keygen_produces_valid_output L253–273
+ TestRunList class class TestRunList L304–361
+ test_list_empty method method test_list_empty L307–320
+ test_list_shows_registered_slots method method test_list_shows_registered_slots L322–341
+ test_list_sorted_by_account method method test_list_sorted_by_account L343–361
+ TestRunRegister class class TestRunRegister L364–412
+ test_register_creates_slot method method test_register_creates_slot L367–382
+ test_register_negative_account_rejected method method test_register_negative_account_rejected L403–412
+ test_register_persists_across_calls method method test_register_persists_across_calls L384–401
+ TestSecurity class class TestSecurity L730–806
+ test_mnemonic_not_in_keygen_json_output method method test_mnemonic_not_in_keygen_json_output L756–765
+ test_mnemonic_not_in_list_output method method test_mnemonic_not_in_list_output L767–778
+ test_negative_account_rejected_in_keygen method method test_negative_account_rejected_in_keygen L733–742
+ test_negative_account_rejected_in_register method method test_negative_account_rejected_in_register L744–754
+ test_slots_dir_not_world_readable method method test_slots_dir_not_world_readable L797–806
+ test_slots_file_symlink_guard method method test_slots_file_symlink_guard L780–795
+ TestStress class class TestStress L576–603
+ test_100_different_accounts_all_unique method method test_100_different_accounts_all_unique L579–582
+ test_register_and_list_100_slots method method test_register_and_list_100_slots L590–603
+ test_repeated_derivation_consistent method method test_repeated_derivation_consistent L584–588
+ TestSubSeedToPublic class class TestSubSeedToPublic L154–172
+ test_deterministic method method test_deterministic L163–166
+ test_different_seeds_different_pubkeys method method test_different_seeds_different_pubkeys L168–172
+ test_returns_32_bytes method method test_returns_32_bytes L157–161
+ _TEST_HOSTNAME variable variable _TEST_HOSTNAME L38–38
+ _TEST_HUB variable variable _TEST_HUB L37–37
+ _TEST_MNEMONIC variable variable _TEST_MNEMONIC L33–36
+ cli variable variable cli L26–26
+ identity_with_mnemonic function function identity_with_mnemonic L79–92
+ Any import import Any L21–21
+ CliRunner import import CliRunner L24–24
+ annotations import import annotations L14–14
+ base64 import import base64 L16–16
+ hashlib import import hashlib L17–17
+ json import import json L18–18
+ pathlib import import pathlib L19–19
+ pytest import import pytest L23–23
+ time import import time L20–20
+ isolated_identity function function isolated_identity L47–60
+ isolated_slots function function isolated_slots L64–75
+ repo_with_hub function function repo_with_hub L96–111
+ runner variable variable runner L27–27
~ tests/test_core_agent_slots.py .py 79 symbols added
+ TestDataIntegrity class class TestDataIntegrity L389–435
+ test_counter_is_monotonic method method test_counter_is_monotonic L392–398
+ test_file_permissions_after_register method method test_file_permissions_after_register L420–426
+ test_msign_path_schema method method test_msign_path_schema L412–418
+ test_next_account_survives_restart method method test_next_account_survives_restart L428–435
+ test_peek_never_exceeds_next method method test_peek_never_exceeds_next L400–404
+ test_slot_hub_field_is_hostname_not_url method method test_slot_hub_field_is_hostname_not_url L406–410
+ TestDocstrings class class TestDocstrings L555–581
+ _public_names method method _public_names L558–567
+ test_agent_slot_typed_dict_has_docstring method method test_agent_slot_typed_dict_has_docstring L579–581
+ test_all_public_functions_have_docstrings method method test_all_public_functions_have_docstrings L569–573
+ test_module_has_docstring method method test_module_has_docstring L575–577
+ TestDump class class TestDump L109–153
+ test_empty_dict_produces_empty_string method method test_empty_dict_produces_empty_string L112–114
+ test_hostnames_sorted_in_output method method test_hostnames_sorted_in_output L146–153
+ test_round_trip_preserves_next_account method method test_round_trip_preserves_next_account L116–131
+ test_slots_sorted_in_output method method test_slots_sorted_in_output L133–144
+ TestE2EReadWriteCycle class class TestE2EReadWriteCycle L307–346
+ test_file_contains_both_hubs method method test_file_contains_both_hubs L339–346
+ test_get_register_list_roundtrip method method test_get_register_list_roundtrip L310–316
+ test_multiple_hubs_in_same_file method method test_multiple_hubs_in_same_file L328–337
+ test_peek_before_and_after_register method method test_peek_before_and_after_register L318–326
+ TestGetNextAccount class class TestGetNextAccount L161–193
+ test_first_call_returns_1 method method test_first_call_returns_1 L164–166
+ test_increments_on_each_call method method test_increments_on_each_call L168–174
+ test_independent_per_hub method method test_independent_per_hub L176–185
+ test_minimum_account_is_1 method method test_minimum_account_is_1 L187–193
+ TestListSlots class class TestListSlots L260–299
+ test_all_fields_present method method test_all_fields_present L292–299
+ test_empty_when_no_file method method test_empty_when_no_file L263–265
+ test_isolated_per_hub method method test_isolated_per_hub L283–290
+ test_returns_registered_slots method method test_returns_registered_slots L267–272
+ test_sorted_by_account_ascending method method test_sorted_by_account_ascending L274–281
+ TestLoadRaw class class TestLoadRaw L85–106
+ test_loads_valid_toml method method test_loads_valid_toml L98–106
+ test_returns_empty_dict_on_corrupt_file method method test_returns_empty_dict_on_corrupt_file L92–96
+ test_returns_empty_dict_when_absent method method test_returns_empty_dict_when_absent L88–90
+ TestPeekNextAccount class class TestPeekNextAccount L196–217
+ test_does_not_increment method method test_does_not_increment L199–207
+ test_reflects_current_state method method test_reflects_current_state L213–217
+ test_returns_1_when_file_absent method method test_returns_1_when_file_absent L209–211
+ TestPerformance class class TestPerformance L443–474
+ test_100_get_next_account_under_3_seconds method method test_100_get_next_account_under_3_seconds L446–454
+ test_100_register_slot_under_3_seconds method method test_100_register_slot_under_3_seconds L456–464
+ test_list_100_slots_under_1_second method method test_list_100_slots_under_1_second L466–474
+ TestRegisterSlot class class TestRegisterSlot L220–257
+ test_msign_path_contains_account method method test_msign_path_contains_account L233–236
+ test_msign_path_starts_with_m method method test_msign_path_starts_with_m L238–241
+ test_overwrite_updates_account method method test_overwrite_updates_account L243–250
+ test_persists_to_file method method test_persists_to_file L252–257
+ test_register_returns_slot_with_correct_fields method method test_register_returns_slot_with_correct_fields L223–231
+ TestSecurity class class TestSecurity L482–547
+ test_concurrent_writes_no_data_loss method method test_concurrent_writes_no_data_loss L513–535
+ test_file_mode_is_0600 method method test_file_mode_is_0600 L497–503
+ test_lock_file_created_in_slots_dir method method test_lock_file_created_in_slots_dir L505–511
+ test_symlink_guard_blocks_write method method test_symlink_guard_blocks_write L485–495
+ test_toml_injection_in_hub_hostname_escaped method method test_toml_injection_in_hub_hostname_escaped L537–547
+ TestStress class class TestStress L354–381
+ test_100_slots_registered_and_listed method method test_100_slots_registered_and_listed L362–367
+ test_500_sequential_get_next_account method method test_500_sequential_get_next_account L357–360
+ test_interleaved_hubs_stay_independent method method test_interleaved_hubs_stay_independent L374–381
+ test_repeated_peek_is_stable method method test_repeated_peek_is_stable L369–372
+ TestTomlEscape class class TestTomlEscape L62–82
+ test_both_special_chars method method test_both_special_chars L77–82
+ test_escapes_backslash method method test_escapes_backslash L65–67
+ test_escapes_double_quote method method test_escapes_double_quote L69–71
+ test_plain_string_unchanged method method test_plain_string_unchanged L73–75
+ _TEST_HOSTNAME variable variable _TEST_HOSTNAME L28–28
+ _TEST_HOSTNAME2 variable variable _TEST_HOSTNAME2 L30–30
+ _TEST_HUB variable variable _TEST_HUB L27–27
+ _TEST_HUB2 variable variable _TEST_HUB2 L29–29
+ Any import import Any L19–19
+ annotations import import annotations L14–14
+ pathlib import import pathlib L16–16
+ pytest import import pytest L21–21
+ stat import import stat L17–17
+ time import import time L18–18
+ slots_dir function function slots_dir L39–48
+ slots_file function function slots_file L52–54
~ tests/test_signing_hd_seed.py .py 43 symbols added
+ TestDataIntegrity class class TestDataIntegrity L272–316
+ test_handle_preference_order method method test_handle_preference_order L301–316
+ test_public_key_matches_direct_derivation method method test_public_key_matches_direct_derivation L275–289
+ test_with_padding_variants method method test_with_padding_variants L291–299
+ TestDocstrings class class TestDocstrings L431–452
+ test_get_signing_identity_has_resolution_order method method test_get_signing_identity_has_resolution_order L442–445
+ test_get_signing_identity_mentions_hd_seed method method test_get_signing_identity_mentions_hd_seed L434–440
+ test_get_signing_identity_mentions_muse_agent_key method method test_get_signing_identity_mentions_muse_agent_key L447–452
+ TestGetSigningIdentityHdSeed class class TestGetSigningIdentityHdSeed L94–176
+ test_hd_seed_takes_priority_over_muse_agent_key method method test_hd_seed_takes_priority_over_muse_agent_key L145–176
+ test_result_handle_defaults_to_agent method method test_result_handle_defaults_to_agent L115–123
+ test_result_handle_falls_back_to_agent_id_param method method test_result_handle_falls_back_to_agent_id_param L135–143
+ test_result_handle_from_muse_agent_handle method method test_result_handle_from_muse_agent_handle L125–133
+ test_result_has_private_key method method test_result_has_private_key L107–113
+ test_returns_signing_identity_when_hd_seed_set method method test_returns_signing_identity_when_hd_seed_set L97–105
+ TestHdSeedDecode class class TestHdSeedDecode L58–86
+ test_64_byte_seed_decodes_correctly method method test_64_byte_seed_decodes_correctly L61–65
+ test_derive_identity_key_from_sub_seed method method test_derive_identity_key_from_sub_seed L73–78
+ test_non_64_byte_seed_identified method method test_non_64_byte_seed_identified L67–71
+ test_public_key_is_32_bytes method method test_public_key_is_32_bytes L80–86
+ TestPerformance class class TestPerformance L324–348
+ test_10_resolutions_under_5_seconds method method test_10_resolutions_under_5_seconds L338–348
+ test_single_resolution_under_2_seconds method method test_single_resolution_under_2_seconds L327–336
+ TestSecurity class class TestSecurity L356–423
+ test_empty_hd_seed_skips_to_next_step method method test_empty_hd_seed_skips_to_next_step L383–408
+ test_hd_seed_env_var_not_logged_at_debug method method test_hd_seed_env_var_not_logged_at_debug L410–423
+ test_malformed_base64_ignored method method test_malformed_base64_ignored L372–381
+ test_wrong_length_seed_ignored method method test_wrong_length_seed_ignored L359–370
+ TestSignVerifyRoundtrip class class TestSignVerifyRoundtrip L184–234
+ test_different_accounts_produce_different_signatures method method test_different_accounts_produce_different_signatures L199–216
+ test_same_account_same_signature method method test_same_account_same_signature L218–234
+ test_sign_and_verify method method test_sign_and_verify L187–197
+ TestStress class class TestStress L242–264
+ test_100_resolutions_all_return_identity method method test_100_resolutions_all_return_identity L245–252
+ test_100_resolutions_all_same_pubkey method method test_100_resolutions_all_same_pubkey L254–264
+ _TEST_MNEMONIC variable variable _TEST_MNEMONIC L29–32
+ _encode_hd_seed function function _encode_hd_seed L48–50
+ _make_hd_seed function function _make_hd_seed L40–45
+ Ed25519PrivateKey import import Ed25519PrivateKey L23–23
+ annotations import import annotations L17–17
+ base64 import import base64 L19–19
+ pytest import import pytest L22–22
+ time import import time L20–20
~ muse/cli/config.py .py 1 symbol modified
← Older Oldest on task/phase-2-remaining
All commits
Newer → Latest on task/phase-2-remaining

0 comments

No comments yet. Be the first to start the discussion.

To add a comment, use the Muse CLI: muse hub commit comment sha256:b8e1a516c20fe1dd22df9b5c13ac1e47b5cac26f7f1d780513193fd0bc58df86 --body "your comment"