Data Model: Mission Schema & Validation Models

Feature: 005-refactor-mission-system Date: 2025-01-16

This document defines the data structures for mission configuration validation, citation tracking, and path enforcement.


Mission Configuration Schema

Core Model Hierarchy

MissionConfig (root)

 WorkflowConfig
    List[PhaseConfig]

 ArtifactsConfig

 required: List[str]
    optional: List[str]

 PathsConfig (Dict[str, str])

 ValidationConfig

 checks: List[str]
    custom_validators: bool

 MCPToolsConfig

 required: List[str]

 recommended: List[str]
    optional: List[str]

 TaskMetadataConfig

 required: List[str]
    optional: List[str]
 CommandsConfig (Dict[str, CommandConfig])

Pydantic Model Definitions

PhaseConfig
from pydantic import BaseModel, Field

class PhaseConfig(BaseModel):
    """Workflow phase definition."""
    name: str = Field(..., description="Phase identifier (e.g., 'design', 'implement')")
    description: str = Field(..., description="Human-readable phase description")

    class Config:
        extra = "forbid"  # Reject unknown fields

Validation:

  • Both name and description required
  • name should be lowercase-hyphenated (e.g., "implement", "code-review")
  • No extra fields allowed

Error Examples:

# Missing description
ValidationError: 1 validation error for PhaseConfig
description
  Field required

# Extra field
ValidationError: 1 validation error for PhaseConfig
priority
  Extra inputs are not permitted

ArtifactsConfig
class ArtifactsConfig(BaseModel):
    """Required and optional artifacts for mission."""
    required: List[str] = Field(default_factory=list, description="Must exist for acceptance")
    optional: List[str] = Field(default_factory=list, description="Nice to have")

    class Config:
        extra = "forbid"

Validation:

  • required and optional are both lists of strings
  • Defaults to empty list if not specified
  • Artifacts can be files (spec.md) or directories (contracts/)

ValidationConfig
class ValidationConfig(BaseModel):
    """Validation rules for mission."""
    checks: List[str] = Field(default_factory=list, description="Validation check names")
    custom_validators: bool = Field(default=False, description="Use validators.py module")

    class Config:
        extra = "forbid"

Known Validation Checks (not enforced by schema, but documented):

  • Software-dev: git_clean, all_tests_pass, kanban_complete, no_clarification_markers
  • Research: all_sources_documented, methodology_clear, findings_synthesized, no_unresolved_questions

WorkflowConfig
class WorkflowConfig(BaseModel):
    """Workflow configuration."""
    phases: List[PhaseConfig] = Field(..., min_length=1, description="Workflow phases")

    class Config:
        extra = "forbid"

Validation:

  • At least one phase required
  • Each phase must be valid PhaseConfig
  • Order matters (phases executed sequentially)

MCPToolsConfig
class MCPToolsConfig(BaseModel):
    """MCP tools configuration."""
    required: List[str] = Field(default_factory=list)
    recommended: List[str] = Field(default_factory=list)
    optional: List[str] = Field(default_factory=list)

    class Config:
        extra = "forbid"

Note: This configuration is currently documentation-only (per research, MCP integration is deferred). Model exists for future integration.


CommandConfig
class CommandConfig(BaseModel):
    """Command customization."""
    prompt: str = Field(..., description="Command-specific prompt/description")

    class Config:
        extra = "forbid"

TaskMetadataConfig
class TaskMetadataConfig(BaseModel):
    """Task metadata field definitions."""
    required: List[str] = Field(default_factory=list)
    optional: List[str] = Field(default_factory=list)

    class Config:
        extra = "forbid"

MissionConfig (Root)
from typing import Literal, Dict, Optional

class MissionConfig(BaseModel):
    """Complete mission configuration schema."""

    # Required fields
    name: str = Field(..., description="Mission display name")
    description: str = Field(..., description="Mission description")
    version: str = Field(..., pattern=r'^\d+\.\d+\.\d+$', description="Semver version")
    domain: Literal["software", "research", "writing", "seo", "other"] = Field(
        ..., description="Mission domain"
    )

    # Workflow configuration
    workflow: WorkflowConfig = Field(..., description="Workflow phases")

    # Artifacts configuration
    artifacts: ArtifactsConfig = Field(..., description="Required and optional artifacts")

    # Path conventions
    paths: Dict[str, str] = Field(
        default_factory=dict,
        description="Path conventions (workspace, tests, deliverables, documentation)"
    )

    # Validation configuration
    validation: ValidationConfig = Field(
        default_factory=ValidationConfig,
        description="Validation rules"
    )

    # MCP tools (optional)
    mcp_tools: Optional[MCPToolsConfig] = Field(
        default=None,
        description="MCP tools recommendations"
    )

    # Agent context (optional)
    agent_context: Optional[str] = Field(
        default=None,
        description="Agent personality/instructions"
    )

    # Task metadata (optional)
    task_metadata: Optional[TaskMetadataConfig] = Field(
        default=None,
        description="Task metadata field definitions"
    )

    # Command customization (optional)
    commands: Optional[Dict[str, CommandConfig]] = Field(
        default=None,
        description="Command-specific configurations"
    )

    class Config:
        extra = "forbid"  # Catch typos like "validaton:"

    def model_post_init(self, __context) -> None:
        """Post-initialization validation."""
        # Validate known path conventions
        valid_path_keys = {"workspace", "tests", "deliverables", "documentation", "data"}
        unknown_paths = set(self.paths.keys()) - valid_path_keys
        if unknown_paths:
            # Warning only, don't fail (allows custom paths)
            import warnings
            warnings.warn(
                f"Unknown path conventions: {unknown_paths}. "
                f"Known conventions: {valid_path_keys}"
            )

Validation Behavior:

  • Missing required field ’ ValidationError with field name
  • Typo in field name ’ "Extra inputs are not permitted" with field name
  • Wrong type ’ Type error with expected type
  • Invalid enum value ’ Lists valid options
  • Invalid version format ’ Pattern mismatch with expected format

Example Errors:

# Missing required field 'name'
ValidationError: 1 validation error for MissionConfig
name
  Field required [type=missing]

# Typo 'validaton' instead of 'validation'
ValidationError: 1 validation error for MissionConfig
validaton
  Extra inputs are not permitted [type=extra_forbidden]

# Invalid domain
ValidationError: 1 validation error for MissionConfig
domain
  Input should be 'software', 'research', 'writing', 'seo' or 'other' [type=literal_error, input_value='coding']

# Invalid version format
ValidationError: 1 validation error for MissionConfig
version
  String should match pattern '^\d+\.\d+\.\d+$' [type=string_pattern_mismatch, input_value='1.0']

Mission Runtime Model

Enhanced Mission Class

from pathlib import Path
from typing import Optional

class Mission:
    """Enhanced mission with validation."""

    def __init__(self, mission_path: Path):
        self.path = mission_path.resolve()

        if not self.path.exists():
            raise MissionNotFoundError(f"Mission directory not found: {self.path}")

        # Load and validate config
        self.config = self._load_and_validate_config()

    def _load_and_validate_config(self) -> MissionConfig:
        """Load mission.yaml and validate with Pydantic."""
        config_file = self.path / "mission.yaml"

        if not config_file.exists():
            raise MissionNotFoundError(
                f"Mission config not found: {config_file}\n"
                f"Expected mission.yaml in mission directory"
            )

        with open(config_file, 'r') as f:
            try:
                raw_config = yaml.safe_load(f)
            except yaml.YAMLError as e:
                raise MissionError(f"Invalid YAML syntax: {e}")

        # Pydantic validation
        try:
            return MissionConfig(**raw_config)
        except ValidationError as e:
            # Format error nicely
            raise MissionError(
                f"Invalid mission configuration in {config_file}:\n"
                f"{e}\n\n"
                f"See mission.yaml schema documentation for details."
            )

    # Typed property accessors
    @property
    def name(self) -> str:
        return self.config.name

    @property
    def domain(self) -> str:
        return self.config.domain

    @property
    def workflow_phases(self) -> List[PhaseConfig]:
        return self.config.workflow.phases

    # ... other properties use config.field for type safety

Benefits:

  • Type safety throughout codebase
  • Validation on construction
  • Clear error messages
  • IDE auto-completion

Citation Data Models

Evidence Log Schema

File: research/evidence-log.csv

Columns:

from dataclasses import dataclass
from datetime import datetime
from typing import Literal

@dataclass
class EvidenceEntry:
    """Single entry in evidence log."""
    timestamp: datetime
    source_type: Literal["journal", "conference", "book", "web", "preprint"]
    citation: str  # BibTeX, APA, or Simple format
    key_finding: str
    confidence: Literal["high", "medium", "low"]
    notes: str

CSV Format:

timestamp,source_type,citation,key_finding,confidence,notes
2025-01-16T10:30:00,journal,"Author (2025). Title. Journal.",Finding text,high,Additional notes

Validation Rules: 1. Citation non-empty 2. source_type in allowed values 3. confidence in allowed values 4. timestamp is valid ISO format


Source Register Schema

File: research/source-register.csv

Columns:

@dataclass
class SourceEntry:
    """Single entry in source register."""
    source_id: str  # Unique identifier
    citation: str  # Full citation
    url: str  # Optional URL
    accessed_date: datetime  # When source was accessed
    relevance: Literal["high", "medium", "low"]
    status: Literal["reviewed", "pending", "archived"]

CSV Format:

source_id,citation,url,accessed_date,relevance,status
smith2025,Smith (2025). Paper title.,https://...,2025-01-16,high,reviewed

Validation Rules: 1. source_id is unique 2. citation non-empty 3. relevance in allowed values 4. status in allowed values


Path Validation Models

PathValidationResult

from dataclasses import dataclass
from typing import List

@dataclass
class PathValidationResult:
    """Result of path convention validation."""
    mission_name: str
    required_paths: Dict[str, str]  # key ’ path
    existing_paths: List[str]  # Paths that exist
    missing_paths: List[str]  # Paths that don't exist
    warnings: List[str]  # Warning messages
    suggestions: List[str]  # Suggested fixes

    @property
    def is_valid(self) -> bool:
        """True if all required paths exist."""
        return len(self.missing_paths) == 0

    def format_warnings(self) -> str:
        """Format warnings for display."""
        if not self.warnings:
            return ""

        output = ["Path Convention Warnings:"]
        for warning in self.warnings:
            output.append(f"  - {warning}")
        output.append("")
        output.append("Suggestions:")
        for suggestion in self.suggestions:
            output.append(f"  - {suggestion}")
        return "\n".join(output)

Example Output:

Path Convention Warnings:
  - Mission expects workspace directory: src/ (not found)
  - Mission expects tests directory: tests/ (not found)

Suggestions:
  - Create directory: mkdir -p src/
  - Create directory: mkdir -p tests/

Worktree Validation Models

WorktreeValidationResult

@dataclass
class WorktreeValidationResult:
    """Result of worktree location validation."""
    current_branch: str
    is_feature_branch: bool
    is_main_branch: bool
    worktree_path: Optional[Path]
    errors: List[str]

    @property
    def is_valid(self) -> bool:
        """True if in valid worktree location."""
        return self.is_feature_branch and not self.errors

    def format_error(self) -> str:
        """Format error message for display."""
        if not self.errors:
            return ""

        output = [
            "Location Pre-flight Check Failed:",
            ""
        ]
        for error in self.errors:
            output.append(f"  {error}")

        if self.is_main_branch:
            output.extend([
                "",
                "You are on the 'main' branch. Commands must run from feature worktrees.",
                "",
                "Available worktrees:",
                "  $ ls .worktrees/",
                "",
                "Navigate to worktree:",
                "  $ cd .worktrees/<feature-name>",
                "",
                "Verify branch:",
                "  $ git branch --show-current"
            ])

        return "\n".join(output)

Mission Switching Validation Models

MissionSwitchValidation

@dataclass
class MissionSwitchValidation:
    """Validation result for mission switch operation."""
    can_switch: bool
    active_worktrees: List[str]  # Blocking issue
    uncommitted_changes: List[str]  # Blocking issue
    missing_artifacts: List[str]  # Warning only
    target_mission_exists: bool
    warnings: List[str]
    errors: List[str]

    @property
    def has_blocking_issues(self) -> bool:
        """True if switch cannot proceed."""
        return bool(
            self.active_worktrees or
            self.uncommitted_changes or
            not self.target_mission_exists
        )

    def format_result(self) -> str:
        """Format validation result for display."""
        if self.has_blocking_issues:
            output = ["Cannot switch missions:", ""]

            if self.active_worktrees:
                output.append("Active features exist (merge or abandon first):")
                for wt in self.active_worktrees:
                    output.append(f"  - {wt}")
                output.append("")

            if self.uncommitted_changes:
                output.append("Uncommitted changes detected:")
                for change in self.uncommitted_changes:
                    output.append(f"  - {change}")
                output.append("")
                output.append("Commit or stash changes before switching.")
                output.append("")

            if not self.target_mission_exists:
                output.append(f"Target mission does not exist.")
                output.append("")

        elif self.warnings:
            output = ["Mission switch will proceed with warnings:", ""]
            for warning in self.warnings:
                output.append(f"  - {warning}")
            output.append("")

        else:
            output = ["Mission switch validation passed."]

        return "\n".join(output)

Citation Validation Models

CitationValidationResult

from enum import Enum

class CitationFormat(Enum):
    """Supported citation formats."""
    BIBTEX = "bibtex"
    APA = "apa"
    SIMPLE = "simple"
    UNKNOWN = "unknown"

@dataclass
class CitationIssue:
    """Single citation validation issue."""
    line_number: int
    field: str
    issue_type: Literal["error", "warning"]
    message: str

@dataclass
class CitationValidationResult:
    """Result of citation validation."""
    file_path: Path
    total_entries: int
    valid_entries: int
    issues: List[CitationIssue]

    @property
    def has_errors(self) -> bool:
        """True if any errors (not warnings)."""
        return any(issue.issue_type == "error" for issue in self.issues)

    @property
    def error_count(self) -> int:
        return sum(1 for i in self.issues if i.issue_type == "error")

    @property
    def warning_count(self) -> int:
        return sum(1 for i in self.issues if i.issue_type == "warning")

    def format_report(self) -> str:
        """Format validation report."""
        output = [
            f"Citation Validation: {self.file_path.name}",
            f"Total entries: {self.total_entries}",
            f"Valid: {self.valid_entries}",
            f"Errors: {self.error_count}",
            f"Warnings: {self.warning_count}",
            ""
        ]

        if self.issues:
            errors = [i for i in self.issues if i.issue_type == "error"]
            warnings = [i for i in self.issues if i.issue_type == "warning"]

            if errors:
                output.append("ERRORS (must fix):")
                for issue in errors:
                    output.append(f"  Line {issue.line_number} ({issue.field}): {issue.message}")
                output.append("")

            if warnings:
                output.append("WARNINGS (recommended fixes):")
                for issue in warnings:
                    output.append(f"  Line {issue.line_number} ({issue.field}): {issue.message}")

        return "\n".join(output)

Entity Relationships

