-
Notifications
You must be signed in to change notification settings - Fork 412
Refactor duplicated workflow helpers and remove dead audit render stubs #36010
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ae15296
56b74ed
5f4b247
7611200
34e0bcb
e2964da
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| # ADR-36010: Consolidate Duplicated Workflow Helpers into Canonical Shared Functions | ||
|
|
||
| **Date**: 2026-05-31 | ||
| **Status**: Draft | ||
|
|
||
| ## Context | ||
|
|
||
| A semantic clustering audit of `pkg/workflow` and `pkg/cli` surfaced a small set of verified duplicates and dead code: engine-ID resolution (`EngineConfig.ID` → `AI` fallback) was implemented independently in both `pkg/cli/mcp_inspect.go` and `pkg/workflow/observability_otlp.go`; two YAML single-quote escapers (`escapeYAMLSingleQuotedScalar` and `escapeSingleQuotedYAMLString`) had byte-identical behavior; the safe-output JSON builders for jobs and actions repeated the same normalize/sort/marshal flow; and four `pkg/cli/audit_report_render_*.go` files were empty `package cli` stubs. Duplicated logic across packages drifts over time and obscures the single intended behavior, while empty stubs imply structure that does not exist. | ||
|
|
||
| ## Decision | ||
|
|
||
| We will consolidate each cluster of duplicated logic into a single canonical helper and delete the dead stubs. Specifically: `workflow.ResolveEngineID` becomes the one engine-ID resolver (exported so `pkg/cli` can reuse it while keeping its CLI-specific `"unknown"` fallback at the call site); `escapeYAMLSingleQuoted` in `pkg/workflow/strings.go` becomes the one YAML single-quote escaper; `buildNormalizedSortedJSON` captures the shared normalize/sort/marshal flow for safe-output job and action maps; and the distinct backslash-based escaper is renamed `escapeSingleQuoteBackslash` to make its differing semantics explicit rather than collapsing it into the YAML escaper. The four empty audit-render files are removed. The refactor is behavior-preserving and covered by focused regression tests. | ||
|
|
||
| ## Alternatives Considered | ||
|
|
||
| ### Alternative 1: Leave the duplicates in place | ||
| Each duplicate is small and individually harmless. Leaving them avoids new cross-package coupling. Rejected because the audit confirmed the implementations are genuinely identical, and divergence over time (a fix applied to one copy but not the other) is the exact failure mode this codebase wants to avoid for security-adjacent helpers like YAML escaping. | ||
|
|
||
| ### Alternative 2: Extract the helpers into a new dedicated utility package | ||
| A neutral `internal/util`-style package would avoid `pkg/cli` taking a dependency on `pkg/workflow`. Rejected because `pkg/cli` already depends on `pkg/workflow` for the `WorkflowData` types these helpers operate on, so the canonical home is the package that owns the domain type; a new package would add an indirection layer without removing any dependency edge. | ||
|
|
||
| ## Consequences | ||
|
|
||
| ### Positive | ||
| - Single source of truth for engine-ID resolution, YAML single-quote escaping, and safe-output JSON normalization — fixes apply in exactly one place. | ||
| - Renaming the backslash escaper to `escapeSingleQuoteBackslash` makes the two distinct escaping semantics explicit and prevents an accidental future merge of the two. | ||
| - Net reduction in code and the removal of four misleading empty package stubs. | ||
|
|
||
| ### Negative | ||
| - `pkg/cli` now calls `workflow.ResolveEngineID`, tightening (though not newly introducing) the cli→workflow coupling; the CLI-specific `"unknown"` fallback now lives at the call site, slightly separated from the resolution logic. | ||
| - Shared helpers raise the blast radius of a future change: a regression in `escapeYAMLSingleQuoted` or `buildNormalizedSortedJSON` now affects every caller at once. | ||
|
|
||
| ### Neutral | ||
| - Output shape of the safe-output JSON and OTLP env rendering is unchanged; the refactor is verified behavior-preserving by the added tests. | ||
| - `ResolveEngineID` returns `""` (not `"unknown"`) when no engine is available; the `"unknown"` presentation concern is intentionally pushed to each caller. | ||
|
|
||
| --- | ||
|
|
||
| *This is a DRAFT ADR generated by the [Design Decision Gate](https://github.com/github/gh-aw/actions/runs/26699729023) workflow. The PR author must review, complete, and finalize this document before the PR can merge.* |
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -498,5 +498,5 @@ func uniqueSorted(values []string) []string { | |
| } | ||
|
|
||
| func escapeSingleQuotedYAMLString(input string) string { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unnecessary wrapper: 💡 Suggested fixSince both call sites in this file are in the same package, replace them directly with // Before (two calls in the same file):
escapeSingleQuotedYAMLString(string(slashRoutesJSON))
escapeSingleQuotedYAMLString(string(labelRoutesJSON))
// After:
escapeYAMLSingleQuoted(string(slashRoutesJSON))
escapeYAMLSingleQuoted(string(labelRoutesJSON))The wrapper function serves no purpose now that the canonical name is |
||
| return strings.ReplaceAll(input, "'", "''") | ||
| return escapeYAMLSingleQuoted(input) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/improve-codebase-architecture] 💡 ContextIf there are other callers in the package that use |
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[/tdd] The replacement test only exercises
&workflow.WorkflowData{}(empty struct). The previous table-driven test also covered thenilinput — which now follows a different path:ResolveEngineID(nil)returns"", the call-site guard inrenderMCPInspectionTreesets it to"unknown", and the tree label becomes"Engine: unknown". That nil → call-site-fallback path is not explicitly exercised here.💡 Suggested additional case
Since the nil guard now lives in the caller rather than inside
ResolveEngineID, a direct integration test of the nil path throughrenderMCPInspectionTreegives confidence that the call-site fallback is wired correctly.