─── V-MAF-010: circular alias detection ───────────────────────────────────── detectCircularModelAliases performs a full DFS cycle check over the merged alias map and returns an error naming every alias in the cycle (V-MAF-010). Algorithm (Section 8.6.1): For each alias key, perform a depth-first
(aliasMap map[string][]string, markdownPath string)
| 239 | // If any traversal reaches an alias key already on the current path, a cycle |
| 240 | // is detected and MUST be reported as a compile-time error. |
| 241 | func detectCircularModelAliases(aliasMap map[string][]string, markdownPath string) error { |
| 242 | modelAliasValidationLog.Printf("Checking for circular alias references in %d aliases", len(aliasMap)) |
| 243 | |
| 244 | // visited tracks keys for which all DFS descendants have been fully explored |
| 245 | // (no cycle detected from that key). |
| 246 | visited := map[string]struct { |
| 247 | }{} |
| 248 | |
| 249 | // Iterate keys in deterministic order for reproducible error messages. |
| 250 | keys := sliceutil.SortedKeys(aliasMap) |
| 251 | |
| 252 | state := &dfsState{ |
| 253 | aliasMap: aliasMap, |
| 254 | visited: visited, |
| 255 | onPath: make(map[string]bool, 16), |
| 256 | path: make([]string, 0, 16), |
| 257 | } |
| 258 | |
| 259 | for _, key := range keys { |
| 260 | if setutil.Contains(visited, key) { |
| 261 | continue |
| 262 | } |
| 263 | clear(state.onPath) |
| 264 | state.path = state.path[:0] |
| 265 | if cycle := state.dfs(key); cycle != nil { |
| 266 | // Format cycle chain for a clear error message. |
| 267 | chain := strings.Join(append(cycle, cycle[0]), " → ") |
| 268 | return formatCompilerError(markdownPath, "error", |
| 269 | fmt.Sprintf("circular alias reference detected: %s\n\n"+ |
| 270 | "Circular alias references are prohibited. Remove or rewrite the cycle in the 'models:' "+ |
| 271 | "frontmatter section (V-MAF-010).", chain), |
| 272 | nil) |
| 273 | } |
| 274 | } |
| 275 | |
| 276 | return nil |
| 277 | } |
| 278 | |
| 279 | // dfsState holds the mutable state for a single DFS traversal. |
| 280 | type dfsState struct { |