Skip to content

Add regression tests for gh-aw-firewall digest pinning at default version (0.27.22)#43343

Merged
pelikhan merged 4 commits into
mainfrom
copilot/gh-aw-firewall-fix-digest-pinning
Jul 4, 2026
Merged

Add regression tests for gh-aw-firewall digest pinning at default version (0.27.22)#43343
pelikhan merged 4 commits into
mainfrom
copilot/gh-aw-firewall-fix-digest-pinning

Conversation

Copilot AI commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Consumer-compiled lock files on v0.82.2 emitted all four gh-aw-firewall images (agent, api-proxy, cli-proxy, squid) with tag-only references — no digest/pinned_image. The embedded pins in action_pins.json were already correct for 0.27.22, but existing tests only covered the old 0.27.0 version and did not cover cli-proxy (new in v0.82) at all. This left no guard against the regression.

Changes

  • docker_pin_test.go — new table case "embedded firewall pins for default version (0.27.22) — all four images" asserts that agent, api-proxy, cli-proxy, and squid at 0.27.22 all resolve via the embedded fallback when no local cache is present.

  • docker_firewall_pin_compile_test.go — new end-to-end test TestCompileWorkflow_FirewallImagesPinnedForDefaultVersion compiles a workflow at the default AWF version with tools.github.mode: gh-proxy (to include cli-proxy) and asserts that all four images appear with full image/digest/pinned_image entries in the manifest and that their digests are embedded in the --image-tag AWF config value.

If DefaultFirewallVersion is bumped without refreshing action_pins.json, these tests will now fail immediately rather than silently shipping unpinned images to consumers.

…sion (0.27.22)

Fixes #43307: the four gh-aw-firewall images (agent, api-proxy, cli-proxy,
squid) at tag 0.27.22 were emitted tag-only in consumer lock files because
there were no tests guarding the embedded pins for the current default
version or for the cli-proxy image introduced in v0.82.

The embedded action_pins.json already contained correct 0.27.22 digests;
this commit adds test coverage to prevent future regressions:

- docker_pin_test.go: new table-driven case verifying all four 0.27.22
  firewall images resolve via the embedded fallback when the local cache
  is absent.
- docker_firewall_pin_compile_test.go: new end-to-end compile test
  (TestCompileWorkflow_FirewallImagesPinnedForDefaultVersion) that
  exercises the default AWF version with tools.github.mode=gh-proxy,
  asserting that agent, api-proxy, cli-proxy, and squid all appear with
  full digest pinning in both the manifest containers block and the
  --image-tag AWF config value.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix regression in gh-aw-firewall image digest pinning Add regression tests for gh-aw-firewall digest pinning at default version (0.27.22) Jul 4, 2026
