fix(session-coordinator): 支持 co_managed_keygen 动态参与者加入

问题: 通过邀请码加入共管钱包会话时报 "party not invited" 错误
原因: 外部参与者不在 party pool 中,CreateSession 时无法预先选择

修复:
- join_session.go: 对于 co_managed_keygen + wildcard token,允许动态添加参与者
- create_session.go: 新增 selectPartiesByCompositionForCoManaged,跳过 TemporaryCount 选择
- report_completion.go: 使用 IsKeygen() 方法,co_managed_keygen 完成后也创建账户记录

注意: 所有修改仅对 co_managed_keygen 类型生效,不影响现有 keygen/sign 流程

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-29 04:25:11 -08:00
parent af08f0f9c6
commit a5ab2e8350
3 changed files with 113 additions and 10 deletions

View File

@ -160,8 +160,14 @@ func (uc *CreateSessionUseCase) Execute(
// Check if party composition is specified
if req.PartyComposition != nil {
// Select parties based on composition requirements
selectedParties, err = uc.selectPartiesByComposition(req.PartyComposition)
// For co_managed_keygen, TemporaryCount represents external participants
// who will join dynamically via invite code - don't select from pool
if sessionType == entities.SessionTypeCoManagedKeygen {
selectedParties, err = uc.selectPartiesByCompositionForCoManaged(req.PartyComposition)
} else {
// Select parties based on composition requirements
selectedParties, err = uc.selectPartiesByComposition(req.PartyComposition)
}
if err != nil {
logger.Warn("failed to select parties by composition, falling back to simple selection",
zap.Error(err))
@ -359,6 +365,62 @@ func (uc *CreateSessionUseCase) Execute(
}, nil
}
// selectPartiesByCompositionForCoManaged selects parties for co_managed_keygen sessions
// For co_managed_keygen, TemporaryCount represents external participants who will join
// dynamically via invite code - we don't select them from pool, only select persistent parties
func (uc *CreateSessionUseCase) selectPartiesByCompositionForCoManaged(composition *input.PartyComposition) ([]output.PartyEndpoint, error) {
if uc.partyPool == nil {
return nil, fmt.Errorf("party pool not configured")
}
var allSelected []output.PartyEndpoint
// Select persistent parties (server-side parties)
if composition.PersistentCount > 0 {
persistent, err := uc.partyPool.SelectPartiesWithFilter(output.PartySelectionFilter{
Count: composition.PersistentCount,
Role: output.PartyRolePersistent,
})
if err != nil {
return nil, fmt.Errorf("failed to select persistent parties: %w", err)
}
allSelected = append(allSelected, persistent...)
}
// Select delegate parties if any
if composition.DelegateCount > 0 {
delegate, err := uc.partyPool.SelectPartiesWithFilter(output.PartySelectionFilter{
Count: composition.DelegateCount,
Role: output.PartyRoleDelegate,
})
if err != nil {
return nil, fmt.Errorf("failed to select delegate parties: %w", err)
}
allSelected = append(allSelected, delegate...)
}
// NOTE: TemporaryCount is intentionally NOT selected from pool for co_managed_keygen
// External participants will join dynamically via invite code with wildcard token
if composition.TemporaryCount > 0 {
logger.Info("co_managed_keygen: TemporaryCount represents external participants, not selecting from pool",
zap.Int("temporary_count", composition.TemporaryCount),
zap.Int("persistent_selected", len(allSelected)))
}
// Apply custom filters if provided
for _, filter := range composition.CustomFilters {
customParties, err := uc.partyPool.SelectPartiesWithFilter(filter)
if err != nil {
return nil, fmt.Errorf("failed to select parties with custom filter: %w", err)
}
allSelected = append(allSelected, customParties...)
}
// For co_managed_keygen, it's OK to have no selected parties if all are external
// The session will use wildcard tokens for all participants
return allSelected, nil
}
// selectPartiesByComposition selects parties based on composition requirements
func (uc *CreateSessionUseCase) selectPartiesByComposition(composition *input.PartyComposition) ([]output.PartyEndpoint, error) {
if uc.partyPool == nil {

View File

@ -81,14 +81,55 @@ func (uc *JoinSessionUseCase) Execute(
}
// 5. Get participant (must already exist from CreateSession)
// For co_managed_keygen with wildcard token, allow dynamic participant addition
participant, err := session.GetParticipant(partyID)
if err != nil {
// Participant doesn't exist - this party was not invited to this session
logger.Warn("party not found in session participants",
zap.String("session_id", session.ID.String()),
zap.String("party_id", inputData.PartyID),
zap.Int("existing_participant_count", len(session.Participants)))
return nil, entities.ErrPartyNotInvited
// Check if this is a co_managed_keygen session with wildcard token
// Wildcard tokens allow any party to join dynamically
if session.SessionType == entities.SessionTypeCoManagedKeygen && claims.PartyID == "*" {
// Dynamic participant addition for co_managed_keygen
if len(session.Participants) >= session.Threshold.N() {
logger.Warn("session is full, cannot add more participants",
zap.String("session_id", session.ID.String()),
zap.String("party_id", inputData.PartyID),
zap.Int("current_participants", len(session.Participants)),
zap.Int("threshold_n", session.Threshold.N()))
return nil, entities.ErrSessionFull
}
// Create new participant with the next available index
newIndex := len(session.Participants)
participant, err = entities.NewParticipant(partyID, newIndex, inputData.DeviceInfo)
if err != nil {
logger.Error("failed to create new participant",
zap.String("session_id", session.ID.String()),
zap.String("party_id", inputData.PartyID),
zap.Error(err))
return nil, err
}
// Add participant to session
if err := session.AddParticipant(participant); err != nil {
logger.Error("failed to add participant to session",
zap.String("session_id", session.ID.String()),
zap.String("party_id", inputData.PartyID),
zap.Error(err))
return nil, err
}
logger.Info("dynamically added participant to co_managed_keygen session",
zap.String("session_id", session.ID.String()),
zap.String("party_id", inputData.PartyID),
zap.Int("party_index", newIndex),
zap.Int("total_participants", len(session.Participants)))
} else {
// Participant doesn't exist - this party was not invited to this session
logger.Warn("party not found in session participants",
zap.String("session_id", session.ID.String()),
zap.String("party_id", inputData.PartyID),
zap.Int("existing_participant_count", len(session.Participants)))
return nil, entities.ErrPartyNotInvited
}
}
logger.Debug("participant found in session",

View File

@ -191,8 +191,8 @@ func (uc *ReportCompletionUseCase) executeWithRetry(
zap.Error(err))
}
// For keygen sessions, automatically create account record
if session.SessionType == entities.SessionTypeKeygen && uc.accountService != nil {
// For keygen sessions (including co_managed_keygen), automatically create account record
if session.SessionType.IsKeygen() && uc.accountService != nil {
uc.createAccountFromKeygen(ctx, session)
}
}