Skip to content

Refactor AWF helpers into focused workflow modules - #48633

Closed
pelikhan with Copilot wants to merge 2 commits into
mainfrom
copilot/refactor-awf-helpers-file
Closed

Refactor AWF helpers into focused workflow modules#48633
pelikhan with Copilot wants to merge 2 commits into
mainfrom
copilot/refactor-awf-helpers-file

Conversation

Copilot AI commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

pkg/workflow/awf_helpers.go had grown into a large mixed-responsibility file, with its coverage concentrated in an equally large companion test file. This change decomposes both into smaller domain-focused units while preserving the existing AWF command-building surface and behavior.

  • Code split by responsibility

    • awf_command.go: top-level AWF command assembly
    • awf_command_builder.go: AWF args, command prefix, shell wrapping
    • awf_capabilities.go: AWF version/capability gates
    • awf_env.go: env exclusion and max-AI-credits injection
    • awf_digest.go: image digest lookup and tag augmentation
    • awf_arc_dind.go: ARC/DinD path rewriting and chroot patch helpers
    • awf_helpers.go: shared constants/config and workflow-call network_allowed handling
  • Tests split along the same boundaries

    • Replaced awf_helpers_test.go with focused test files for:
      • API target helpers
      • command/args behavior
      • capability gates
      • env handling
      • digest resolution
      • ARC/DinD behavior
  • Behavior kept stable

    • Public entry points remain unchanged.
    • Existing logic for AWF command construction, version gating, env exclusion, digest pinning, and ARC/DinD rewriting is preserved; the change is organizational rather than semantic.

Example of the new boundary:

// command assembly
func BuildAWFCommand(config AWFCommandConfig) string

// env handling
func ComputeAWFExcludeEnvVarNames(workflowData *WorkflowData, coreSecretVarNames []string) []string

// capability gating
func awfSupportsCliProxy(firewallConfig *FirewallConfig) bool

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Refactor pkg/workflow/awf_helpers.go into smaller files Refactor AWF helpers into focused workflow modules Jul 28, 2026
Copilot AI requested a review from pelikhan July 28, 2026 14:08
@pelikhan
pelikhan marked this pull request as ready for review July 28, 2026 14:12
Copilot AI review requested due to automatic review settings July 28, 2026 14:12
@pelikhan pelikhan closed this Jul 28, 2026
@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Splits AWF integration and tests into focused modules while preserving existing behavior and APIs.

Changes:

  • Separates command, capability, environment, digest, and ARC/DinD logic.
  • Reorganizes the monolithic test suite by domain.
  • Retains shared configuration and workflow-call helpers.
Show a summary per file
File Description
awf_helpers.go Retains shared configuration helpers.
awf_command.go Houses command assembly.
awf_command_builder.go Builds arguments and shell wrappers.
awf_capabilities.go Contains version capability gates.
awf_env.go Handles environment exclusions and credits.
awf_digest.go Resolves image digests.
awf_arc_dind.go Handles ARC/DinD rewriting.
awf_helpers_test.go Removes the monolithic test suite.
awf_command_test.go Tests command assembly.
awf_args_test.go Tests argument construction.
awf_capabilities_test.go Tests capability gates.
awf_env_test.go Tests environment handling.
awf_digest_test.go Tests digest resolution.
awf_arc_dind_test.go Tests ARC/DinD behavior.
awf_api_helpers_test.go Tests API helper behavior.
awf_custom_api_targets_test.go Tests custom API targets.
awf_provider_targets_test.go Tests provider targets.

Review details

Tip

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

  • Files reviewed: 17/17 changed files
  • Comments generated: 2
  • Review effort level: Medium

Comment on lines +12 to +14
// BuildAWFCommand builds a complete AWF command with all arguments.
// This consolidates the AWF command building logic that was duplicated across
// Copilot, Claude, and Codex engines.
Comment on lines +38 to +39
// When AWF also supports chroot config (v0.27.1+), the Python patch body is embedded inside
// the same if-block so the script only contains one DOCKER_HOST condition check.
@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 82/100 — Excellent

Analyzed 37 test(s): 32 design, 5 implementation, 0 violation(s).

📊 Metrics (37 tests)
Metric Value
Analyzed 37 (Go: 37, JS: 0)
✅ Design 32 (86%)
⚠️ Implementation 5 (14%)
Edge/error coverage 31 (84%)
Duplicate clusters 0
Inflation 2 files (awf_capabilities 5:1, awf_arc_dind 3.3:1)
🚨 Violations 0
Test Category Examples
Design TestArcDindDockerHostDetection, TestBuildAWFCommand_IncludesChrootInjectScript, TestBuildAWFConfigJSON, TestAWFSupportsExcludeEnv
Implementation TestMainAgentRunUsesStandardCreditsExpressionNotDetectionExpression (config detail)
Edge/Error nil configs, version gating, invalid URLs, missing env vars

