gabriel / musehub public
feat BREAKING task/proposer-signature #1 / 1
AI Agent gabriel · 127 days ago · May 8, 2026 · Diff

feat: Ed25519 proposer signature on merge proposals

Every proposal now optionally carries an Ed25519 signature by the proposer over the canonical PROPOSE pre-image (repo_id, from_branch, to_branch, author, client-supplied timestamp).

- musehub/proposals/signing.py: canonical_propose_message(), verify_proposer_signature() (uses decode_sig/decode_pubkey from muse.core.types), check_timestamp_skew() - alembic/versions/0048: proposer_signature + proposer_public_key columns (nullable) - musehub_models.py: ORM columns on MusehubProposal - musehub.py: ProposalCreate gains proposerPublicKey/Signature/Timestamp fields; ProposalResponse gains proposerSignature + proposerPublicKey - musehub_proposals service: create_proposal accepts + stores both fields - proposals route: verifies registered key (403), valid sig (422), timestamp skew (422) - tests/test_proposal_proposer_signature.py: 10 tests (T1-T7) all passing

sha256:e55c0e6cc811e108d2f1638d065cbf87a81cd0d34d16a2f8a28ba07f876d77b3 sha
+65 ~6 symbols
sha256:2244c9743039565289a65799fb84d483220cb185f86f17adbecd68bab2a66a0b snapshot
+65
symbols added
~6
symbols modified
0
dead code introduced
Semantic Changes 71 symbols
+ branch_labels variable variable branch_labels L17–17
+ depends_on variable variable depends_on L18–18
+ down_revision variable variable down_revision L16–16
+ downgrade function function downgrade L32–34
+ annotations import import annotations L10–10
+ op import import op L13–13
+ sa import import sa L12–12
+ revision variable variable revision L15–15
+ upgrade function function upgrade L21–29
~ musehub/proposals/signing.py .py 14 symbols added
+ _DOMAIN_PREFIX variable variable _DOMAIN_PREFIX L29–29
+ _MAX_SKEW variable variable _MAX_SKEW L30–30
+ canonical_propose_message function function canonical_propose_message L33–58
+ check_timestamp_skew function function check_timestamp_skew L94–106
+ KeyAlgorithm import import KeyAlgorithm L26–26
+ SignatureError import import SignatureError L26–26
+ annotations import import annotations L21–21
+ datetime import import datetime L23–23
+ decode_pubkey import import decode_pubkey L25–25
+ decode_sig import import decode_sig L25–25
+ timedelta import import timedelta L23–23
+ timezone import import timezone L23–23
+ verify_signature import import verify_signature L26–26
+ verify_proposer_signature function function verify_proposer_signature L61–91
+ _TEST_IDENTITY_ID variable variable _TEST_IDENTITY_ID L55–55
+ _create_repo function async_function _create_repo L94–101
+ _make_keypair function function _make_keypair L62–67
+ _push_branch function async_function _push_branch L104–113
+ _register_key function async_function _register_key L70–85
+ _sign_propose function function _sign_propose L88–91
+ AsyncClient import import AsyncClient L39–39
+ AsyncSession import import AsyncSession L40–40
+ Ed25519PrivateKey import import Ed25519PrivateKey L38–38
+ MusehubAuthKey import import MusehubAuthKey L44–44
+ MusehubBranch import import MusehubBranch L45–45
+ MusehubIdentity import import MusehubIdentity L45–45
+ StrDict import import StrDict L50–50
+ annotations import import annotations L32–32
+ b64url_decode import import b64url_decode L51–51
+ b64url_encode import import b64url_encode L51–51
+ base64 import import base64 L34–34
+ canonical_propose_message import import canonical_propose_message L46–46
+ compute_identity_id import import compute_identity_id L42–42
+ compute_key_id import import compute_key_id L42–42
+ compute_repo_id import import compute_repo_id L42–42
+ datetime import import datetime L35–35
+ key_fingerprint import import key_fingerprint L43–43
+ pytest import import pytest L37–37
+ timezone import import timezone L35–35
+ verify_proposer_signature import import verify_proposer_signature L46–46
+ test_canonical_propose_message_format function function test_canonical_propose_message_format L133–149
+ test_canonical_propose_message_is_deterministic function function test_canonical_propose_message_is_deterministic L120–130
+ test_create_proposal_bad_signature_returns_422 function async_function test_create_proposal_bad_signature_returns_422 L313–341
+ test_create_proposal_unregistered_key_returns_403 function async_function test_create_proposal_unregistered_key_returns_403 L349–379
+ test_create_proposal_with_valid_signature function async_function test_create_proposal_with_valid_signature L215–253
+ test_create_proposal_without_signature_succeeds function async_function test_create_proposal_without_signature_succeeds L191–207
+ test_get_proposal_returns_signature_fields function async_function test_get_proposal_returns_signature_fields L261–305
+ test_verify_proposer_signature_accepts_valid function function test_verify_proposer_signature_accepts_valid L156–160
+ test_verify_proposer_signature_rejects_tampered_message function function test_verify_proposer_signature_rejects_tampered_message L163–173
+ test_verify_proposer_signature_rejects_wrong_key function function test_verify_proposer_signature_rejects_wrong_key L176–183
~ musehub/api/routes/musehub/proposals.py .py 6 symbols added, 1 symbol modified
+ MusehubAuthKey import import MusehubAuthKey L36–36
+ SignatureError import import SignatureError L34–34
+ datetime import import datetime L25–25
+ select import import select L28–28
+ split_pubkey import import split_pubkey L31–31
+ timezone import import timezone L25–25
~ musehub/db/musehub_models.py .py 1 symbol modified
← Older Oldest on task/proposer-signature
All commits
Newer → Latest on task/proposer-signature

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