Skip to content

feat: allow GitHub Actions expressions in models alias parser#30000

Merged
pelikhan merged 4 commits intomainfrom
copilot/update-models-alias-parser
May 3, 2026
Merged

feat: allow GitHub Actions expressions in models alias parser#30000
pelikhan merged 4 commits intomainfrom
copilot/update-models-alias-parser

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 3, 2026

Summary

Updates the model alias parser to allow GitHub Actions expressions (${{ ... }}) that are resolved at runtime, not just whole-string expressions, while preserving all compile-time static checks on literal portions.

Problem

Previously, only whole-string expressions (where the entire value is a ${{ ... }} expression) were exempted from compile-time syntax validation. Partial expressions like:

  • ${{ inputs.model }}?effort=high — expression with query params
  • copilot/${{ inputs.model }} — expression embedded in a provider-scoped identifier

would fail validation because isExpression() returns false for them, causing ParseModelIdentifier to error on the $ character.

Additionally, the original fix using containsExpression() as the outer guard for the engine.model block inadvertently bypassed the V-MAF-004 glob check entirely — allowing values like "${{ inputs.model }}*" or "copilot/*${{ inputs.model }}" to pass despite having a literal * outside the expression.

Changes

pkg/workflow/model_alias_validation.go

  • Replace isExpression() with containsExpression() in the validateModelIdentifierStrings and warnUnrecognizedModelParams expression-skip guards (alias entry loops).
  • For engine.model (V-MAF-004): decouple the glob check from the expression skip. The glob restriction is now always enforced on the literal text (using ExpressionPattern.ReplaceAllString to strip ${{ ... }} segments before checking for *), so a * inside an expression body is not falsely flagged, but a * in the literal portions is always caught. Only syntax/param validation is skipped for expression-containing values.
  • Remove the now-unused validateEngineModelNotGlob helper (logic inlined with the new strip-then-check approach).

pkg/workflow/model_alias_validation_test.go

Added comprehensive table-driven tests covering all expression forms:

  • TestValidateModelIdentifierStrings_ExpressionForms — tests validateModelIdentifierStrings directly with whole-string expressions, partial expressions (with query params), provider-scoped expressions, multiple expression entries, mixed expression+valid/invalid lists, and compact ${{...}} syntax.
  • TestWarnUnrecognizedModelParams_ExpressionSkipped — verifies that V-MAF-011 warnings are suppressed for all expression-containing identifiers while still firing for plain identifiers with unknown params.
  • TestValidateModelAliasMap_EngineModelExpressionForms — covers all engine.model expression variants end-to-end, including V-MAF-004 rejection for literal globs even when an expression is also present (e.g. "${{ inputs.model }}*", "copilot/*${{ inputs.model }}"), and confirms valid expression forms are accepted.
  • Integration-style tests: TestValidateModelAliasMap_PartialExpressionEngineModelSkipped, TestValidateModelAliasMap_ExpressionInAliasEntriesSkipped, TestValidateModelAliasMap_PartialExpressionInAliasEntriesSkipped.

Testing

All existing tests pass. The new tests confirm the new behavior. make fmt lint build recompile test-unit all pass.

Copilot AI and others added 2 commits May 3, 2026 19:49
Update validateModelAliasMap and helpers to use containsExpression()
instead of isExpression() so partial expressions like
\"${{ inputs.model }}?effort=high\" and \"copilot/${{ inputs.model }}\"
are also exempt from compile-time syntax validation.

Add four new tests covering the new partial-expression cases."

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/20ec3dd8-3bdc-4c19-8c95-632689808d51

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
@pelikhan
Copy link
Copy Markdown
Collaborator

pelikhan commented May 3, 2026

@copilot add tests

Add comprehensive table-driven tests covering all expression forms:
- TestValidateModelIdentifierStrings_ExpressionForms: tests validateModelIdentifierStrings directly with whole/partial/embedded expressions, mixed valid/invalid lists, and compact syntax
- TestWarnUnrecognizedModelParams_ExpressionSkipped: verifies no V-MAF-011 warnings emitted for expression-containing identifiers
- TestValidateModelAliasMap_EngineModelExpressionForms: covers all engine.model expression variants including glob rejection guard still works

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/b030920f-e288-4006-a38d-c3da732c1146

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 3, 2026

