# Inspector Feature Implementation Plan

## Overview
Add a model inspection feature that validates database schemas against configurable rules. The inspector will read any supported format, apply validation rules from a YAML config, and output a report in markdown or JSON format.

## Architecture

### Core Components

1. **CLI Command** (`cmd/relspec/inspect.go`)
   - New subcommand: `relspec inspect`
   - Flags:
     - `--from` (required): Input format (dbml, pgsql, json, etc.)
     - `--from-path`: File path for file-based formats
     - `--from-conn`: Connection string for database formats
     - `--rules` (optional): Path to rules YAML file (default: `.relspec-rules.yaml`)
     - `--output-format`: Report format (markdown, json) (default: markdown)
     - `--output`: Output file path (default: stdout)
     - `--schema`: Schema name filter (optional)

2. **Inspector Package** (`pkg/inspector/`)
   - `inspector.go`: Main inspector logic
   - `rules.go`: Rule definitions and configuration
   - `validators.go`: Individual validation rule implementations
   - `report.go`: Report generation (markdown, JSON)
   - `config.go`: YAML config loading and parsing

### Data Flow
```
Input Format → Reader → Database Model → Inspector → Validation Results → Report Formatter → Output
```

## Rules Configuration Structure

### YAML Schema (`rules.yaml`)
```yaml
version: "1.0"
rules:
  # Primary Key Rules
  primary_key_naming:
    enabled: enforce|warn|off
    pattern: "^id_"  # regex pattern
    message: "Primary key columns must start with 'id_'"

  primary_key_datatype:
    enabled: enforce|warn|off
    allowed_types: ["bigserial", "bigint", "int", "serial", "integer"]
    message: "Primary keys must use approved integer types"

  primary_key_auto_increment:
    enabled: enforce|warn|off
    require_auto_increment: true|false
    message: "Primary keys without auto-increment detected"

  # Foreign Key Rules
  foreign_key_column_naming:
    enabled: enforce|warn|off
    pattern: "^rid_"
    message: "Foreign key columns must start with 'rid_'"

  foreign_key_constraint_naming:
    enabled: enforce|warn|off
    pattern: "^fk_"
    message: "Foreign key constraint names must start with 'fk_'"

  foreign_key_index:
    enabled: enforce|warn|off
    require_index: true
    message: "Foreign keys should have indexes"

  # Naming Convention Rules
  table_naming_case:
    enabled: enforce|warn|off
    case: "lowercase"  # lowercase, uppercase, snake_case, camelCase
    pattern: "^[a-z][a-z0-9_]*$"
    message: "Table names must be lowercase with underscores"

  column_naming_case:
    enabled: enforce|warn|off
    case: "lowercase"
    pattern: "^[a-z][a-z0-9_]*$"
    message: "Column names must be lowercase with underscores"

  # Length Rules
  table_name_length:
    enabled: enforce|warn|off
    max_length: 64
    message: "Table name exceeds maximum length"

  column_name_length:
    enabled: enforce|warn|off
    max_length: 64
    message: "Column name exceeds maximum length"

  # Reserved Keywords
  reserved_keywords:
    enabled: enforce|warn|off
    check_tables: true
    check_columns: true
    message: "Using reserved SQL keywords"

  # Schema Integrity Rules
  missing_primary_key:
    enabled: enforce|warn|off
    message: "Table missing primary key"

  orphaned_foreign_key:
    enabled: enforce|warn|off
    message: "Foreign key references non-existent table"

  circular_dependency:
    enabled: enforce|warn|off
    message: "Circular foreign key dependency detected"
```

### Rule Levels
- **enforce**: Violations are errors (exit code 1)
- **warn**: Violations are warnings (exit code 0)
- **off**: Rule disabled

## Implementation Details

### 1. Inspector Core (`pkg/inspector/inspector.go`)

```go
type Inspector struct {
    config *Config
    db     *models.Database
}

type ValidationResult struct {
    RuleName    string
    Level       string // "error" or "warning"
    Message     string
    Location    string // e.g., "schema.table.column"
    Context     map[string]interface{}
    Passed      bool
}

type InspectorReport struct {
    Summary     ReportSummary
    Violations  []ValidationResult
    GeneratedAt time.Time
    Database    string
    SourceFormat string
}

type ReportSummary struct {
    TotalRules      int
    RulesChecked    int
    RulesSkipped    int
    ErrorCount      int
    WarningCount    int
    PassedCount     int
}

func NewInspector(db *models.Database, config *Config) *Inspector
func (i *Inspector) Inspect() (*InspectorReport, error)
func (i *Inspector) validateDatabase() []ValidationResult
func (i *Inspector) validateSchema(schema *models.Schema) []ValidationResult
func (i *Inspector) validateTable(table *models.Table) []ValidationResult
```

### 2. Rule Definitions (`pkg/inspector/rules.go`)