Quality Signals ✅

  • All 9 new test files have mandatory //go:build !integration tags — codifies unit-test discipline.
  • Strong assertion context — 94% of assertions include descriptive failure messages (e.g., "expected docker host passthrough value to be set for DOCKER_HOST=%s").
  • Behavioral contracts — tests verify user-visible outputs: generated shell scripts, CLI args, JSON configs, version compatibility gates. Not internal implementation details.
  • Error case coverage — nil/empty configs, version boundaries, URL parsing edge cases, invalid inputs are explicitly tested.
  • Table-driven structure — 20+ subtests using t.Run() per function, enabling clear scenario documentation.
  • No mock library violations — zero gomock/testify/mock usage; tests rely on real data structures and assertion patterns.

Test Inflation Analysis

Two files exceed the 2:1 ratio:

  • awf_capabilities_test.go (358 lines): awf_capabilities.go (71 lines) = 5.0:1 — justified by 9 version-gating behavior tests and 7 flag compatibility scenarios.
  • awf_arc_dind_test.go (184 lines): awf_arc_dind.go (56 lines) = 3.3:1 — justified by shell regex validation, Docker host passthrough, and chroot injection script structural verification.

These ratios are acceptable for configuration helper functions with multiple version-dependent code paths and shell-script generation.

Verdict

passed. 14% implementation tests (threshold: 30%). Refactoring from monolithic awf_helpers_test.go (deleted) to focused modules is well-covered. Tests validate behavioral contracts across version gates, configuration generation, and shell script emission — not implementation plumbing.

🧪 Test quality analysis by Test Quality Sentinel · haiku45 · 22.5 AIC · ⌖ 10.5 AIC · ⊞ 8.1K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Test Quality Sentinel: 82/100. 14% implementation tests (threshold: 30%). All 37 new tests verify behavioral contracts with strong edge-case coverage and proper build tags. Zero mock library violations.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review: Refactor AWF helpers into focused workflow modules

The decomposition is well-structured — splitting a 1000-line mixed-responsibility file into seven focused modules (command, builder, capabilities, env, digest, arc-dind, helpers) with matching test splits is a clean improvement. Behaviour is preserved as claimed.

Two issues found:

  1. Blocking — nil dereference in ComputeAWFExcludeEnvVarNames (awf_env.go line 106): workflowData fields are accessed without a nil guard; the function lacks the top-level if workflowData == nil { return coreSecretVarNames } that comparable helpers use. This is a latent panic.

  2. Non-blocking — duplicated run-type switch in BuildAWFCommand (awf_command.go lines ~152-168): the IsEvalsRun/IsDetectionRun switch appears twice in the same block. These must stay in sync if a new run type is added.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 90 AIC · ⌖ 5.96 AIC · ⊞ 5K

Comment thread pkg/workflow/awf_env.go
}

// GitHub MCP server token is always a secret when the GitHub tool is present.
if hasGitHubTool(workflowData.ParsedTools) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nil dereference in ComputeAWFExcludeEnvVarNames

workflowData is dereferenced without a nil guard. Line 106 calls workflowData.ParsedTools, line 111 workflowData.Tools, line 118 workflowData.MCPScripts, and line 155 workflowData.ExcludedEnv — all of which panic if workflowData is nil. The function accepts a pointer but has no top-level nil guard, unlike similar helpers (HasMCPServers, isGitHubCLIModeEnabled) which safely handle nil.

Suggested fix:

func ComputeAWFExcludeEnvVarNames(workflowData *WorkflowData, coreSecretVarNames []string) []string {
	if workflowData == nil {
		return coreSecretVarNames
	}
	// ... rest unchanged
}

@copilot please address this.