Copilot AI requested a review from pelikhan July 4, 2026 08:15
Comment thread pkg/workflow/docker_pin_test.go Outdated
// pinned references.
name: "embedded firewall pins for default version (0.27.22) — all four images",
images: []string{
"ghcr.io/github/gh-aw-firewall/agent:0.27.22",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@copilot use constant to make test resilient to version change Md

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. Replaced the hardcoded version/digest table-case with a standalone TestApplyContainerPins_DefaultFirewallVersion that builds image names from constants.DefaultFirewallRegistry/constants.DefaultFirewallVersion and derives expected values from getEmbeddedContainerPin at runtime — so the test automatically tracks future version bumps. Committed in the latest push.

Replace hardcoded "0.27.22" version string and digest literals with a
standalone test (TestApplyContainerPins_DefaultFirewallVersion) that
drives image names from constants.DefaultFirewallVersion and
constants.DefaultFirewallRegistry, and derives expected values from
getEmbeddedContainerPin at runtime. The test now automatically tracks
future version bumps without needing manual digest updates.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI requested a review from pelikhan July 4, 2026 08:36
@pelikhan pelikhan marked this pull request as ready for review July 4, 2026 08:45
Copilot AI review requested due to automatic review settings July 4, 2026 08:45
@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

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

@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

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

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

Adds regression coverage to ensure gh-aw-firewall sidecar images are always emitted as digest-pinned references (via embedded pins) at the current default firewall version, including the newer cli-proxy image.

Changes:

  • Adds a unit-level regression test to verify embedded pin presence for all four firewall sidecars at constants.DefaultFirewallVersion.
  • Adds an end-to-end compile regression test to ensure the generated lock file includes full image/digest/pinned_image metadata and --image-tag digest embedding for all four sidecars.
Show a summary per file
File Description
pkg/workflow/docker_pin_test.go Adds a regression test that exercises embedded container pin lookup and pin application for all firewall sidecars at the default version.
pkg/workflow/docker_firewall_pin_compile_test.go Adds an end-to-end compile regression test asserting default-version firewall images (including cli-proxy) are digest-pinned in consumer lock files.

Review details

Tip

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

  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Low

Comment on lines +123 to +129
require.NotEmpty(t, pin.Digest, "Digest must be non-empty for %s", image)
require.NotEmpty(t, pin.PinnedImage, "PinnedImage must be non-empty for %s", image)

refs, pinEntries := applyContainerPins([]string{image}, nil)
require.Len(t, refs, 1)
assert.Equal(t, pin.PinnedImage, refs[0], "resolved ref for %s", image)
assert.Equal(t, pin.Digest, pinEntries[0].Digest, "digest in manifest entry for %s", image)
@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate - ADR Required

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

Draft ADR committed: docs/adr/43343-regression-tests-for-firewall-digest-pinning.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-43343: Two-Layer Regression Tests for gh-aw-firewall Digest Pinning

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., 43343-regression-tests-for-firewall-digest-pinning.md for PR #43343).

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · 48.6 AIC · ⌖ 12.3 AIC · ⊞ 8.4K ·
Comment /review to run again

@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 100/100 — Excellent

Analyzed 2 test(s): 2 design, 0 implementation, 0 violation(s).

📊 Metrics (2 tests)
Metric Value
Analyzed 2 (Go: 2, JS: 0)
✅ Design 2 (100%)
⚠️ Implementation 0 (0%)
Edge/error coverage 2 (100%)
Duplicate clusters 0
Inflation NO
🚨 Violations 0
Test File Classification Issues
TestCompileWorkflow_FirewallImagesPinnedForDefaultVersion docker_firewall_pin_compile_test.go:90 design_test / behavioral_contract / high_value None
TestApplyContainerPins_DefaultFirewallVersion docker_pin_test.go:114 design_test / behavioral_contract / high_value None

Verdict

Passed. 0% implementation tests (threshold: 30%). No violations detected.

Notes:

  • Both tests are explicit regression tests for gh-aw#43307, verifying that all four gh-aw-firewall sidecar images (agent, api-proxy, cli-proxy, squid) receive digest pins at constants.DefaultFirewallVersion even without a local action cache.
  • TestApplyContainerPins_DefaultFirewallVersion uses table-driven subtests (t.Run) across all 4 sidecars, providing granular regression coverage per image.
  • TestCompileWorkflow_FirewallImagesPinnedForDefaultVersion is an end-to-end compile regression test: it compiles a real workflow (with gh-proxy mode to include cli-proxy) and checks that the lock file contains pinned references, manifest JSON metadata, and AWF config entries for every image.
  • Both tests use constants.DefaultFirewallVersion so they automatically track future version bumps — a strong design choice.
  • All require/assert calls include descriptive failure context. No forbidden mocks. Build tags present on line 1 of both files. ✅

References: §28700930007

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

🧪 Test quality analysis by Test Quality Sentinel · 43.3 AIC · ⌖ 11.2 AIC · ⊞ 6.8K ·
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: 100/100. 0% implementation tests (threshold: 30%). No violations detected.

@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: Add regression tests for gh-aw-firewall digest pinning at default version (0.27.22)

Good addition — the gap described in the PR body was real and the two-layer test strategy (unit + compile-level) is the right approach.

docker_pin_test.goTestApplyContainerPins_DefaultFirewallVersion is clean. It calls getEmbeddedContainerPin dynamically, so it will auto-fail on version bumps without any maintenance.

docker_firewall_pin_compile_test.go — One non-blocking but meaningful improvement opportunity: the compile-level test hard-codes the four sha256: digests while dynamically deriving imageTag from constants.DefaultFirewallVersion. This creates a double-update requirement on every version bump — update action_pins.json and the four literal digests in this test. A lookup from getEmbeddedContainerPin (matching the unit test's pattern) would make it truly self-maintaining. See the inline comment for a concrete suggestion.

No correctness, security, or reliability issues were found. The embedded pin data in action_pins.json confirms all four 0.27.22 entries exist with the correct digests.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 64 AIC · ⌖ 6.22 AIC · ⊞ 4.9K

yamlStr := string(yaml)

expectedPins := map[string]string{
"ghcr.io/github/gh-aw-firewall/agent:" + imageTag: "sha256:55f06588411008b7148eb64b8dfe28602a0cce3675b36c6b190b54aca138468e",

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.

Hardcoded digests vs. dynamic version creates a maintenance trap. The test dynamically derives imageTag from constants.DefaultFirewallVersion, but the four sha256: digests in expectedPins are hardcoded. When DefaultFirewallVersion is bumped, both action_pins.json and these four constants must be updated — exactly the kind of silent omission the PR description says we want to prevent.

Consider deriving the expected digests from the embedded pin table (the same way TestApplyContainerPins_DefaultFirewallVersion does) so the compile-level test stays in sync automatically:

expectedPins := make(map[string]string)
for _, sidecar := range []string{"agent", "api-proxy", "cli-proxy", "squid"} {
    image := constants.DefaultFirewallRegistry + "/" + sidecar + ":" + imageTag
    pin, ok := getEmbeddedContainerPin(image)
    require.True(t, ok, "embedded pin must exist for %s", image)
    expectedPins[image] = pin.Digest
}

This keeps a single source of truth in action_pins.json and makes the test truly auto-tracking.

@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.

Skills-Based Review 🧠

Applied /tdd — requesting changes on test-correctness and fragility issues in the new regression tests.

📋 Key Themes & Highlights

Key Themes

  1. Digest duplication (docker_firewall_pin_compile_test.go): The E2E test hardcodes all four SHA256 digests as string literals. The unit test (docker_pin_test.go) correctly avoids this by calling getEmbeddedContainerPin via constants — the E2E test should do the same, or the two sets of digests will diverge silently on a version bump.

  2. Missing negative assertion: Neither test asserts the absence of tag-only (unpinned) references, which is the exact failure mode of gh-aw#43307. A compile that emits both a pinned and an unpinned line would still pass.

  3. Fragile substring probe (imageTag + ","): Matches incidental output formatting rather than a structured contract. The per-sidecar sidecar=sha256:... checks are the real guard and should stand alone.

  4. Assertion clarity: Three overlapping strings.Contains calls per image are testing the same underlying fact three different ways; a small shared helper would make the intent explicit and reduce noise on failure.

Positive Highlights

  • ✅ Excellent coverage choice: adds cli-proxy (the missing image from v0.82) alongside the three legacy sidecars
  • ✅ Unit test TestApplyContainerPins_DefaultFirewallVersion correctly derives everything from constants — no duplication
  • ✅ Clear doc comments that tie each test back to the bug number
  • ✅ Using tools.github.mode: gh-proxy in the E2E frontmatter is a good design decision to force cli-proxy into the compile path
  • ✅ Table-driven sub-tests with t.Run(sidecar, ...) in the unit test give clean, named failure output

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 81.4 AIC · ⌖ 7.76 AIC · ⊞ 6.6K
Comment /matt to run again

yamlStr := string(yaml)

expectedPins := map[string]string{
"ghcr.io/github/gh-aw-firewall/agent:" + imageTag: "sha256:55f06588411008b7148eb64b8dfe28602a0cce3675b36c6b190b54aca138468e",

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] Hardcoded SHA256 digests will silently diverge when DefaultFirewallVersion is bumped — the test docs say it "will fail immediately", but that is only true for the unit test in docker_pin_test.go (which calls getEmbeddedContainerPin via constants). The E2E test here hardcodes the digests as string literals, so a version bump that updates action_pins.json but leaves these strings stale will still compile the workflow correctly while making this test pass with incorrect expectations.

💡 Suggested fix

Derive the expected digests from the same embedded table that production code uses, rather than duplicating them as literals:

for _, sidecar := range []string{"agent", "api-proxy", "cli-proxy", "squid"} {
    image := constants.DefaultFirewallRegistry + "/" + sidecar + ":" + imageTag
    pin, ok := getEmbeddedContainerPin(image)
    require.True(t, ok, "no embedded pin for %s", image)
    expectedPins[image] = pin.Digest
}

This makes the E2E test self-consistent with docker_pin_test.go and eliminates duplicate maintenance.

@copilot please address this.

mode: gh-proxy
---

# Test

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] The AWF-config string probe imageTag + "," is fragile — it matches a bare image tag followed by a comma in YAML, but this exact substring could be present for reasons unrelated to pinning. A more precise assertion would check the structured key that embeds the version, e.g. asserting the full --image-tag AWF config value contains each sidecar=sha256:... pair.

💡 Why this matters

The check imageTag + "," relies on incidental output formatting. If the serialisation changes (e.g. JSON key ordering, trailing comma removal), this probe will silently stop verifying what it intends to. The existing per-sidecar sidecar=sha256:... assertions on lines 157–160 are correctly precise and serve as the real guard — consider removing the redundant bare-imageTag check, or replace it with a structured assertion on the --image-tag config blob.

@copilot please address this.

on: workflow_dispatch
engine: claude
network:
allowed:

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] Three overlapping strings.Contains checks for each image (manifest header JSON, comment block, and download reference) test the same data three different ways. If the format of any one of these changes legitimately, all three probes break at once — obscuring which assertion is the actual contract.

💡 Suggestion

Consider consolidating into a helper that captures the intent explicitly:

func assertImagePinned(t *testing.T, yamlStr, image, digest string) {
    t.Helper()
    pinnedImage := image + "@" + digest
    assert.Contains(t, yamlStr, `"pinned_image":"`+pinnedImage+`"`, "manifest header for "+image)
    assert.Contains(t, yamlStr, pinnedImage, "download ref for "+image)
}

The separate comment-block probe can be kept if you want to assert a specific output section, but give it its own clearly-named assertion.

@copilot please address this.

require.NotEmpty(t, pin.Digest, "Digest must be non-empty for %s", image)
require.NotEmpty(t, pin.PinnedImage, "PinnedImage must be non-empty for %s", image)

refs, pinEntries := applyContainerPins([]string{image}, nil)

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] applyContainerPins is called with a single-element slice and then refs[0] is accessed directly. If applyContainerPins ever returns an empty slice for a failed lookup, this would index-panic rather than produce a useful test failure. The require.Len(t, refs, 1) on the previous line guards against this, which is correct — but the comment above the call says the pin "must exist" without explaining what would cause it not to exist, making the intent slightly unclear to future readers.

