# Research: FMEA & Poka-Yoke Marketplace Framework

**Branch**: `006-marketplace-fmea-poka-yoke`
**Date**: 2025-12-14
**Status**: Complete

## 1. TOML Schema for [fmea] Section

### Decision
Use array of inline tables with explicit field names.

### Rationale
TOML inline tables are concise while remaining readable. The `[[fmea.controls]]` array syntax allows unlimited entries while maintaining clear structure.

### Schema Design

```toml
[fmea]
# Enable FMEA validation for this package
enabled = true
# Minimum coverage: percentage of failure modes that must have controls
min_coverage = 100  # 100% = all critical modes must have controls

[[fmea.controls]]
id = "F1"
mode = "Developer edits generated file"
severity = 9      # 1-10: Impact if failure occurs
occurrence = 6    # 1-10: Likelihood of occurrence
detection = 4     # 1-10: Difficulty detecting before release
rpn = 216         # severity × occurrence × detection (auto-calculated)
control = "⚠️ DO NOT EDIT header + .gitignore"

[[fmea.controls]]
id = "F2"
mode = "Regenerate overwrites domain logic"
severity = 10
occurrence = 7
detection = 2
rpn = 140
control = "Trait boundary separation - domain in protected_paths"
```

### Alternatives Considered
1. **YAML nested in TOML**: Rejected - not valid TOML
2. **JSON array in string**: Rejected - poor readability
3. **Separate FMEA.toml file**: Rejected - fragmentation, single package.toml preferred

---

## 2. Path Pattern Matching

### Decision
Use `globset` crate from BurntSushi (ripgrep author).

### Rationale
- **Performance**: Compiles patterns into optimized automata
- **Features**: Supports `**`, `?`, `[...]`, negation
- **Trust**: Same author as ripgrep, well-maintained
- **Existing use**: ggen already uses `glob` crate; `globset` is compatible

### Implementation

```rust
use globset::{Glob, GlobSetBuilder};

pub struct PathMatcher {
    protected: GlobSet,
    regenerate: GlobSet,
}

impl PathMatcher {
    pub fn new(protected: &[String], regenerate: &[String]) -> Result<Self, Error> {
        let mut protected_builder = GlobSetBuilder::new();
        for pattern in protected {
            protected_builder.add(Glob::new(pattern)?);
        }

        let mut regenerate_builder = GlobSetBuilder::new();
        for pattern in regenerate {
            regenerate_builder.add(Glob::new(pattern)?);
        }

        Ok(Self {
            protected: protected_builder.build()?,
            regenerate: regenerate_builder.build()?,
        })
    }

    pub fn is_protected(&self, path: &Path) -> bool {
        self.protected.is_match(path)
    }

    pub fn is_regeneratable(&self, path: &Path) -> bool {
        self.regenerate.is_match(path)
    }

    pub fn validate_no_overlap(&self) -> Result<(), ValidationError> {
        // Check that no path can match both protected and regenerate
        // Implementation: test common paths against both glob sets
    }
}
```

### Alternatives Considered
1. **`glob` crate**: Simpler but slower, no precompilation
2. **`ignore` crate**: Full gitignore semantics but heavier
3. **regex**: Overkill, glob is more readable for paths

---

## 3. Warning Header Format

### Decision
Use multi-line comment with `@generated` marker and regeneration command.

### Rationale
- `@generated` is recognized by GitHub, GitLab, Bitbucket
- IDEs (VS Code, IntelliJ, Vim) can be configured to highlight
- Includes actionable command for regeneration

### Format (Rust)

```rust
// ⚠️ DO NOT EDIT - This file is auto-generated
// @generated by ggen from RDF ontology
//
// Regenerate with:
//   ggen generate --template <template> --domain <domain.rdf>
//
// Changes will be overwritten on next generation.
// Place your code in src/domain/ instead.
```

### Format (TypeScript/JavaScript)

```typescript
/**
 * ⚠️ DO NOT EDIT - This file is auto-generated
 * @generated by ggen from RDF ontology
 *
 * Regenerate with:
 *   ggen generate --template <template> --domain <domain.rdf>
 *
 * Changes will be overwritten on next generation.
 * Place your code in src/domain/ instead.
 */
```

