gabriel / muse public
feat BREAKING task/muse-sign-and-show-rename #1 / 1
gabriel · 166 days ago · Apr 14, 2026 · Diff

feat: muse sign — canonical MSign CLI for the agent era

- New muse/core/msign.py: single source of truth for all Ed25519 signing across MuseHub, Stori, Maestro, and MPay. Exports canonical_message, build_msign_header, parse_msign_header, verify_msign_header, build_payment_claim (with MPAY domain separator for cross-protocol safety).

- New muse/cli/commands/sign.py: five subcommands: muse sign header — emit Authorization: MSign … header value muse sign verify — exit 0/1 based on Ed25519 + replay-window check muse sign request — sign and execute an authenticated HTTP round-trip muse sign curl — print a copy-pasteable signed curl command muse sign payment — sign an MPay micropayment claim (chain-linkable)

- transport.py, hub.py, domains.py, coord_bus.py: all callers updated to import build_msign_header from muse.core.msign — duplicate removed.

- muse hub issue read → show, muse hub proposal read → show: consistent with muse show (commits) and muse hub repo show. No backwards compat shim.

- Full test coverage: tests/test_core_msign.py (37 tests — unit, RFC 8032 determinism vectors, performance) and tests/test_cmd_sign.py (28 tests — all subcommands, domain separation, JSON schemas).

Closes #21.