💡 Minor suggestion

Add a brief inline comment noting the invariant being checked:

// applyContainerPins should resolve the embedded pin, so exactly one ref is expected.
refs, pinEntries := applyContainerPins([]string{image}, nil)
require.Len(t, refs, 1, "expected pinned ref from embedded table")

This mirrors the intent expressed in the unit test for the table case "embedded pin used when cache is absent".

@copilot please address this.

}

// TestCompileWorkflow_FirewallImagesPinnedForDefaultVersion is a regression test for
// gh-aw#43307: the four gh-aw-firewall images at the current default version

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] The test does not verify the absence of tag-only (unpinned) references — which is the precise failure mode described in gh-aw#43307. A compile that writes ghcr.io/github/gh-aw-firewall/agent:0.27.22 without a digest would still pass all current assertions as long as the pinned form also appears elsewhere in the YAML.

💡 Suggested addition

Add a negative assertion after verifying pinned images:

for image := range expectedPins {
    // A tag-only reference must NOT appear; every occurrence must be digest-pinned.
    assert.NotContains(t, yamlStr, `image: `+image+`\n`,
        "tag-only (unpinned) reference must not appear for %s", image)
}

This directly encodes the regression condition from the bug.

@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.

REQUEST_CHANGES — The tests add useful coverage but have two issues that need fixing before merge, plus two secondary concerns.

