Skip to content

Commit 0368043

Browse files
committed
feat: unify validation process with RBAC support and enhance validator functionality
1 parent 2b7e0e4 commit 0368043

File tree

5 files changed

+409
-24
lines changed

5 files changed

+409
-24
lines changed

internal/cmd/validate.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,14 @@ func runValidate(cmd *cobra.Command, args []string) error {
106106

107107
fmt.Printf("Found %d changed file(s)\n", len(changes))
108108

109-
// Create validator
110-
v := validator.NewLLMValidator(llmClient, &policy)
109+
// Create unified validator that handles all engines + RBAC
110+
v := validator.NewValidator(&policy, true) // verbose=true for CLI
111+
v.SetLLMClient(llmClient)
112+
defer v.Close()
111113

112114
// Validate changes
113115
ctx := context.Background()
114-
result, err := v.Validate(ctx, changes)
116+
result, err := v.ValidateChanges(ctx, changes)
115117
if err != nil {
116118
return fmt.Errorf("validation failed: %w", err)
117119
}

internal/git/repo.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,13 @@ func IsGitRepo() bool {
5454
err := cmd.Run()
5555
return err == nil
5656
}
57+
58+
// GetCurrentUser returns the current git user name
59+
func GetCurrentUser() (string, error) {
60+
cmd := exec.Command("git", "config", "--get", "user.name")
61+
output, err := cmd.Output()
62+
if err != nil {
63+
return "", fmt.Errorf("failed to get git user.name: %w", err)
64+
}
65+
return strings.TrimSpace(string(output)), nil
66+
}

internal/mcp/server.go

Lines changed: 83 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/DevSymphony/sym-cli/internal/git"
1616
"github.com/DevSymphony/sym-cli/internal/llm"
1717
"github.com/DevSymphony/sym-cli/internal/policy"
18+
"github.com/DevSymphony/sym-cli/internal/roles"
1819
"github.com/DevSymphony/sym-cli/internal/validator"
1920
"github.com/DevSymphony/sym-cli/pkg/schema"
2021
sdkmcp "github.com/modelcontextprotocol/go-sdk/mcp"
@@ -446,9 +447,16 @@ func (s *Server) handleQueryConventions(params map[string]interface{}) (interfac
446447
}
447448
textContent += "\n"
448449
}
449-
textContent += "\n✓ Next Step: Implement your code following these conventions. After completion, MUST call validate_code to verify compliance."
450450
}
451451

