Skip to content

compiler: sort manifest header sections for deterministic lock output#16138

Closed
Copilot wants to merge 4 commits intomainfrom
copilot/canonicalize-header-ordering
Closed

compiler: sort manifest header sections for deterministic lock output#16138
Copilot wants to merge 4 commits intomainfrom
copilot/canonicalize-header-ordering

Conversation

Copy link
Contributor

Copilot AI commented Feb 16, 2026

Make manifest/import header ordering deterministic for stable lock output

Summary

Successfully implemented deterministic sorting for ImportedFiles and IncludedFiles in workflow lock file headers. This ensures stable output and prevents noisy diffs when workflows are recompiled.

Changes Made

1. Modified pkg/workflow/compiler_yaml.go

  • Added sort import
  • Sort ImportedFiles before rendering (lines 87-89)
  • Sort IncludedFiles before rendering (lines 101-103)
  • Uses sort.Strings() for alphabetical ordering
  • Creates copies to avoid modifying original data

2. Added TestManifestHeaderOrderingDeterministic regression test

  • Tests 4 scenarios with non-alphabetical input:
    • Reverse alphabetical imports
    • Reverse alphabetical includes
    • Mixed order for both
    • Nested paths
  • Verifies files always appear in sorted order
  • All tests pass ✓

3. Recompiled workflows with sorted headers

  • 3 workflows updated with properly sorted imports:
    • ci-coach.lock.yml: ci-data-analysis now before ci-optimization-strategies
    • copilot-session-insights.lock.yml: copilot-session-data-fetch now before jqschema
    • python-data-charts.lock.yml: charts-with-trending now before mood

Validation

  • ✅ New test passes with all 4 subtests
  • ✅ Existing ANSI stripping test still passes
  • ✅ All YAML-related tests pass
  • ✅ Parser import tests pass
  • ✅ Frontmatter hash tests pass (unchanged behavior)
  • ✅ Code formatted with make fmt
  • ✅ Manual verification of existing workflows
  • ✅ Successfully recompiled 154 workflows

Technical Details

  • No behavior change outside header rendering
  • No modification of original ImportedFiles/IncludedFiles slices
  • Preserves existing ANSI stripping and path normalization
  • Compatible with topological sorting done at import time
  • Minimal code changes (11 lines added)

Acceptance Criteria Met

✅ Header output always emits imports/includes in sorted order
✅ Regression test validates deterministic output with mixed-order inputs
✅ No behavior change outside header rendering

Original prompt

This section details on the original issue you should resolve

<issue_title>compiler: make manifest/import header ordering deterministic for stable lock output</issue_title>
<issue_description>## Problem
Lock output can drift in non-semantic ways when manifest-style header sections (ImportedFiles / IncludedFiles) are emitted in varying order. This creates noisy diffs and undermines deterministic compile evidence.

Expected behavior

Manifest header sections should be emitted in a stable, canonical order independent of input slice ordering.

Scope (Phase 1)

  • Canonicalize ordering of imported/included file lists in workflow header generation.
  • Add regression tests that prove deterministic output for mixed-order inputs.

Acceptance criteria

  • Header output always emits imports/includes in sorted order.
  • Regression test fails on non-deterministic ordering and passes with fix.
  • No behavior change outside header rendering.
    </issue_description>

Comments on the Issue (you are @copilot in this section)


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 2 commits February 16, 2026 15:59
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix header ordering for deterministic lock output compiler: sort manifest header sections for deterministic lock output Feb 16, 2026
Copilot AI requested a review from pelikhan February 16, 2026 16:03
@pelikhan
Copy link
Contributor

@copilot recompile

@pelikhan pelikhan marked this pull request as ready for review February 16, 2026 16:09
Copilot AI review requested due to automatic review settings February 16, 2026 16:09
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 16, 2026

@copilot recompile