### Blocking issues
  1. Hardcoded digest literals (docker_firewall_pin_compile_test.go:130–133) — The compile test hardcodes four SHA256 digests but the unit test in the same PR correctly reads them from getEmbeddedContainerPin at runtime. The compile test's digests will go stale silently on the next DefaultFirewallVersion bump and fail with a misleading error. Apply the same pattern as the unit test.

  2. agent-act unpinned and undetected (docker_pin_test.go:116) — buildAWFImageTagWithDigests queries agent-act for digest inclusion but action_pins.json has no entry for 0.27.22. The silently skipped digest means AWF pulls agent-act by tag, not digest — the same class of regression this PR is supposed to prevent. Neither test covers this; one test comment incorrectly claims to cover "all four images" when agent-act is a fifth.

### Non-blocking issues
  1. "imageTag" literal check is vacuous (docker_firewall_pin_compile_test.go:150) — checks that the JSON field name appears in the lock file, not that the version value is correct. Replace with "imageTag":" + imageTag.

  2. require.Len(t, refs, 1) asserts nothing (docker_pin_test.go:127) — applyContainerPins always returns same-length slices; this assertion can never fail.

🔎 Code quality review by PR Code Quality Reviewer · 185.4 AIC · ⌖ 7.76 AIC · ⊞ 5.4K
Comment /review to run again