@copilot add tests

Added three new table-driven test functions in commit de825aa:

  • TestValidateModelIdentifierStrings_ExpressionForms — tests validateModelIdentifierStrings directly with 9 subtests covering whole-string expressions, partial expressions (with query params), provider-scoped expressions, multiple expressions, mixed expression+valid and expression+invalid lists, and compact ${{...}} syntax.
  • TestWarnUnrecognizedModelParams_ExpressionSkipped — verifies that V-MAF-011 warnings are suppressed for all expression-containing identifiers (whole, partial, provider-scoped) while still firing for plain identifiers with unknown params.
  • TestValidateModelAliasMap_EngineModelExpressionForms — covers all engine.model expression variants end-to-end, including confirming the glob rejection guard (V-MAF-004) is still enforced for non-expression values.

@pelikhan pelikhan marked this pull request as ready for review May 3, 2026 19:58
Copilot AI review requested due to automatic review settings May 3, 2026 19:58
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates model alias compile-time validation to treat strings containing GitHub Actions expressions (${{ ... }}) as runtime-resolved values, so partial/embedded expressions no longer fail model identifier parsing.

Changes:

  • Replace whole-string expression detection (isExpression) with substring detection (containsExpression) in model alias validation/warning paths.
  • Expand unit tests to cover partial and embedded expression forms in engine.model, alias entries, identifier validation, and unknown-param warnings.
Show a summary per file
File Description
pkg/workflow/model_alias_validation.go Skips static parsing/validation when an identifier contains a complete ${{ ... }} expression.
pkg/workflow/model_alias_validation_test.go Adds test coverage for expression-containing identifiers across validation and warning logic.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 2/2 changed files
  • Comments generated: 1

