validateModelAliasMap is the main entry point for compile-time model-alias validation. It validates: - The user-supplied alias map entries in frontmatterModels (V-MAF-001..006, V-MAF-011). - The engine.model value (V-MAF-001, V-MAF-004, V-MAF-006). - Circular references across the fully-merged alias
( mergedAliasMap map[string][]string, frontmatterModels map[string][]string, engineModel string, markdownPath string, )
| 93 | // Returns a non-nil error (causing compilation to abort) for hard violations. |
| 94 | // Warnings are printed to stderr via the compiler's warning counter. |
| 95 | func (c *Compiler) validateModelAliasMap( |
| 96 | mergedAliasMap map[string][]string, |
| 97 | frontmatterModels map[string][]string, |
| 98 | engineModel string, |
| 99 | markdownPath string, |
| 100 | ) error { |
| 101 | modelAliasValidationLog.Printf("Validating model alias map: %d merged entries, %d frontmatter entries, engine.model=%q", |
| 102 | len(mergedAliasMap), len(frontmatterModels), engineModel) |
| 103 | |
| 104 | // V-MAF-004: engine.model MUST NOT contain a glob pattern ("*"). |
| 105 | // The check is always performed, but only on the *literal* parts of the |
| 106 | // value — expression segments (${{ ... }}) are stripped first so that a |
| 107 | // "*" inside an expression body (e.g. "${{ contains(inputs.m, '*') }}") |
| 108 | // is not falsely flagged. A "*" that appears *outside* an expression |
| 109 | // (e.g. "${{ inputs.model }}*" or "copilot/*${{ inputs.model }}") is |
| 110 | // still a glob and must be rejected. |
| 111 | if engineModel != "" { |
| 112 | literalText := ExpressionPattern.ReplaceAllString(engineModel, "") |
| 113 | if strings.Contains(literalText, "*") { |
| 114 | return formatCompilerError(markdownPath, "error", |
| 115 | fmt.Sprintf("engine.model: glob patterns are not allowed in engine.model; "+ |
| 116 | "got %q — glob patterns may only appear in models alias list entries (V-MAF-004)", engineModel), |
| 117 | nil) |
| 118 | } |
| 119 | // Syntax and parameter checks ($-character parsing, known params) are |
| 120 | // skipped for runtime-resolved expressions — they cannot be parsed at |
| 121 | // compile time. |
| 122 | if !containsExpression(engineModel) { |
| 123 | // V-MAF-001 + V-MAF-006: validate syntax of engine.model. |
| 124 | if errs := validateModelIdentifierStrings([]string{engineModel}, "engine.model"); len(errs) > 0 { |
| 125 | return formatCompilerError(markdownPath, "error", errs[0], nil) |
| 126 | } |
| 127 | // V-MAF-011: warn about unrecognised parameter keys in engine.model. |
| 128 | c.warnUnrecognizedModelParams([]string{engineModel}, markdownPath) |
| 129 | } |
| 130 | } |
| 131 | |
| 132 | // Validate user-supplied frontmatter aliases only (builtins are pre-validated). |
| 133 | for key, entries := range frontmatterModels { |
| 134 | // V-MAF-005: alias keys MUST NOT contain "/", "?", or "&". |
| 135 | if err := validateAliasKey(key, markdownPath); err != nil { |
| 136 | return err |
| 137 | } |
| 138 | |
| 139 | // V-MAF-001 + V-MAF-002 + V-MAF-003 + V-MAF-006: validate each entry string. |
| 140 | if errs := validateModelIdentifierStrings(entries, "models."+displayKey(key)); len(errs) > 0 { |
| 141 | return formatCompilerError(markdownPath, "error", errs[0], nil) |
| 142 | } |
| 143 | |
| 144 | // V-MAF-011: warn about unrecognised parameter keys in each entry. |
| 145 | c.warnUnrecognizedModelParams(entries, markdownPath) |
| 146 | } |
| 147 | |
| 148 | // V-MAF-010: detect circular alias references across the merged map. |
| 149 | if err := detectCircularModelAliases(mergedAliasMap, markdownPath); err != nil { |
| 150 | return err |
| 151 | } |
| 152 |