yamlStr := string(yaml)

expectedPins := map[string]string{
"ghcr.io/github/gh-aw-firewall/agent:" + imageTag: "sha256:55f06588411008b7148eb64b8dfe28602a0cce3675b36c6b190b54aca138468e",

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.

Hardcoded digest literals will silently break when DefaultFirewallVersion is bumped — the test claim of automatic version tracking is only half true.

💡 Details and suggested fix

The map keys correctly derive imageTag from constants.DefaultFirewallVersion, but the digest values are raw literals:

"ghcr.io/github/gh-aw-firewall/agent:" + imageTag: "sha256:55f06588...",

When DefaultFirewallVersion is bumped, these four digests will no longer match and the test will fail with:

Expected manifest header to include pinned metadata for ghcr.io/github/gh-aw-firewall/agent:0.28.0

...with zero indication that the hardcoded digests are stale. The companion unit test in docker_pin_test.go avoids this correctly — it calls getEmbeddedContainerPin at runtime. Apply the same pattern here:

for _, sidecar := range []string{"agent", "api-proxy", "cli-proxy", "squid"} {
    image := constants.DefaultFirewallRegistry + "/" + sidecar + ":" + imageTag
    pin, ok := getEmbeddedContainerPin(image)
    require.True(t, ok, "no embedded pin for %s", image)
    expectedPins[image] = pin.Digest
}

This makes the compile test consistent with the unit test and eliminates the maintenance trap entirely.

}