452+
// Add RBAC information if available
453+
rbacInfo := s.getRBACInfo()
454+
if rbacInfo != "" {
455+
textContent += "\n\n" + rbacInfo
456+
}
457+
458+
textContent += "\n✓ Next Step: Implement your code following these conventions. After completion, MUST call validate_code to verify compliance."
459+
452460
// Return MCP-compliant response with content array
453461
return map[string]interface{}{
454462
"content": []map[string]interface{}{
@@ -635,11 +643,15 @@ func (s *Server) handleValidateCode(params map[string]interface{}) (interface{},
635643
}
636644

637645
llmClient := llm.NewClient(apiKey)
638-
llmValidator := validator.NewLLMValidator(llmClient, validationPolicy)
639646

640-
// Validate git changes
647+
// Create unified validator that handles all engines + RBAC
648+
v := validator.NewValidator(validationPolicy, false) // verbose=false for MCP
649+
v.SetLLMClient(llmClient)
650+
defer v.Close()
651+
652+
// Validate git changes using unified validator
641653
ctx := context.Background()
642-
result, err := llmValidator.Validate(ctx, changes)
654+
result, err := v.ValidateChanges(ctx, changes)
643655
if err != nil {
644656
return nil, &RPCError{
645657
Code: -32000,
@@ -904,3 +916,70 @@ func (s *Server) needsConversion(codePolicyPath string) bool {
904916
func (s *Server) convertUserPolicy(userPolicyPath, codePolicyPath string) error {
905917
return ConvertPolicyWithLLM(userPolicyPath, codePolicyPath)
906918
}
919+
920+
// getRBACInfo returns RBAC information for the current user
921+
func (s *Server) getRBACInfo() string {
922+
// Try to get current user
923+
username, err := git.GetCurrentUser()
924+
if err != nil {
925+
// Not in a git environment or user not configured
926+
return ""
927+
}
928+
929+
// Get user's role
930+
userRole, err := roles.GetUserRole(username)
931+
if err != nil {
932+
// Roles not configured
933+
return ""
934+
}
935+
936+
if userRole == "none" {
937+
return fmt.Sprintf("⚠️ RBAC: User '%s' has no assigned role. You may not have permission to modify files.", username)
938+
}
939+
940+
// Load user policy to get RBAC details
941+
userPolicy, err := roles.LoadUserPolicyFromRepo()
942+
if err != nil {
943+
// User policy not available
944+
return fmt.Sprintf("🔐 RBAC: Current user '%s' has role '%s'", username, userRole)
945+
}
946+
947+
// Check if RBAC is defined
948+
if userPolicy.RBAC == nil || userPolicy.RBAC.Roles == nil {
949+
return fmt.Sprintf("🔐 RBAC: Current user '%s' has role '%s' (no restrictions defined)", username, userRole)
950+
}
951+
952+
// Get role configuration
953+
roleConfig, exists := userPolicy.RBAC.Roles[userRole]
954+
if !exists {
955+
return fmt.Sprintf("⚠️ RBAC: User '%s' has role '%s', but role is not defined in policy", username, userRole)
956+
}
957+
958+
// Build RBAC info message
959+
var rbacMsg strings.Builder
960+
rbacMsg.WriteString("🔐 RBAC Information:\n")
961+
rbacMsg.WriteString(fmt.Sprintf(" User: %s\n", username))
962+
rbacMsg.WriteString(fmt.Sprintf(" Role: %s\n", userRole))
963+
964+
if len(roleConfig.AllowWrite) > 0 {
965+
rbacMsg.WriteString(fmt.Sprintf(" Allowed paths: %s\n", strings.Join(roleConfig.AllowWrite, ", ")))
966+
} else {
967+
rbacMsg.WriteString(" Allowed paths: All files (no restrictions)\n")
968+
}
969+
970+
if len(roleConfig.DenyWrite) > 0 {
971+
rbacMsg.WriteString(fmt.Sprintf(" Denied paths: %s\n", strings.Join(roleConfig.DenyWrite, ", ")))
972+
}
973+
974+
if roleConfig.CanEditPolicy {
975+
rbacMsg.WriteString(" Can edit policy: Yes\n")
976+
}
977+
978+
if roleConfig.CanEditRoles {
979+
rbacMsg.WriteString(" Can edit roles: Yes\n")
980+
}
981+
982+
rbacMsg.WriteString("\n⚠️ Note: Modifications to denied paths will be blocked during validation.")
983+
984+
return rbacMsg.String()
985+
}

internal/validator/llm_validator.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ func (v *LLMValidator) Validate(ctx context.Context, changes []GitChange) (*Vali
6969
for _, rule := range llmRules {
7070
result.Checked++
7171

72-
violation, err := v.checkRule(ctx, change, addedLines, rule)
72+
violation, err := v.CheckRule(ctx, change, addedLines, rule)
7373
if err != nil {
7474
// Log error but continue
7575
fmt.Printf("Warning: failed to check rule %s: %v\n", rule.ID, err)
@@ -106,8 +106,9 @@ func (v *LLMValidator) filterLLMRules() []schema.PolicyRule {
106106
return llmRules
107107
}
108108

109-
// checkRule checks if code violates a specific rule using LLM
110-
func (v *LLMValidator) checkRule(ctx context.Context, change GitChange, addedLines []string, rule schema.PolicyRule) (*Violation, error) {
109+
// CheckRule checks if code violates a specific rule using LLM
110+
// This is the single source of truth for LLM-based validation logic
111+
func (v *LLMValidator) CheckRule(ctx context.Context, change GitChange, addedLines []string, rule schema.PolicyRule) (*Violation, error) {
111112
// Build prompt for LLM
112113
systemPrompt := `You are a code reviewer. Check if the code changes violate the given coding convention.
113114

0 commit comments

Comments
 (0)