defaultMaxAICredits := strconv.FormatInt(constants.DefaultMaxAICredits, 10)
if config.WorkflowData != nil {
switch {
case config.WorkflowData.IsEvalsRun:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Duplicated run-type switch in BuildAWFCommand

The IsEvalsRun/IsDetectionRun switch appears twice in the same if block — once to pick defaultMaxAICredits (lines 150-156) and again to pick the right compilerenv.Build* expression (lines 164-168). Both blocks must be updated together if a new run type is ever added, creating a silent divergence risk.

Suggested refactor — compute the run type once:

runType := runTypeStandard
if config.WorkflowData != nil {
    switch {
    case config.WorkflowData.IsEvalsRun:
        runType = runTypeEvals
    case config.WorkflowData.IsDetectionRun:
        runType = runTypeDetection
    }
}
// then use runType for both defaultMaxAICredits and expr selection

@copilot please address this.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Verdict: Request changes — mostly a clean mechanical split, but one real test-coverage regression must be fixed before merge.

Verified function-level parity between the original awf_helpers.go/awf_helpers_test.go and all seven new split files: no functions were dropped, added, or altered, go build ./... and go vet ./... are clean, and no duplicated consts/vars across the new files. The organizational split is faithful to the stated goal.

💡 Themes and verification notes
  • Compared func signatures across the old monolith and the new combined files: zero diffs.
  • Manually reviewed awf_env.go, awf_digest.go, awf_capabilities.go, awf_arc_dind.go, awf_command_builder.go, and the trimmed awf_helpers.go line-by-line — logic, constants, and doc comments carried over verbatim with no behavior drift.
  • One blocking finding: TestBuildAWFArgsCliProxy (testing real BuildAWFArgs --difc-proxy-host/--difc-proxy-ca-cert injection behavior, not just the capability-gate predicate) was dropped during the test-file split. Confirmed by diffing test function names between the deleted awf_helpers_test.go and the new split test files — this was the only missing test function out of ~38. Flagged as a review comment on awf_args_test.go.
  • No other coverage gaps found; all other original test functions have a home in the new split files.

🔎 Code quality review by PR Code Quality Reviewer · sonnet50 · 75.5 AIC · ⌖ 4.51 AIC · ⊞ 7.4K
Comment /review to run again

// But should still emit --enable-host-access for backward compat
assert.Contains(t, args, "--enable-host-access", "Should still emit --enable-host-access for legacy mode")
})
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Test coverage regression: TestBuildAWFArgsCliProxy (4 subtests exercising BuildAWFArgs's actual --difc-proxy-host/--difc-proxy-ca-cert arg injection and version gating) from the deleted awf_helpers_test.go was dropped during the split and not migrated into this file.

💡 Details

The original pkg/workflow/awf_helpers_test.go (lines 1349-1526, verified via git show <base>:pkg/workflow/awf_helpers_test.go) contained TestBuildAWFArgsCliProxy, which asserted that BuildAWFArgs correctly:

  • omits --difc-proxy-host/--difc-proxy-ca-cert/deprecated --enable-cli-proxy/--cli-proxy-policy when the cli-proxy feature flag is absent
  • includes --difc-proxy-host host.docker.internal:18443 and --difc-proxy-ca-cert when cli-proxy is enabled and the firewall version supports it
  • version-gates the flags correctly against awfSupportsCliProxy

Only TestAWFSupportsCliProxy (in awf_capabilities_test.go) survived the split, and it only tests the boolean capability-gate predicate — not the actual BuildAWFArgs arg-construction/injection behavior. This is a real functional-behavior test, not boilerplate, and its absence means a regression in the CLI-proxy arg-building logic in awf_command_builder.go/awf_command.go would go undetected by CI.

Fix: port TestBuildAWFArgsCliProxy from the original file (same subtests) into awf_args_test.go, since that's where the rest of BuildAWFArgs arg-construction tests now live.

@github-actions

Copy link
Copy Markdown
Contributor

Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (3,413 new lines in pkg/workflow/) but does not have a linked Architecture Decision Record (ADR).

Draft ADR committed: docs/adr/48633-refactor-awf-helpers-into-focused-modules.md — review and complete it before merging.

This PR cannot merge until an ADR is linked in the PR body.

What to do next
  1. Review the draft ADR committed to your branch — it was generated from the PR diff
  2. Complete the missing sections — add context the AI could not infer, refine the decision rationale, and list real alternatives you considered
  3. Commit the finalized ADR to docs/adr/ on your branch
  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-48633: Refactor AWF Helpers into Focused Workflow Modules

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

Why ADRs Matter

ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you.

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 numbered by PR number (e.g., 48633-refactor-awf-helpers-into-focused-modules.md for PR #48633).

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · sonnet46 · 50.6 AIC · ⌖ 15.7 AIC · ⊞ 8.5K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Skills-Based Review 🧠

Applied /codebase-design and /tdd — commenting with a few targeted improvements; no blocking issues.

📋 Key Themes & Highlights

Key Themes

  • Stale doc comment (awf_digest.go): agent-act is appended in code but omitted from the function's example format string — easy one-liner fix.
  • Module boundary leak (awf_env.go): injectMaxAICreditsExpression is a JSON config manipulation helper with its only call site in awf_command.go; it should live there (or in a awf_config.go).
  • Duplicate switch logic (awf_command.go): Two nearly identical IsEvalsRun/IsDetectionRun switches within the same block can be collapsed into one, removing the silent redundancy in defaultMaxAICredits.
  • Thin test file for a large function (awf_command_test.go): BuildAWFCommand is 399 lines; the dedicated test file has 2 cases. Key paths (tool-cache probe, ARC/DinD pre-create, config-file printf, ResolveMaxAICreditsFromEnv) are tested elsewhere or not at all.

Positive Highlights

  • ✅ Excellent decomposition: seven focused modules with clear, non-overlapping responsibilities.
  • ✅ Test suite restructured in parallel — each domain now has its own _test.go.
  • ✅ Public API surface preserved; this is a pure organisational refactor.
  • ✅ Thorough nil-safety across all new modules.
  • ✅ Sorting of --exclude-env args for deterministic lock-file output is a nice correctness touch.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 · 68.2 AIC · ⌖ 5.24 AIC · ⊞ 6.7K
Comment /matt to run again


// buildAWFImageTagWithDigests returns an image tag value for AWF's --image-tag flag.
// When known firewall container digests are available, it appends AWF's digest
// metadata format:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/codebase-design] The doc comment lists four digest keys (squid, agent, api-proxy, cli-proxy) but the code also appends agent-act (line 32). The comment is stale and will mislead anyone reasoning about which images are digest-pinned.

💡 Suggested fix

Update both doc examples to include agent-act:

// <tag>,squid=sha256:...,agent=sha256:...,agent-act=sha256:...,api-proxy=sha256:...,cli-proxy=sha256:...
// For arc-dind: same + ,build-tools=sha256:...

@copilot please address this.

Comment thread pkg/workflow/awf_env.go
// shellEscapeArgWithVarPreserved is then used to double-quote the JSON arg while
// preserving the ${varName} reference for bash expansion and escaping bare $ signs
// (e.g. "$schema" → "\$schema").
func injectMaxAICreditsExpression(awfConfigJSON string, expr string) string {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/codebase-design] injectMaxAICreditsExpression manipulates raw config JSON and is only called from awf_command.go. Its current home in awf_env.go (env exclusion / credits env vars) breaks the module boundary described in the PR description.

💡 Why this matters

The split declares awf_env.go owns env exclusion and credits-env injection, while awf_command.go owns command assembly. injectMaxAICreditsExpression is a JSON string manipulation step used only at command-assembly time — it belongs in awf_command.go alongside its sole call site, or in a new awf_config.go.

Moving it will make awf_env.go a clean leaf module with no dependency on JSON config shape.

@copilot please address this.

cmd := BuildAWFCommand(config)
assert.NotContains(t, cmd, "--allow-host-service-ports", "Should NOT emit --allow-host-service-ports in strict mode")
})
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/tdd] awf_command_test.go covers only 2 scenarios for BuildAWFCommand: service-port legacy mode and the credits-expression routing check. The 399-line BuildAWFCommand function has significant untested paths — ARC/DinD mount pre-creation, tool-cache probe, config-file printf setup, and the ResolveMaxAICreditsFromEnv branch.

