compiler: sort manifest header sections for deterministic lock output#16138
compiler: sort manifest header sections for deterministic lock output#16138
Conversation
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
|
@copilot recompile |
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Recompiled all workflows. 3 lock files updated with sorted manifest headers (8f4ad5e):
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:
If you need me to access, download, or install something from one of these locations, you can either:
|
There was a problem hiding this comment.
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
ImportedFilesandIncludedFilesat render time ingenerateWorkflowHeader. - 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.
| // 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"}, | ||
| }, | ||
| } |
There was a problem hiding this comment.
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).
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.gosortimportsort.Strings()for alphabetical ordering2. Added
TestManifestHeaderOrderingDeterministicregression test3. Recompiled workflows with sorted headers
ci-coach.lock.yml: ci-data-analysis now before ci-optimization-strategiescopilot-session-insights.lock.yml: copilot-session-data-fetch now before jqschemapython-data-charts.lock.yml: charts-with-trending now before moodValidation
make fmtTechnical Details
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
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.