# FMEA Analysis & Poka-Yoke Code Recommendations for ggen

**Feature Branch**: `006-marketplace-fmea-poka-yoke`
**Created**: 2025-12-14 | **Status**: Complete | **Completed**: 2025-12-14
**Purpose**: Identify failure modes in ggen and recommend structural code changes to prevent them

---

## Critical Context: ggen is for AI Coding Agents

**ggen is NOT intended for human developers**. It's used by AI coding agents (Claude Code, etc.) to generate code from RDF ontologies.

**Who uses ggen**:
- Claude Code authoring CLI applications
- AI agents generating domain models from ontologies
- Automated pipelines creating type-safe code from RDF

**This changes the FMEA analysis**:
- "User error" = AI agent making mistakes
- "Visual warnings" = Agent-readable markers in output
- "Domain protection" = Ensuring agent doesn't overwrite its own prior work

---

## Architectural Principle: ggen.toml is the Single Source of Truth

**ALL configuration and automation flows from ggen.toml**. The agent reads ggen.toml, understands the project structure, and operates accordingly.

```toml
# ggen.toml - The ONLY file that defines project behavior
[project]
name = "my-cli"
ontology = "ontology/"

[generation]
generated_dir = "src/generated"   # Agent regenerates here freely
domain_dir = "src/domain"         # Agent NEVER overwrites here (after stubs)
warning_headers = true
```

**Why ggen.toml-centric**:
1. Single file to read = deterministic agent behavior
2. All paths defined = no guessing where files go
3. Declarative config = agent knows the rules upfront

---

## The Problem to Solve

**Question**: "How do AI agents regenerate code freely without losing previously authored domain logic?"

**Current Risk**: When an AI agent runs `ggen generate`, it could accidentally overwrite domain implementations it authored in a previous session.

---

## FMEA Analysis: ggen Generation Lifecycle (AI Agent Context)

### Failure Mode Identification