sha256:da940da8e23fc08e7874fc6ebfa08790bfe0c36506e124e7a3f51a5d5064e75f sha
+149 ~141 −2 symbols
sha256:544b8771fddc5f2785037492a638307ba0d1539665c7719121e1db26efb809a4 snapshot
+149
symbols added
~141
symbols modified
−2
symbols removed
0
dead code introduced
Semantic Changes 292 symbols
~ muse/cli/commands/sign.py .py 20 symbols added
+ _add_body_flags function function _add_body_flags L416–429
+ _add_common_flags function function _add_common_flags L381–413
+ _emit function function _emit L144–150
+ _load_signing function function _load_signing L92–119
+ _public_key_info function function _public_key_info L136–141
+ _read_body function function _read_body L122–133
+ TYPE_CHECKING import import TYPE_CHECKING L82–82
+ annotations import import annotations L74–74
+ argparse import import argparse L76–76
+ base64 import import base64 L77–77
+ hashlib import import hashlib L78–78
+ json import import json L79–79
+ pathlib import import pathlib L80–80
+ sys import import sys L81–81
+ register function function register L432–563
+ run_curl function function run_curl L300–333
+ run_header function function run_header L157–204
+ run_payment function function run_payment L340–374
+ run_request function function run_request L243–293
+ run_verify function function run_verify L211–236
~ muse/core/msign.py .py 18 symbols added
+ EMPTY_BODY_HASH variable variable EMPTY_BODY_HASH L77–77
+ ParsedMSign class class ParsedMSign L84–88
+ PaymentClaim class class PaymentClaim L91–101
+ REPLAY_WINDOW_SECONDS variable variable REPLAY_WINDOW_SECONDS L68–68
+ _MSIGN_RE variable variable _MSIGN_RE L72–74
+ build_msign_header function function build_msign_header L134–179
+ build_payment_claim function function build_payment_claim L299–361
+ canonical_message function function canonical_message L108–131
+ TYPE_CHECKING import import TYPE_CHECKING L59–59
+ TypedDict import import TypedDict L59–59
+ annotations import import annotations L52–52
+ base64 import import base64 L54–54
+ hashlib import import hashlib L55–55
+ re import import re L56–56
+ time import import time L57–57
+ parse import import urllib.parse L58–58
+ parse_msign_header function function parse_msign_header L182–206
+ verify_msign_header function function verify_msign_header L209–292
~ tests/test_cmd_sign.py .py 54 symbols added
+ TestLoadSigning class class TestLoadSigning L500–537
+ test_exits_1_when_no_identity method method test_exits_1_when_no_identity L501–510
+ test_key_path_override_loads_from_file method method test_key_path_override_loads_from_file L512–537
+ TestRunCurl class class TestRunCurl L275–361
+ _args method method _args L276–291
+ test_authorization_header_embedded method method test_authorization_header_embedded L304–314
+ test_body_file_uses_data_binary method method test_body_file_uses_data_binary L327–338
+ test_curl_starts_with_curl method method test_curl_starts_with_curl L293–302
+ test_get_method method method test_get_method L353–361
+ test_inline_body_uses_data_flag method method test_inline_body_uses_data_flag L340–351
+ test_url_appears_in_output method method test_url_appears_in_output L316–325
+ TestRunHeader class class TestRunHeader L61–151
+ _args method method _args L65–80
+ setUp method method setUp L62–63
+ test_empty_body_uses_empty_sha256 method method test_empty_body_uses_empty_sha256 L127–138
+ test_inline_body_reflected_in_sha256 method method test_inline_body_reflected_in_sha256 L140–151
+ test_json_output_fields method method test_json_output_fields L94–109
+ test_path_plus_hub_constructs_url method method test_path_plus_hub_constructs_url L111–125
+ test_text_output_is_msign_header method method test_text_output_is_msign_header L82–92
+ TestRunPayment class class TestRunPayment L368–493
+ _args method method _args L371–387
+ test_canonical_message_has_mpay_prefix method method test_canonical_message_has_mpay_prefix L408–418
+ test_deterministic_at_fixed_timestamp method method test_deterministic_at_fixed_timestamp L461–478
+ test_domain_separation_from_http_msign method method test_domain_separation_from_http_msign L439–459
+ test_json_output_has_all_fields method method test_json_output_has_all_fields L389–406
+ test_signature_is_verifiable method method test_signature_is_verifiable L420–437
+ test_text_output_prints_signature_to_stdout method method test_text_output_prints_signature_to_stdout L480–493
+ TestRunVerify class class TestRunVerify L158–268
+ _args method method _args L166–179
+ setUp method method setUp L159–164
+ test_body_mismatch_exits_1 method method test_body_mismatch_exits_1 L251–258
+ test_custom_max_age_accepted method method test_custom_max_age_accepted L260–268
+ test_expired_timestamp_exits_1 method method test_expired_timestamp_exits_1 L221–229
+ test_malformed_header_exits_1 method method test_malformed_header_exits_1 L231–240
+ test_method_mismatch_exits_1 method method test_method_mismatch_exits_1 L242–249
+ test_valid_header_exits_0 method method test_valid_header_exits_0 L181–186
+ test_valid_header_json_output method method test_valid_header_json_output L188–196
+ test_wrong_public_key_exits_1 method method test_wrong_public_key_exits_1 L198–206
+ test_wrong_public_key_json_valid_false method method test_wrong_public_key_json_valid_false L208–219
+ _make_header function function _make_header L43–47
+ _make_signing function function _make_signing L29–34
+ _public_key_b64 function function _public_key_b64 L37–40
+ _run_cmd function function _run_cmd L50–54
+ MagicMock import import MagicMock L22–22
+ annotations import import annotations L12–12
+ argparse import import argparse L14–14
+ base64 import import base64 L15–15
+ io import import io L16–16
+ json import import json L17–17
+ patch import import patch L22–22
+ sys import import sys L18–18
+ time import import time L19–19
+ unittest import import unittest L20–20
+ mock import import unittest.mock L21–21
~ tests/test_core_msign.py .py 56 symbols added
+ TestBuildMsignHeader class class TestBuildMsignHeader L127–170
+ test_contains_all_components method method test_contains_all_components L136–142
+ test_format method method test_format L128–134
+ test_none_body_treated_as_empty method method test_none_body_treated_as_empty L157–162
+ test_sig_is_base64url_no_padding method method test_sig_is_base64url_no_padding L144–155
+ test_url_query_string_included_in_signing method method test_url_query_string_included_in_signing L164–170
+ TestBuildPaymentClaim class class TestBuildPaymentClaim L423–503
+ test_canonical_message_format method method test_canonical_message_format L447–462
+ test_chain_linkage method method test_chain_linkage L490–503
+ test_deterministic method method test_deterministic L480–488
+ test_domain_separation_from_http_signing method method test_domain_separation_from_http_signing L464–478
+ test_structure method method test_structure L424–445
+ TestCanonicalMessage class class TestCanonicalMessage L78–120
+ test_body_hash_covers_raw_bytes_not_repr method method test_body_hash_covers_raw_bytes_not_repr L102–110
+ test_empty_body_uses_empty_sha256 method method test_empty_body_uses_empty_sha256 L85–90
+ test_format method method test_format L79–83
+ test_method_uppercase_preserved method method test_method_uppercase_preserved L97–100
+ test_newline_separated_fields method method test_newline_separated_fields L112–120
+ test_query_string_included method method test_query_string_included L92–95
+ TestDeterminism class class TestDeterminism L177–220
+ test_different_body_different_signature method method test_different_body_different_signature L197–202
+ test_different_ts_different_signature method method test_different_ts_different_signature L190–195
+ test_known_vector method method test_known_vector L204–220
+ test_same_inputs_same_signature method method test_same_inputs_same_signature L178–188
+ TestParseMsignHeader class class TestParseMsignHeader L227–256
+ test_empty_raises_value_error method method test_empty_raises_value_error L248–251
+ test_full_authorization_prefix method method test_full_authorization_prefix L236–241
+ test_invalid_raises_value_error method method test_invalid_raises_value_error L243–246
+ test_ts_is_int method method test_ts_is_int L253–256
+ test_valid_header method method test_valid_header L228–234
+ TestPerformance class class TestPerformance L510–524
+ test_10000_sequential_header_calls_under_2s method method test_10000_sequential_header_calls_under_2s L511–524
+ TestVerifyMsignHeader class class TestVerifyMsignHeader L263–416
+ _sign_and_header method method _sign_and_header L264–273
+ test_bad_public_key_rejected method method test_bad_public_key_rejected L357–368
+ test_different_method_rejected method method test_different_method_rejected L393–404
+ test_different_url_rejected method method test_different_url_rejected L406–416
+ test_expired_timestamp_rejected method method test_expired_timestamp_rejected L302–314
+ test_future_timestamp_rejected method method test_future_timestamp_rejected L316–328
+ test_garbage_header_rejected method method test_garbage_header_rejected L370–378
+ test_none_body_same_as_empty method method test_none_body_same_as_empty L380–391
+ test_tampered_body_rejected method method test_tampered_body_rejected L289–300
+ test_timestamp_at_edge_of_window_accepted method method test_timestamp_at_edge_of_window_accepted L330–341
+ test_valid_round_trip method method test_valid_round_trip L275–287
+ test_wrong_key_rejected method method test_wrong_key_rejected L343–355
+ _Identity class class _Identity L49–57
+ public_key_b64 method method public_key_b64 L55–57
+ _known_identity function function _known_identity L65–71
+ _make_identity function function _make_identity L60–62
+ Ed25519PrivateKey import import Ed25519PrivateKey L42–42
+ NamedTuple import import NamedTuple L39–39
+ annotations import import annotations L34–34
+ base64 import import base64 L36–36
+ hashlib import import hashlib L37–37
+ pytest import import pytest L41–41
+ time import import time L38–38
~ muse/cli/commands/domains.py .py 1 symbol modified
~ muse/core/coord_bus.py .py 1 symbol modified
~ muse/core/transport.py .py 2 symbols removed, 2 symbols modified
− _build_msign_header method method _build_msign_header L902–908
− build_msign_header function function build_msign_header L862–891
← Older Oldest on task/muse-sign-and-show-rename
All commits
Newer → Latest on task/muse-sign-and-show-rename

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:da940da8e23fc08e7874fc6ebfa08790bfe0c36506e124e7a3f51a5d5064e75f --body "your comment"