Recompiled all workflows. 3 lock files updated with sorted manifest headers (8f4ad5e):

  • ci-coach.lock.yml
  • copilot-session-insights.lock.yml
  • python-data-charts.lock.yml

All imports are now in alphabetical order.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/graphql
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw (http block)
  • https://api.github.com/repos/actions/ai-inference/git/ref/tags/v2
    • Triggering command: /usr/bin/gh gh api /repos/actions/ai-inference/git/ref/tags/v2 --jq .object.sha (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/11bd71901bbe5b1630ceea73d27597364c9af683
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/11bd71901bbe5b1630ceea73d27597364c9af683 --jq .object.sha (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v6
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha (http block)
  • https://api.github.com/repos/actions/github-script/git/ref/tags/v7
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v7 --jq .object.sha (http block)
  • https://api.github.com/repos/actions/setup-go/git/ref/tags/4dc6199c7b1a012772edbd06daecab0f50c9053c
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-go/git/ref/tags/4dc6199c7b1a012772edbd06daecab0f50c9053c --jq .object.sha (http block)
  • https://api.github.com/repos/actions/setup-go/git/ref/tags/v5
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-go/git/ref/tags/v5 --jq .object.sha (http block)
  • https://api.github.com/repos/actions/setup-go/git/ref/tags/v6
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-go/git/ref/tags/v6 --jq .object.sha (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-go/git/ref/tags/v6 --jq .object.sha &#34; -o gh-aw ./cmd-f (http block)
  • https://api.github.com/repos/actions/setup-node/git/ref/tags/v6
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-node/git/ref/tags/v6 --jq .object.sha (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-node/git/ref/tags/v6 --jq .object.sha .js (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-node/git/ref/tags/v6 --jq .object.sha -c=4 -nolocalimports -importcfg /tmp/go-build1742175393/b320/importcfg -embedcfg /tmp/go-build1742175393/b320/embedcfg -pack (http block)
  • https://api.github.com/repos/anchore/sbom-action/git/ref/tags/v0
    • Triggering command: /usr/bin/gh gh api /repos/anchore/sbom-action/git/ref/tags/v0 --jq .object.sha (http block)
  • https://api.github.com/repos/docker/build-push-action/git/ref/tags/v6
    • Triggering command: /usr/bin/gh gh api /repos/docker/build-push-action/git/ref/tags/v6 --jq .object.sha (http block)
  • https://api.github.com/repos/docker/login-action/git/ref/tags/v3
    • Triggering command: /usr/bin/gh gh api /repos/docker/login-action/git/ref/tags/v3 --jq .object.sha (http block)
  • https://api.github.com/repos/docker/metadata-action/git/ref/tags/v5
    • Triggering command: /usr/bin/gh gh api /repos/docker/metadata-action/git/ref/tags/v5 --jq .object.sha (http block)
  • https://api.github.com/repos/docker/setup-buildx-action/git/ref/tags/v3
    • Triggering command: /usr/bin/gh gh api /repos/docker/setup-buildx-action/git/ref/tags/v3 --jq .object.sha (http block)
  • https://api.github.com/repos/github/gh-aw/git/ref/tags/c4e091835c7a94dc7d3acb8ed3ae145afb4995f3
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/c4e091835c7a94dc7d3acb8ed3ae145afb4995f3 --jq .object.sha (http block)
  • https://api.github.com/repos/githubnext/agentics/git/ref/tags/-
    • Triggering command: /usr/bin/gh gh api /repos/githubnext/agentics/git/ref/tags/- --jq .object.sha (http block)
  • https://api.github.com/user
    • Triggering command: /usr/bin/gh gh api user --jq .login (http block)

If you need me to access, download, or install something from one of these locations, you can either:

Copy link
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

This PR makes workflow lock file manifest headers deterministic by sorting the ImportedFiles and IncludedFiles lists when rendering the YAML header, reducing non-semantic diffs in compiled outputs.

Changes:

  • Sort ImportedFiles and IncludedFiles at render time in generateWorkflowHeader.
  • Add a regression test to assert stable ordering for multiple input permutations.
  • Update existing workflow lock files to reflect the new canonical ordering.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
pkg/workflow/compiler_yaml.go Sorts manifest header sections (Imports / Includes) to make lock output deterministic.
pkg/workflow/compiler_yaml_test.go Adds a regression test covering multiple orderings of imports/includes.
.github/workflows/python-data-charts.lock.yml Updates manifest comment ordering to match new deterministic render.
.github/workflows/copilot-session-insights.lock.yml Updates manifest comment ordering to match new deterministic render.
.github/workflows/ci-coach.lock.yml Updates manifest comment ordering to match new deterministic render.
Comments suppressed due to low confidence (2)

pkg/workflow/compiler_yaml.go:95

  • Sorting is done on the raw file strings, but the rendered output is normalized via StripANSIEscapeCodes() and filepath.ToSlash(). This can still produce non-deterministic ordering across platforms/inputs (e.g., Windows "a\b.md" sorts differently than normalized "a/b.md" vs a sibling "a0.md"). Consider normalizing/stripping into a temporary slice first, then sorting that cleaned slice before rendering.

This issue also appears on line 100 of the same file.

			// Sort imports for deterministic output
			sortedImports := make([]string, len(data.ImportedFiles))
			copy(sortedImports, data.ImportedFiles)
			sort.Strings(sortedImports)
			for _, file := range sortedImports {
				cleanFile := stringutil.StripANSIEscapeCodes(file)
				// Normalize to Unix paths (forward slashes) for cross-platform compatibility
				cleanFile = filepath.ToSlash(cleanFile)
				fmt.Fprintf(yaml, "#     - %s\n", cleanFile)
			}

pkg/workflow/compiler_yaml.go:108

  • Same issue as imports: includes are sorted before ANSI stripping and path normalization. To guarantee deterministic lock output independent of path separator / formatting, normalize+strip first and sort the cleaned values that will actually be rendered.
			// Sort includes for deterministic output
			sortedIncludes := make([]string, len(data.IncludedFiles))
			copy(sortedIncludes, data.IncludedFiles)
			sort.Strings(sortedIncludes)
			for _, file := range sortedIncludes {
				cleanFile := stringutil.StripANSIEscapeCodes(file)
				// Normalize to Unix paths (forward slashes) for cross-platform compatibility
				cleanFile = filepath.ToSlash(cleanFile)
				fmt.Fprintf(yaml, "#     - %s\n", cleanFile)

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

Comment on lines +1266 to +1292
// Test multiple orderings of imported and included files
tests := []struct {
name string
importedFiles []string
includedFiles []string
}{
{
name: "reverse_alphabetical_imports",
importedFiles: []string{"z-file.md", "m-file.md", "a-file.md"},
includedFiles: []string{},
},
{
name: "reverse_alphabetical_includes",
importedFiles: []string{},
includedFiles: []string{"z-include.md", "m-include.md", "a-include.md"},
},
{
name: "mixed_order_both",
importedFiles: []string{"b-import.md", "a-import.md", "c-import.md"},
includedFiles: []string{"y-include.md", "x-include.md", "z-include.md"},
},
{
name: "nested_paths",
importedFiles: []string{"shared/z.md", "common/a.md", "lib/m.md"},
includedFiles: []string{"tools/y.md", "utils/b.md", "helpers/k.md"},
},
}
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

The new determinism test only exercises already-normalized paths. Since generateWorkflowHeader also normalizes paths (ToSlash) and strips ANSI, add a case with Windows-style separators (and/or ANSI codes) to ensure ordering is deterministic after normalization (not just after sorting the raw input slice).

Copilot uses AI. Check for mistakes.
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.

compiler: make manifest/import header ordering deterministic for stable lock output

2 participants