| ID | Failure Mode | Severity (1-10) | Occurrence (1-10) | Detection (1-10) | RPN | Root Cause |
|----|--------------|-----------------|-------------------|------------------|-----|------------|
| F1 | Agent edits generated file (thinking it's domain) | 8 | 6 | 5 | **240** | No marker distinguishing generated from domain |
| F2 | Regenerate overwrites domain logic from prior session | 10 | 4 | 3 | **120** | Generated and domain code in same location |
| F3 | Agent loses context between sessions | 5 | 7 | 8 | **280** | No persistent record of what was generated vs authored |
| F4 | Trait signature doesn't enforce Result<T, E> | 8 | 6 | 5 | **240** | No contract enforcement in generated code |
| F5 | Agent doesn't know ggen.toml conventions | 6 | 8 | 4 | **192** | Configuration not self-documenting |

**RPN = Severity × Occurrence × Detection** (higher = worse)

### Critical Failure Modes (RPN > 200)

1. **F3 (RPN=280)**: Agent loses context between sessions (what's generated vs authored)
2. **F1 (RPN=240)**: Agent edits generated file thinking it's domain code
3. **F4 (RPN=240)**: Missing error handling because trait doesn't require it

---

## Poka-Yoke Recommendations (ggen.toml-Driven Changes)

### Recommendation 1: Directory Separation via ggen.toml (Fixes F2, F3)

**Current State**: No clear separation between generated and domain code.

**ggen.toml Configuration**:
```toml
[generation]
# These paths are read by ggen and respected by the agent
generated_dir = "src/generated"   # ggen ALWAYS regenerates here
domain_dir = "src/domain"         # ggen NEVER touches here (after initial stubs)
```

**Resulting Structure**:
```
src/
├── generated/     # ggen regenerates freely here
│   └── user/
│       ├── mod.rs
│       └── suspend.rs  # pub trait UserSuspend { ... }
│
└── domain/        # Agent-authored code lives here permanently
    └── user/
        ├── mod.rs
        └── suspend.rs  # impl UserSuspend for User { ... }
```

**Implementation Location**: `crates/ggen-domain/src/template.rs`

**Code Change**:
```rust
// Read paths from ggen.toml config
fn get_output_path(config: &GgenConfig, noun: &str, verb: &str, file_type: FileType) -> PathBuf {
    match file_type {
        FileType::Trait => config.generation.generated_dir.join(noun).join(format!("{}.rs", verb)),
        FileType::Impl => config.generation.domain_dir.join(noun).join(format!("{}.rs", verb)),
    }
}
```

**Why This Works**: ggen.toml declares the contract. Agent reads it. Physical separation makes overwrites **impossible**.

---

### Recommendation 2: Trait Boundary Pattern (Fixes F4)

**Current State**: No contract between generated and domain code.

**Recommended Code Change**: Generated code defines `pub trait`, domain code provides `impl`:

**Generated file** (`src/generated/user/suspend.rs`):
```rust
// DO NOT EDIT - Generated by ggen from user.ttl
// Regenerate with: ggen generate

/// Trait defining the contract for suspend operation
pub trait UserSuspend {
    /// Suspend a user account
    ///
    /// # Arguments
    /// * `user_id` - The user to suspend
    /// * `reason` - Suspension reason for audit
    ///
    /// # Errors
    /// Returns error if user not found or already suspended
    fn suspend(&self, user_id: UserId, reason: String) -> Result<SuspendResult, DomainError>;
}
```

**Domain file** (`src/domain/user/suspend.rs`):
```rust
use crate::generated::user::suspend::UserSuspend;

impl UserSuspend for UserService {
    fn suspend(&self, user_id: UserId, reason: String) -> Result<SuspendResult, DomainError> {
        // Developer's actual implementation here
        // Compiler enforces this matches the trait signature
    }
}
```

**Implementation Location**: Templates in `marketplace/packages/*/templates/`

**Why This Works**:
- Rust compiler **enforces** that domain implements the trait
- If trait signature changes, domain **won't compile** until updated
- Error handling (`Result<T, E>`) is required by the trait

---

### Recommendation 3: Warning Header Injection (Fixes F1)

**Current State**: No indication that a file is generated.

**Recommended Code Change**: `ggen generate` prepends a warning header to all generated files:

```rust
// ============================================================
// DO NOT EDIT THIS FILE
//
// This file is auto-generated by ggen from RDF ontology.
// Any manual changes will be OVERWRITTEN on regeneration.
//
// To customize behavior, implement the trait in:
//   src/domain/user/suspend.rs
//
// Regenerate with: ggen generate
// ============================================================
```

**Implementation Location**: `crates/ggen-domain/src/generation/headers.rs` (new file)

**Code Change**:
```rust
pub fn inject_warning_header(content: &str, file_type: &str) -> String {
    let comment_start = match file_type {
        "rs" => "//",
        "ts" | "js" => "//",
        "py" => "#",
        "go" => "//",
        _ => "//",
    };

    let header = format!(
        "{cs} ============================================================\n\
         {cs} DO NOT EDIT THIS FILE\n\
         {cs}\n\
         {cs} This file is auto-generated by ggen from RDF ontology.\n\
         {cs} Any manual changes will be OVERWRITTEN on regeneration.\n\
         {cs} ============================================================\n\n",
        cs = comment_start
    );

    format!("{}{}", header, content)
}
```

**Why This Works**: Clear visual warning prevents accidental edits.

---

### Recommendation 4: One-File-Per-Verb Pattern (Fixes F3)

**Current State**: All verbs could be in one file → merge conflicts.

**Recommended Code Change**: Each verb gets its own file:

```
src/generated/user/
├── mod.rs          # pub mod suspend; pub mod activate; ...
├── suspend.rs      # trait UserSuspend
├── activate.rs     # trait UserActivate
└── delete.rs       # trait UserDelete
```

**Implementation Location**: Template structure in marketplace packages

**Why This Works**:
- Developer A adds `suspend.ttl` → generates `suspend.rs`
- Developer B adds `activate.ttl` → generates `activate.rs`
- **Zero merge conflicts** because they touch different files

---

### Recommendation 5: First-Generation Stub Creation (Fixes F5)

**Current State**: New developers don't know what to implement.

**Recommended Code Change**: On first generation, create domain stubs with `unimplemented!()`:

**Generated stub** (`src/domain/user/suspend.rs` - created ONCE on first run):
```rust
use crate::generated::user::suspend::UserSuspend;

impl UserSuspend for UserService {
    fn suspend(&self, user_id: UserId, reason: String) -> Result<SuspendResult, DomainError> {
        // TODO: Implement suspend logic
        // This stub was created by ggen. Fill in your implementation.
        unimplemented!("UserService::suspend")
    }
}
```

**Implementation Location**: `crates/ggen-domain/src/generation/stubs.rs` (new file)

**Code Change**:
```rust
pub fn should_create_stub(domain_path: &Path) -> bool {
    !domain_path.exists()  // Only create if file doesn't exist
}

pub fn generate_stub(trait_name: &str, method_sig: &str) -> String {
    format!(
        "// TODO: Implement {} logic\n\
         // This stub was created by ggen. Fill in your implementation.\n\
         unimplemented!(\"{}\")",
        trait_name, trait_name
    )
}
```

**Why This Works**: New developers see exactly where to put their code.

---

## Summary: Code Changes to Make

| Change | File Location | What to Implement |
|--------|---------------|-------------------|
| Directory separation | `crates/ggen-domain/src/template.rs` | Output to `src/generated/` and `src/domain/` |
| Trait boundary | Template files | Generate `pub trait` in generated, `impl` in domain |
| Warning headers | `crates/ggen-domain/src/generation/headers.rs` | Inject "DO NOT EDIT" header |
| One-file-per-verb | Template structure | Each verb in separate file |
| Stub creation | `crates/ggen-domain/src/generation/stubs.rs` | Create `unimplemented!()` stubs |

---

## Answering the Original Question

**"How do you regenerate freely without losing domain logic?"**

**Answer**:
1. `src/generated/` contains **only traits** (the contract) - regenerate anytime
2. `src/domain/` contains **only implementations** - ggen never touches this
3. Rust compiler ensures domain implements the traits correctly
4. Warning headers prevent accidental edits to generated files

**Result**: Run `ggen generate` as often as you want. Your domain code is safe.

---

## Configuration Reference: What Already Exists in ggen.toml

The ggen-config schema (`crates/ggen-config/src/schema.rs`) already defines these structures:

### Already Implemented in Schema

**1. `GenerationSafetyConfig` ([generation] section)**:
```rust
pub struct GenerationSafetyConfig {
    pub enabled: bool,
    pub protected_paths: Vec<String>,      // Glob patterns - NEVER overwrite
    pub regenerate_paths: Vec<String>,     // Glob patterns - safe to overwrite
    pub generated_header: Option<String>,  // "DO NOT EDIT" header
    pub require_confirmation: bool,
    pub backup_before_write: bool,
    pub poka_yoke: Option<PokaYokeSettings>,
}
```

**2. `PokaYokeSettings`**:
```rust
pub struct PokaYokeSettings {
    pub warning_headers: bool,
    pub gitignore_generated: bool,
    pub gitattributes_generated: bool,
    pub validate_imports: bool,
}
```

**3. `MarketplaceConfig` ([marketplace] section)**:
```rust
pub struct MarketplaceConfig {
    pub fmea_validation: bool,     // Validate FMEA during install
    pub require_fmea: bool,        // Require [fmea] section
    pub critical_threshold: u16,   // RPN threshold (default 200)
}
```

**4. `FmeaControl` (for parsing package.toml)**:
```rust
pub struct FmeaControl {
    pub id: String,
    pub mode: String,
    pub severity: u8,
    pub occurrence: u8,
    pub detection: u8,
    pub control: Option<String>,
    pub evidence: Option<String>,
}

impl FmeaControl {
    pub fn rpn(&self) -> u16 { ... }
    pub fn is_mitigated(&self) -> bool { ... }
    pub fn is_critical_unmitigated(&self, threshold: u16) -> bool { ... }
}
```

**5. `CodeownersConfig` ([codeowners] section)**:
```rust
pub struct CodeownersConfig {
    pub enabled: bool,
    pub source_dirs: Vec<String>,
    pub base_dirs: Vec<String>,
    pub output_path: Option<String>,
    pub auto_regenerate: bool,
}
```

---

## What Needs Implementation

The config structs exist but the **behavior is not implemented**. The code changes needed:

| Config | Schema Exists | Behavior Implemented | What's Needed |
|--------|---------------|---------------------|---------------|
| `[generation].protected_paths` | ✅ Yes | ❌ No | Add path check before file write |
| `[generation].regenerate_paths` | ✅ Yes | ❌ No | Add path classification logic |
| `[generation].generated_header` | ✅ Yes | ❌ No | Inject header in template render |
| `[generation.poka_yoke].warning_headers` | ✅ Yes | ❌ No | Header injection based on file type |
| `[marketplace].fmea_validation` | ✅ Yes | ❌ No | Validate during `ggen marketplace install` |
| `[codeowners].enabled` | ✅ Yes | ❌ No | Generate CODEOWNERS from OWNERS files |

---

## Example ggen.toml Using Poka-Yoke

```toml
[project]
name = "my-cli"
version = "1.0.0"

# Enable Poka-Yoke controls for safe regeneration
[generation]
enabled = true
protected_paths = ["src/domain/**/*"]           # Agent code lives here permanently
regenerate_paths = ["src/generated/**/*"]       # ggen regenerates here freely
generated_header = "// DO NOT EDIT - Generated by ggen from RDF ontology"
backup_before_write = true

[generation.poka_yoke]
warning_headers = true
gitattributes_generated = true

# FMEA validation for marketplace packages
[marketplace]
fmea_validation = true
require_fmea = true
critical_threshold = 200

# CODEOWNERS from noun OWNERS files
[codeowners]
enabled = true
source_dirs = ["ontology"]
base_dirs = ["ontology", "src/generated", "src/domain"]
output_path = ".github/CODEOWNERS"
```

That's it. The schema is already there. Implementation needs to honor these settings.