💡 Suggested additions

At minimum, add:

  • A test verifying mkdir -p is emitted for ARC/DinD mounts (topology = ArcDind).
  • A test for ResolveMaxAICreditsFromEnv=true to confirm the shell-variable form (${GH_AW_MAX_AI_CREDITS:-1000}) is used instead of the Actions expression.
  • A test that awf-config.json printf line appears in the output.

awf_arc_dind_test.go covers TestBuildAWFCommand_ArcDindPreCreatesMountDirs but it is isolated in a different file — a note or cross-reference here would help discoverability.

@copilot please address this.

defaultMaxAICredits := strconv.FormatInt(constants.DefaultMaxAICredits, 10)
if config.WorkflowData != nil {
switch {
case config.WorkflowData.IsEvalsRun:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/codebase-design] There are two consecutive switch blocks on IsEvalsRun/IsDetectionRun (lines 152–155 for defaultMaxAICredits, lines 164–168 for expr) that are almost identical. The first is redundant — defaultMaxAICredits is set but then overridden when ResolveMaxAICreditsFromEnv is false (the common path), which always picks expr from the second switch.

💡 Suggested refactor

Collapse into a single switch that sets both defaultMaxAICredits and the right builder function, then derive expr once:

creditsDefault := strconv.FormatInt(constants.DefaultMaxAICredits, 10)
buildExpr := compilerenv.BuildDefaultMaxAICreditsExpression
if config.WorkflowData != nil && (config.WorkflowData.IsEvalsRun || config.WorkflowData.IsDetectionRun) {
    creditsDefault = strconv.FormatInt(constants.DefaultDetectionMaxAICredits, 10)
    if config.WorkflowData.IsEvalsRun {
        buildExpr = compilerenv.BuildDefaultEvalsMaxAICreditsExpression
    } else {
        buildExpr = compilerenv.BuildDefaultDetectionMaxAICreditsExpression
    }
}

This also makes it obvious that evals and detection share the same fallback integer but differ in the expression builder.

@copilot please address this.

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.

[file-diet] Refactor: pkg/workflow/awf_helpers.go exceeds 800 lines (1131 LOC)

3 participants