### Format (Python)

```python
# ⚠️ DO NOT EDIT - This file is auto-generated
# @generated by ggen from RDF ontology
#
# Regenerate with:
#   ggen generate --template <template> --domain <domain.rdf>
#
# Changes will be overwritten on next generation.
# Place your code in domain/ instead.
```

### Template Configuration

```toml
[poka_yoke]
generated_file_header = """
⚠️ DO NOT EDIT - This file is auto-generated
@generated by ggen from RDF ontology

Regenerate with:
  ggen generate --template {template} --domain {domain}

Changes will be overwritten on next generation.
Place your code in {protected_paths[0]} instead.
"""
```

### Alternatives Considered
1. **Short header only**: Rejected - missing regeneration command
2. **IDE-specific markers**: Rejected - not portable
3. **Binary marker in file**: Rejected - modifies content unexpectedly

---

## 4. CODEOWNERS Aggregation

### Decision
Generate `.github/CODEOWNERS` from noun-level OWNERS files with path prefixes.

### Rationale
- GitHub CODEOWNERS is standard for PR review assignment
- Path-based ownership maps naturally to noun directories
- Breaking changes can require additional reviewers via glob patterns

### Format (noun OWNERS file)

```text
# ontology/user/OWNERS
# Identity Team owns user noun
@company/identity-team
@john.smith
@jane.doe

# Breaking changes require Platform Team
*.breaking.ttl @company/platform-team
```

### Generated CODEOWNERS

```text
# Auto-generated by ggen from noun OWNERS files
# DO NOT EDIT - regenerate with: ggen generate --codeowners

# User noun (Identity Team)
/ontology/user/ @company/identity-team @john.smith @jane.doe
/src/generated/user/ @company/identity-team @john.smith @jane.doe
/src/domain/user/ @company/identity-team @john.smith @jane.doe

# Order noun (Commerce Team)
/ontology/order/ @company/commerce-team @alice.wong
/src/generated/order/ @company/commerce-team @alice.wong
/src/domain/order/ @company/commerce-team @alice.wong

# Breaking changes require Platform Team approval
*.breaking.ttl @company/platform-team
```

### Aggregation Algorithm

```rust
pub fn aggregate_codeowners(noun_dirs: &[PathBuf]) -> String {
    let mut lines = vec![
        "# Auto-generated by ggen from noun OWNERS files",
        "# DO NOT EDIT - regenerate with: ggen generate --codeowners",
        "",
    ];

    for noun_dir in noun_dirs {
        let owners_path = noun_dir.join("OWNERS");
        if owners_path.exists() {
            let owners = parse_owners(&owners_path);
            let noun_name = noun_dir.file_name().unwrap().to_str().unwrap();

            // Add comment
            lines.push(&format!("# {} noun", noun_name));

            // Add paths with owners
            let owners_str = owners.join(" ");
            lines.push(&format!("/ontology/{noun_name}/ {owners_str}"));
            lines.push(&format!("/src/generated/{noun_name}/ {owners_str}"));
            lines.push(&format!("/src/domain/{noun_name}/ {owners_str}"));
            lines.push("");
        }
    }

    // Add breaking changes rule
    lines.push("# Breaking changes require Platform Team approval");
    lines.push("*.breaking.ttl @company/platform-team");

    lines.join("\n")
}
```

### Alternatives Considered
1. **Manual CODEOWNERS only**: Rejected - doesn't scale, drift risk
2. **CODEOWNERS per crate**: Rejected - too granular
3. **Single owner for all**: Rejected - doesn't support multi-team

---

## Summary of Decisions

| Topic | Decision | Key Benefit |
|-------|----------|-------------|
| FMEA TOML | Array of inline tables | Readable, unlimited entries |
| Path matching | `globset` crate | Performance, well-maintained |
| Warning header | Multi-line with `@generated` | IDE/GitHub recognition |
| CODEOWNERS | Generate from noun OWNERS | Scalable, accurate |

All research questions resolved. Proceed to Phase 1 design.
