# Implementation Plan: FMEA & Poka-Yoke for ggen

**Branch**: `006-marketplace-fmea-poka-yoke`
**Date**: 2025-12-14
**Spec**: [spec.md](./spec.md)

---

## Summary

Implement the behavior for **already-defined ggen.toml config sections**:

| Config Section | Schema Status | Behavior Status | Implementation Needed |
|----------------|---------------|-----------------|----------------------|
| `[generation]` | ✅ Exists | ❌ Not implemented | Path protection, header injection |
| `[marketplace]` | ✅ Exists | ❌ Not implemented | FMEA validation on install |
| `[codeowners]` | ✅ Exists | ✅ Partial (types exist) | Wire into generate flow |

---

## What Already Exists

### Config Schema (ggen-config/src/schema.rs)
- `GenerationSafetyConfig` - protected_paths, regenerate_paths, headers
- `PokaYokeSettings` - warning_headers, gitattributes
- `MarketplaceConfig` - fmea_validation, require_fmea, critical_threshold
- `FmeaControl` - severity, occurrence, detection, rpn(), is_mitigated()
- `CodeownersConfig` - source_dirs, base_dirs, output_path

### CODEOWNERS Generator (ggen-core/src/types/codeowners.rs)
- `CodeownersGenerator` - scans OWNERS files
- `OwnersFile::parse()` - parses OWNERS format
- `generate_codeowners()` - convenience function

---

## Implementation Tasks

### Phase 1: Path Protection in Generation

**Goal**: Honor `[generation].protected_paths` during template rendering.

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

**Changes**:
1. Before writing any file, check if path matches `protected_paths` glob
2. If match AND file exists → error (don't overwrite)
3. If match AND file doesn't exist → write stub (first generation only)
4. If matches `regenerate_paths` → always write

```rust
// Pseudocode for template.rs
fn write_output(&self, path: &Path, content: &str, config: &GenerationSafetyConfig) -> Result<()> {
    if is_protected(path, &config.protected_paths) {
        if path.exists() {
            return Err(Error::ProtectedPathExists(path.to_path_buf()));
        }
        // First generation - create stub
    }
    // Proceed with write
}
```

**Tests**:
- `test_protected_path_not_overwritten`
- `test_protected_path_stub_created_first_time`
- `test_regenerate_path_always_written`

---

### Phase 2: Warning Header Injection

**Goal**: Honor `[generation.poka_yoke].warning_headers` during template rendering.

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

**Changes**:
1. After rendering template, prepend warning header
2. Header format based on file extension (.rs, .ts, .py, etc.)
3. Use `generated_header` from config or default

```rust
// Pseudocode
fn inject_header(content: &str, ext: &str, config: &GenerationSafetyConfig) -> String {
    if !config.poka_yoke.as_ref().map_or(false, |p| p.warning_headers) {
        return content.to_string();
    }

    let header = config.generated_header.as_deref()
        .unwrap_or("// DO NOT EDIT - Generated by ggen");

    format_header_for_extension(header, ext) + content
}
```

**Tests**:
- `test_header_injected_rust`
- `test_header_injected_typescript`
- `test_header_respects_config_disabled`

---

### Phase 3: FMEA Validation on Marketplace Install

**Goal**: Honor `[marketplace].fmea_validation` during package installation.

**Location**: `crates/ggen-cli/src/cmds/marketplace.rs`

**Changes**:
1. During `ggen marketplace install`, load package.toml
2. If `fmea_validation = true`, check for `[fmea]` section
3. If `require_fmea = true` and missing → error
4. Calculate RPN for each control, check against `critical_threshold`
5. Block install if unmitigated critical mode found

```rust
// Pseudocode
fn validate_package_fmea(package: &PackageToml, config: &MarketplaceConfig) -> Result<()> {
    if !config.fmea_validation {
        return Ok(());
    }

    let fmea = package.fmea.as_ref()
        .ok_or_else(|| Error::MissingFmea)?;

    for control in &fmea.controls {
        if control.is_critical_unmitigated(config.critical_threshold) {
            return Err(Error::UnmitigatedCritical(control.clone()));
        }
    }

    Ok(())
}
```

**Tests**:
- `test_install_fails_missing_fmea`
- `test_install_fails_unmitigated_critical`
- `test_install_passes_valid_fmea`

---

### Phase 4: Wire CODEOWNERS into Generate Flow

**Goal**: Honor `[codeowners].enabled` during generation.

**Location**: `crates/ggen-domain/src/template.rs` or generate command

**Changes**:
1. After template generation, if `codeowners.enabled`:
2. Use existing `CodeownersGenerator` from ggen-core
3. Scan `source_dirs` for OWNERS files
4. Generate `.github/CODEOWNERS` to `output_path`

```rust
// Pseudocode - post-generation hook
if let Some(ref co_config) = config.codeowners {
    if co_config.enabled {
        let mut generator = CodeownersGenerator::new()
            .with_base_dirs(co_config.base_dirs.clone());

        for dir in &co_config.source_dirs {
            generator.scan_owners_files(Path::new(dir))?;
        }

        let output = co_config.output_path.as_deref()
            .unwrap_or(".github/CODEOWNERS");
        generator.write_to_path(Path::new(output))?;
    }
}
```

**Tests**:
- `test_codeowners_generated_when_enabled`
- `test_codeowners_respects_source_dirs`
- `test_codeowners_skipped_when_disabled`

---

## File Changes Summary

| File | Change Type | Purpose |
|------|-------------|---------|
| `crates/ggen-domain/src/template.rs` | MODIFY | Add path protection + header injection |
| `crates/ggen-domain/src/generation/mod.rs` | CREATE | Module for generation utilities |
| `crates/ggen-domain/src/generation/headers.rs` | CREATE | Header injection logic |
| `crates/ggen-domain/src/generation/protection.rs` | CREATE | Path protection logic |
| `crates/ggen-cli/src/cmds/marketplace.rs` | MODIFY | Add FMEA validation to install |
| `tests/integration/path_protection_test.rs` | CREATE | Path protection tests |
| `tests/integration/fmea_validation_test.rs` | CREATE | FMEA validation tests |

---

## Dependencies

- `globset` crate for glob pattern matching (may already be a dep)
- Existing `ggen-core/src/types/codeowners.rs` (already implemented)
- Existing `ggen-config/src/schema.rs` (config structs ready)

---

## Definition of Done

- [ ] `protected_paths` honored - files not overwritten
- [ ] `warning_headers` honored - headers injected in generated files
- [ ] `fmea_validation` honored - install fails on missing/invalid FMEA
- [ ] `codeowners.enabled` honored - CODEOWNERS generated from OWNERS
- [ ] All tests pass (`cargo make test`)
- [ ] No new clippy warnings (`cargo make lint`)