for _, imageTagPart := range []string{
`imageTag`,

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.

The "imageTag" literal check verifies the JSON field name exists, not that the version value is correct — this assertion is vacuous.

💡 Details and suggested fix
for _, imageTagPart := range []string{
    `imageTag`,       // ← matches the JSON key name, always present
    imageTag + `,`,  // ← this is the one that actually checks the version
    ...
}

The backtick-quoted imageTag string is a literal JSON key name emitted by any AWF config section. It passes as long as any AWF container config block exists, even if the version value is completely wrong. It provides zero regression protection for the pinning bug described in the PR.

Replace it with a check that validates both key and value:

`"imageTag":"` + imageTag,

This ensures the lock file actually encodes the expected version in the AWF config, not just that an AWF config section exists at all.

require.NotEmpty(t, pin.PinnedImage, "PinnedImage must be non-empty for %s", image)

refs, pinEntries := applyContainerPins([]string{image}, nil)
require.Len(t, refs, 1)

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.

require.Len(t, refs, 1) can never fail — it asserts nothing about pinning correctness.

💡 Details and suggested fix

applyContainerPins always allocates and returns slices of exactly len(images). Passing a single-element slice guarantees refs has length 1 unconditionally, whether the image was pinned or not. The assertion provides zero regression protection.

Replace it with a check that distinguishes a pinned result from an unchanged one:

require.Len(t, refs, 1)
assert.Contains(t, refs[0], "`@sha256`:", "ref for %s must include digest", image)

Or assert the full pinned image directly, which is already done on the next line — in that case, just delete the require.Len call entirely since it adds no value.

// Using constants means the test automatically tracks version bumps.
func TestApplyContainerPins_DefaultFirewallVersion(t *testing.T) {
imageTag := strings.TrimPrefix(string(constants.DefaultFirewallVersion), "v")
sidecars := []string{"agent", "api-proxy", "cli-proxy", "squid"}

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.

agent-act is absent from the sidecar list, leaving a gap that buildAWFImageTagWithDigests will silently swallow.

💡 Details and suggested fix

buildAWFImageTagWithDigests in awf_helpers.go:806 includes agent-act when building the AWF --image-tag config value:

{name: "agent-act", image: constants.DefaultFirewallRegistry + "/agent-act:" + imageTag},

However, agent-act:0.27.22 has no entry in action_pins.json (the latest agent-act entry is 0.27.13). At runtime buildAWFImageTagWithDigests silently skips images with no pin, so the AWF config imageTag value is emitted without agent-act=sha256:..., meaning AWF must pull agent-act at the tag rather than by digest.

The new test's sidecar list — ["agent", "api-proxy", "cli-proxy", "squid"] — does not include agent-act, so this gap is invisible. The test comment claims to cover "all four gh-aw-firewall images" but agent-act is a fifth image used in production.

Two options:

  1. Intentional omission: If agent-act is no longer used at the default version, remove it from the specs list in buildAWFImageTagWithDigests so there's no silent skip.
  2. Gap to fix: Add agent-act:0.27.22 to action_pins.json and add "agent-act" to the sidecar list in both tests with an assertion that its digest appears in the AWF config imageTag.

Either way, update the test comment to accurately describe what images are (and are not) covered.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill and address the blocking review feedback before rerunning checks. Open items:

  • replace hardcoded firewall digests in docker_firewall_pin_compile_test.go with values derived from embedded pins
  • cover the unpinned/tag-only regression mode explicitly, including the agent-act gap called out by review
  • update the PR body to link the ADR requested by Design Decision Gate

Generated by 👨‍🍳 PR Sous Chef · 8.28 AIC · ⌖ 5.03 AIC · ⊞ 4.3K ·
Comment /souschef to run again

@pelikhan pelikhan merged commit 263040b into main Jul 4, 2026
29 of 30 checks passed
@pelikhan pelikhan deleted the copilot/gh-aw-firewall-fix-digest-pinning branch July 4, 2026 09:35
Copilot stopped work on behalf of gh-aw-bot due to an error July 4, 2026 09:35
Copilot AI requested a review from gh-aw-bot July 4, 2026 09:35
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.

gh-aw-firewall images emitted tag-only in v0.82.2 lock files (regression of #38561)

4 participants