Comment thread pkg/workflow/model_alias_validation.go Outdated
Comment on lines 55 to 59
// V-MAF-004: engine.model MUST NOT be a glob pattern.
// GitHub Actions expressions (${{ … }}) are exempt from syntax checks.
if engineModel != "" && !isExpression(engineModel) {
// GitHub Actions expressions (${{ ... }}) are exempt from syntax checks,
// including partial expressions such as "${{ inputs.model }}?effort=high".
if engineModel != "" && !containsExpression(engineModel) {
if err := validateEngineModelNotGlob(engineModel, markdownPath); err != nil {
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 3, 2026

⚠️ Design Decision Gate — ADR Found but Not Referenced and Partially Divergent

This PR modifies a feature area covered by an existing Architecture Decision Record but does not link to it in the PR body, and the implementation introduces a behavior change that extends beyond the ADR's current normative scope.

ADR reviewed: ADR-29995: Compile-Time Validation of the Model Alias Format Specification

Findings

1. ADR Not Referenced in PR Body

PR #30000 modifies pkg/workflow/model_alias_validation.go, which was introduced by the feature described in ADR-29995. The PR body does not link to this ADR. Please add a reference such as:

ADR: [ADR-29995: Compile-Time Validation of the Model Alias Format Specification](docs/adr/29995-compile-time-model-alias-format-validation.md)

2. Divergence: Expression-Skip Guard Extended Beyond ADR Scope

The ADR's normative specification (Part 2, Model Identifier Parsing, requirement 4) states:

Implementations SHOULD exempt strings matching the GitHub Actions expression pattern (${{ ... }}) from syntax validation, as their content is resolved at runtime.

The ADR's Neutral consequences also explicitly name the isExpression helper as the mechanism:

GitHub Actions expressions in model identifier fields (${{ inputs.model }}) must be explicitly detected and skipped, adding a dependency on an isExpression helper that must be kept in sync with the GHA expression syntax.

This PR replaces isExpression() with containsExpression() in all three guards (validateModelAliasMap, validateModelIdentifierStrings, warnUnrecognizedModelParams), extending the exemption from whole-string expressions to partial expressions — strings that contain a ${{ ... }} fragment alongside literal content (e.g., ${{ inputs.model }}?effort=high, copilot/${{ inputs.model }}).

Location ADR-described behavior PR behavior
Normative req 4 Exempt strings matching the GHA expression pattern Exempt strings containing a GHA expression
Neutral note Uses isExpression helper Uses containsExpression helper

3. Alignment Assessment: Partially Aligned

The change is consistent with the ADR's core rationale ("resolved at runtime"), and the ADR uses SHOULD rather than MUST for this requirement, giving implementations latitude. However, the normative text and neutral note describe the narrower whole-string match, so the ADR's written specification no longer accurately reflects the code after this PR.

What to Do Next

  1. Link the ADR in the PR body (see template above).
  2. Update ADR-29995 to reflect the extended behavior:
    • Change normative requirement 4 to say "strings containing a GitHub Actions expression pattern" (or equivalent).
    • Update the Neutral note from isExpression to containsExpression and explain the distinction between whole-string and substring matching.
  3. Commit the updated ADR to docs/adr/29995-compile-time-model-alias-format-validation.md on this branch.

Once the ADR is linked in the PR body and the divergences are resolved, this gate will re-run.


📋 Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context — What is the problem? What forces are at play?
  • Decision — What did you decide? Why?
  • Alternatives Considered — What else could have been done?
  • Consequences — What are the trade-offs (positive and negative)?

All ADRs are stored in docs/adr/ as Markdown files.

🔒 This PR should not merge until the ADR is linked and the divergences are resolved.

References: §25289292363

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · ● 117.5K ·

@pelikhan
Copy link
Copy Markdown
Collaborator

pelikhan commented May 3, 2026

@copilot review all comments

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 3, 2026

test

🧪 Test quality analysis by Test Quality Sentinel · ● 924.5K ·

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Test Quality Sentinel: 90/100. Test quality is excellent — 0% of new tests are implementation tests (threshold: 30%). All 6 new tests verify behavioral contracts for the expression-skip feature across all expression forms.

…on is present

The engine.model guard now always enforces the glob restriction on the
literal text (after stripping ${{ ... }} segments), while still skipping
syntax/param validation for runtime-resolved expressions.

Previously, containsExpression() in the outer `if` caused the entire
validation block to be skipped, allowing values like "${{ inputs.m }}*"
or "copilot/*${{ inputs.model }}" to pass despite having a literal glob.

Changes:
- Inline the V-MAF-004 glob check using ExpressionPattern.ReplaceAllString
  to strip expressions before checking for "*", so a "*" inside an
  expression body is not falsely flagged
- Move syntax/param validation inside a nested containsExpression() guard
- Remove now-unused validateEngineModelNotGlob helper
- Rename literalParts -> literalText for clarity
- Extend TestValidateModelAliasMap_EngineModelExpressionForms with 3 new
  subtests covering expression+trailing-glob, literal-glob-before-expression,
  and expression-between-globs; also assert the original value is quoted in
  the error message

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/2beddf03-18a0-4e6a-a2a8-0c1f01a29c98

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented May 3, 2026

@copilot review all comments

Addressed in commit 1b24e5e. The reviewer's concern about V-MAF-004 being bypassed for expression-containing values was valid.

Fix in model_alias_validation.go: The engine.model guard now always enforces the glob restriction on the literal text (using ExpressionPattern.ReplaceAllString to strip ${{ ... }} segments before checking for *). Only the syntax/param parsing checks are skipped for expression-containing values. The unused validateEngineModelNotGlob helper was removed.

New test cases in TestValidateModelAliasMap_EngineModelExpressionForms:

  • "${{ inputs.model }}*" → rejected with V-MAF-004 (trailing literal glob)
  • "copilot/*${{ inputs.model }}" → rejected with V-MAF-004 (leading literal glob)
  • "*${{ inputs.model }}*" → rejected with V-MAF-004 (globs on both sides)

@pelikhan pelikhan merged commit bc024f2 into main May 3, 2026
@pelikhan pelikhan deleted the copilot/update-models-alias-parser branch May 3, 2026 20:15
Copilot stopped work on behalf of pelikhan due to an error May 3, 2026 20:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants