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_validatorsflag matches file existence
Project ’ Active Mission
- Relationship: One project has one active mission (via symlink)
- Storage:
.kittify/active-mission’missions/{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 MissionConfigsrc/specify_cli/guards.py- WorktreeValidationResultsrc/specify_cli/validators/research.py- Citation validationsrc/specify_cli/validators/paths.py- Path validation