package roles import ( "context" "fmt" "inbox/internal/base/timeutil" "inbox/internal/domain/role" "inbox/internal/domain/skill" ) type Repository interface { GetRole(ctx context.Context, name string) (role.Definition, error) GetRoleConfig(ctx context.Context, roleName string) (role.Config, error) ListRoles(ctx context.Context) ([]role.Definition, error) UpsertRole(ctx context.Context, value role.Definition, changedBy string) (role.Definition, error) UpsertRolePrompt(ctx context.Context, value role.Prompt, changedBy string) (role.Prompt, error) ListRolePrompts(ctx context.Context, roleName string) ([]role.Prompt, error) UpsertRoleConfig(ctx context.Context, value role.Config, changedBy string) (role.Config, error) UpsertRoleSkillBinding(ctx context.Context, value role.SkillBinding, changedBy string) (role.SkillBinding, error) ListRoleSkillBindings(ctx context.Context, roleName string) ([]role.SkillBinding, error) } type SkillRepository interface { GetSkillByKey(ctx context.Context, skillKey string) (skill.Definition, error) ListSkillsByIDs(ctx context.Context, ids []string) (map[string]skill.Definition, error) } type Detail struct { Role role.Definition `json:"role"` Prompts []role.Prompt `json:"prompts"` Config role.Config `json:"config"` Bindings []role.SkillBinding `json:"bindings"` Workspace string `json:"workspace"` } type Service struct { repo Repository skills SkillRepository clock timeutil.Clock } func NewService(repo Repository, skills SkillRepository, clock timeutil.Clock) *Service { if clock == nil { clock = timeutil.SystemClock{} } return &Service{ repo: repo, skills: skills, clock: clock, } } func (s *Service) List(ctx context.Context) ([]role.Definition, error) { return s.repo.ListRoles(ctx) } func (s *Service) GetDetail(ctx context.Context, roleName, workspaceID string) (Detail, error) { definition, err := s.repo.GetRole(ctx, roleName) if err != nil { return Detail{}, err } prompts, err := s.repo.ListRolePrompts(ctx, roleName) if err != nil { return Detail{}, err } config, err := s.repo.GetRoleConfig(ctx, roleName) if err != nil { return Detail{}, err } bindings, err := s.repo.ListRoleSkillBindings(ctx, roleName) if err != nil { return Detail{}, err } return Detail{ Role: definition, Prompts: prompts, Config: config, Bindings: bindings, Workspace: workspaceID, }, nil } func (s *Service) Upsert(ctx context.Context, value role.Definition, changedBy string) (role.Definition, error) { return s.repo.UpsertRole(ctx, value, changedBy) } func (s *Service) UpsertPrompt(ctx context.Context, value role.Prompt, changedBy string) (role.Prompt, error) { return s.repo.UpsertRolePrompt(ctx, value, changedBy) } func (s *Service) UpsertConfig(ctx context.Context, value role.Config, changedBy string) (role.Config, error) { return s.repo.UpsertRoleConfig(ctx, value, changedBy) } func (s *Service) UpsertSkillBinding(ctx context.Context, roleName, skillKey string, value role.SkillBinding, changedBy string) (role.SkillBinding, error) { skillDef, err := s.skills.GetSkillByKey(ctx, skillKey) if err != nil { return role.SkillBinding{}, fmt.Errorf("get skill %q: %w", skillKey, err) } value.RoleName = roleName value.SkillID = skillDef.ID return s.repo.UpsertRoleSkillBinding(ctx, value, changedBy) }