Mission ’ Templates

  • Relationship: One mission has many templates
  • Storage: {mission.path}/templates/*.md
  • Validation: Check file existence on mission load

Mission ’ Commands

  • Relationship: One mission has many command prompts
  • Storage: {mission.path}/commands/*.md
  • Validation: Check file existence on mission load

Mission ’ Validators

  • Relationship: One mission has optional custom validators
  • Storage: {mission.path}/validators.py
  • Validation: Check custom_validators flag matches file existence

Project ’ Active Mission

  • Relationship: One project has one active mission (via symlink)
  • Storage: .kittify/active-missionmissions/{name}/
  • Validation: Check symlink target exists and is valid mission

Research Mission ’ Evidence Entries

  • Relationship: One research feature has many evidence entries
  • Storage: research/evidence-log.csv
  • Validation: Check citation format and completeness

State Transitions

Mission Configuration Lifecycle

[mission.yaml created]
         “
[Loaded by Mission.__init__]
         “
[Parsed by yaml.safe_load]
         “
[Validated by MissionConfig(**data)]

 VALID ’ Mission object ready
          INVALID ’ ValidationError with details

Mission Switching State Machine

[User runs: spec-kitty mission switch <name>]
         “
[Validate: no active worktrees]

 FAIL ’ Error: "Active features exist"
          PASS ’ Continue
         “
[Validate: git is clean]

 FAIL ’ Error: "Uncommitted changes"
          PASS ’ Continue
         “
[Validate: target mission exists]

 FAIL ’ Error: "Mission not found"
          PASS ’ Continue
         “
[Check: new mission requires artifacts]

 Missing ’ Warning: "Artifacts not found"
          All present ’ No warning
         “
[Update: .kittify/active-mission symlink]
         “
[SUCCESS: Mission switched]

Path Validation States

[Mission Switch or Acceptance]
         “
[Load: mission.paths from config]
         “
[Check: each path exists]

 All exist ’ PASS
          Some missing ’ Generate warnings/errors
         “
[Context: Mission Switch]
          Missing paths ’ WARNING (non-blocking)

[Context: Acceptance]
          Missing paths ’ ERROR (blocking)

Data Validation Patterns

mission.yaml Validation Pattern

def load_validated_mission(mission_path: Path) -> Mission:
    """Load and validate mission configuration."""
    try:
        return Mission(mission_path)
    except ValidationError as e:
        # Format Pydantic errors nicely
        print("[red]Mission configuration invalid:[/red]")
        for error in e.errors():
            field = " ’ ".join(str(x) for x in error['loc'])
            msg = error['msg']
            print(f"  [yellow]{field}[/yellow]: {msg}")
        raise
    except MissionError as e:
        print(f"[red]Mission loading failed:[/red] {e}")
        raise

Citation Validation Pattern

def validate_research_citations(feature_dir: Path) -> CitationValidationResult:
    """Validate all citations in research feature."""
    evidence_log = feature_dir / "research" / "evidence-log.csv"

    if not evidence_log.exists():
        return CitationValidationResult(
            file_path=evidence_log,
            total_entries=0,
            valid_entries=0,
            issues=[CitationIssue(
                line_number=0,
                field="file",
                issue_type="error",
                message="evidence-log.csv not found"
            )]
        )

    issues = []
    total = 0
    valid = 0

    with open(evidence_log) as f:
        reader = csv.DictReader(f)
        for i, row in enumerate(reader, start=2):
            total += 1
            entry_issues = validate_citation_entry(row, i)
            if not entry_issues:
                valid += 1
            issues.extend(entry_issues)

    return CitationValidationResult(
        file_path=evidence_log,
        total_entries=total,
        valid_entries=valid,
        issues=issues
    )

Path Validation Pattern

def validate_mission_paths(
    mission: Mission,
    project_root: Path,
    strict: bool = False
) -> PathValidationResult:
    """Validate project structure matches mission path conventions.

    Args:
        mission: Active mission
        project_root: Project root directory
        strict: If True, errors on missing paths. If False, warnings only.
    """
    required_paths = mission.config.paths
    existing = []
    missing = []
    warnings = []
    suggestions = []

    for key, path_str in required_paths.items():
        full_path = project_root / path_str
        if full_path.exists():
            existing.append(path_str)
        else:
            missing.append(path_str)
            warning_msg = f"Mission expects {key} directory: {path_str} (not found)"
            warnings.append(warning_msg)
            suggestions.append(f"Create directory: mkdir -p {path_str}")

    return PathValidationResult(
        mission_name=mission.name,
        required_paths=required_paths,
        existing_paths=existing,
        missing_paths=missing,
        warnings=warnings,
        suggestions=suggestions
    )

Summary

Key Data Models

1. MissionConfig (Pydantic) - Schema-validated mission.yaml 2. WorktreeValidationResult - Pre-flight check results 3. MissionSwitchValidation - Mission switch validation 4. CitationValidationResult - Research citation validation 5. PathValidationResult - Path convention validation 6. EvidenceEntry - Research evidence tracking 7. SourceEntry - Research source registry

Validation Strategy

  • Fail Fast: Schema errors on mission load
  • Progressive: Path warnings at switch, errors at acceptance
  • Helpful: All errors include suggestions for fixing
  • Type Safe: Pydantic models provide IDE support and type checking

Implementation Files

  • src/specify_cli/mission.py - Enhanced Mission class with MissionConfig
  • src/specify_cli/guards.py - WorktreeValidationResult
  • src/specify_cli/validators/research.py - Citation validation
  • src/specify_cli/validators/paths.py - Path validation