```go
type Config struct {
    Version string
    Rules   map[string]Rule
}

type Rule struct {
    Enabled       string // "enforce", "warn", "off"
    Message       string
    Pattern       string
    AllowedTypes  []string
    MaxLength     int
    Case          string
    RequireIndex  bool
    CheckTables   bool
    CheckColumns  bool
    // ... rule-specific fields
}

type RuleValidator interface {
    Name() string
    Validate(db *models.Database, rule Rule) []ValidationResult
}

func LoadConfig(path string) (*Config, error)
func GetDefaultConfig() *Config
```

**Configuration Loading Behavior:**
- If `--rules` flag is provided but file not found: Use default configuration (don't error)
- If file exists but is invalid YAML: Return error
- Default configuration has sensible rules enabled at "warn" level
- Users can override by creating their own `.relspec-rules.yaml` file

### 3. Validators (`pkg/inspector/validators.go`)

Each validator implements rule logic:

```go
// Primary Key Validators
func validatePrimaryKeyNaming(db *models.Database, rule Rule) []ValidationResult
func validatePrimaryKeyDatatype(db *models.Database, rule Rule) []ValidationResult
func validatePrimaryKeyAutoIncrement(db *models.Database, rule Rule) []ValidationResult

// Foreign Key Validators
func validateForeignKeyColumnNaming(db *models.Database, rule Rule) []ValidationResult
func validateForeignKeyConstraintNaming(db *models.Database, rule Rule) []ValidationResult
func validateForeignKeyIndex(db *models.Database, rule Rule) []ValidationResult

// Naming Convention Validators
func validateTableNamingCase(db *models.Database, rule Rule) []ValidationResult
func validateColumnNamingCase(db *models.Database, rule Rule) []ValidationResult

// Length Validators
func validateTableNameLength(db *models.Database, rule Rule) []ValidationResult
func validateColumnNameLength(db *models.Database, rule Rule) []ValidationResult

// Reserved Keywords Validator
func validateReservedKeywords(db *models.Database, rule Rule) []ValidationResult

// Integrity Validators
func validateMissingPrimaryKey(db *models.Database, rule Rule) []ValidationResult
func validateOrphanedForeignKey(db *models.Database, rule Rule) []ValidationResult
func validateCircularDependency(db *models.Database, rule Rule) []ValidationResult

// Registry of all validators
var validators = map[string]RuleValidator{
    "primary_key_naming": primaryKeyNamingValidator{},
    // ...
}
```

### 4. Report Formatting (`pkg/inspector/report.go`)

```go
type ReportFormatter interface {
    Format(report *InspectorReport) (string, error)
}

type MarkdownFormatter struct {
    UseColors bool  // ANSI colors for terminal output
}
type JSONFormatter struct{}

func (f *MarkdownFormatter) Format(report *InspectorReport) (string, error)
func (f *JSONFormatter) Format(report *InspectorReport) (string, error)

// Helper to detect if output is a TTY (terminal)
func isTerminal(w io.Writer) bool
```

**Output Behavior:**
- Markdown format will use ANSI color codes when outputting to a terminal (TTY)
- When piped or redirected to a file, plain markdown without colors
- Colors: Red for errors, Yellow for warnings, Green for passed checks

**Markdown Format Example:**
```markdown
# RelSpec Inspector Report

**Database:** my_database
**Source Format:** pgsql
**Generated:** 2025-12-31 10:30:45

## Summary
- Rules Checked: 12
- Errors: 3
- Warnings: 5
- Passed: 4

## Violations

### Errors (3)

#### primary_key_naming
**Table:** users, **Column:** user_id
Primary key columns must start with 'id_'

#### table_name_length
**Table:** user_authentication_sessions_with_metadata
Table name exceeds maximum length (64 characters)

### Warnings (5)

#### foreign_key_index
**Table:** orders, **Column:** customer_id
Foreign keys should have indexes

...
```

**JSON Format Example:**
```json
{
  "summary": {
    "total_rules": 12,
    "rules_checked": 12,
    "error_count": 3,
    "warning_count": 5,
    "passed_count": 4
  },
  "violations": [
    {
      "rule_name": "primary_key_naming",
      "level": "error",
      "message": "Primary key columns must start with 'id_'",
      "location": "public.users.user_id",
      "context": {
        "schema": "public",
        "table": "users",
        "column": "user_id",
        "current_name": "user_id",
        "expected_pattern": "^id_"
      },
      "passed": false
    }
  ],
  "generated_at": "2025-12-31T10:30:45Z",
  "database": "my_database",
  "source_format": "pgsql"
}
```

### 5. CLI Command (`cmd/relspec/inspect.go`)

```go
var inspectCmd = &cobra.Command{
    Use:   "inspect",
    Short: "Inspect and validate database schemas against rules",
    Long:  `Read database schemas from various formats and validate against configurable rules.`,
    RunE:  runInspect,
}

func init() {
    inspectCmd.Flags().String("from", "", "Input format (dbml, pgsql, json, etc.)")
    inspectCmd.Flags().String("from-path", "", "Input file path")
    inspectCmd.Flags().String("from-conn", "", "Database connection string")
    inspectCmd.Flags().String("rules", ".relspec-rules.yaml", "Rules configuration file")
    inspectCmd.Flags().String("output-format", "markdown", "Output format (markdown, json)")
    inspectCmd.Flags().String("output", "", "Output file (default: stdout)")
    inspectCmd.Flags().String("schema", "", "Filter by schema name")
    inspectCmd.MarkFlagRequired("from")
}

func runInspect(cmd *cobra.Command, args []string) error {
    // 1. Parse flags
    // 2. Create reader (reuse pattern from convert.go)
    // 3. Read database
    // 4. Load rules config (use defaults if file not found)
    // 5. Create inspector
    // 6. Run inspection
    // 7. Detect if output is terminal (for color support)
    // 8. Format report (with/without ANSI colors)
    // 9. Write output
    // 10. Exit with appropriate code (0 if no errors, 1 if errors)
}
```

## Implementation Phases

### Phase 1: Core Infrastructure
1. Create `pkg/inspector/` package structure
2. Implement `Config` and YAML loading
3. Implement `Inspector` core with basic validation framework
4. Create CLI command skeleton

### Phase 2: Basic Validators
1. Implement naming convention validators
   - Primary key naming
   - Foreign key column naming
   - Foreign key constraint naming
   - Table/column case validation
2. Implement length validators
3. Implement reserved keywords validator (leverage `pkg/pgsql/keywords.go`)

### Phase 3: Advanced Validators
1. Implement datatype validators
2. Implement integrity validators (missing PK, orphaned FK, circular deps)
3. Implement foreign key index validator

### Phase 4: Reporting
1. Implement `InspectorReport` structure
2. Implement markdown formatter
3. Implement JSON formatter
4. Add summary statistics

### Phase 5: CLI Integration
1. Wire up CLI command with flags
2. Integrate reader factory (from convert.go pattern)
3. Add output file handling
4. Add exit code logic
5. Add progress reporting

### Phase 6: Testing & Documentation
1. Unit tests for validators
2. Integration tests with sample schemas
3. Test with all reader formats
4. Update README with inspector documentation
5. Create example rules configuration file

## Files to Create

1. `pkg/inspector/inspector.go` - Core inspector logic
2. `pkg/inspector/rules.go` - Rule definitions and config loading
3. `pkg/inspector/validators.go` - Validation implementations
4. `pkg/inspector/report.go` - Report formatting
5. `pkg/inspector/config.go` - Config utilities
6. `cmd/relspec/inspect.go` - CLI command
7. `.relspec-rules.yaml.example` - Example configuration
8. `pkg/inspector/inspector_test.go` - Tests

## Files to Modify

1. `cmd/relspec/root.go` - Register inspect command
2. `README.md` - Add inspector documentation (if requested)

## Example Usage

```bash
# Inspect a PostgreSQL database with default rules
relspec inspect --from pgsql --from-conn "postgresql://localhost/mydb"

# Inspect a DBML file with custom rules
relspec inspect --from dbml --from-path schema.dbml --rules my-rules.yaml

# Output JSON report to file
relspec inspect --from json --from-path db.json --output-format json --output report.json

# Inspect specific schema only
relspec inspect --from pgsql --from-conn "..." --schema public

# Use custom rules location
relspec inspect --from dbml --from-path schema.dbml --rules /path/to/rules.yaml
```

## Exit Codes
- 0: Success (no errors, only warnings or all passed)
- 1: Validation errors found (rules with level="enforce" failed)
- 2: Runtime error (invalid config, reader error, etc.)

## Dependencies
- Existing: `pkg/models`, `pkg/readers`, `pkg/pgsql/keywords.go`
- New: `gopkg.in/yaml.v3` for YAML parsing (may already be in go.mod)

## Design Decisions

### Confirmed Choices (from user)
1. **Example config file**: Create `.relspec-rules.yaml.example` in repository root with documented examples
2. **Missing rules file**: Use sensible built-in defaults (don't error), all rules at "warn" level by default
3. **Terminal output**: ANSI colors (red/yellow/green) when outputting to terminal, plain markdown when piped/redirected
4. **Foreign key naming**: Separate configurable rules for both FK column names and FK constraint names

### Architecture Rationale
1. **Why YAML for config?**: Human-readable, supports comments, standard for config files
2. **Why three levels (enforce/warn/off)?**: Flexibility for gradual adoption, different contexts
3. **Why markdown + JSON?**: Markdown for human review, JSON for tooling integration
4. **Why pkg/inspector?**: Follows existing package structure, separates concerns
5. **Reuse readers**: Leverage existing reader infrastructure, supports all formats automatically
6. **Exit codes**: Follow standard conventions (0=success, 1=validation fail, 2=error)

## Future Enhancements (Not in Scope)
- Auto-fix mode (automatically rename columns, etc.)
- Custom rule plugins
- HTML report format
- Rule templates for different databases
- CI/CD integration examples
- Performance metrics in report
