feat: ErrImageNotInRegistry sentinel + wfctl render-side actionable hint#579
Merged
Conversation
For IaC drivers that pre-flight image presence in the registry before mutating an AppSpec (e.g. DOCR, ECR). The exact message string is load-bearing: wfctl render-side actionable hint matches it verbatim as a fallback for the gRPC plugin boundary, where structpb does not preserve sentinel identity. Tests assert the message string is stable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…egistry When wfctl infra apply/plan surfaces an error wrapping interfaces.ErrImageNotInRegistry — either via errors.Is (in-process driver) or via verbatim message-string match (gRPC plugin boundary, where structpb does not preserve sentinel identity) — emit a single actionable hint to stderr suggesting a re-run of the build/deploy workflow or an --image-sha override. Hooks: - cmd/wfctl/infra_apply.go: aggregated error from result.Errors[] (both v1 + v2 apply paths) - cmd/wfctl/infra.go: compute-plan error wrap site 4 unit tests cover typed-error path, gRPC string-match fallback, unrelated-error no-op, and nil-input no-op. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds an IaC-layer sentinel error (interfaces.ErrImageNotInRegistry) to represent “referenced image digest/tag is missing from the registry”, and wires wfctl infra plan/apply to emit a GitHub Actions–style actionable stderr hint when that condition is detected (including a string-match fallback for gRPC plugin boundaries).
Changes:
- Introduces
interfaces.ErrImageNotInRegistrywith documentation describing typed (errors.Is) and cross-process string matching expectations. - Adds unit tests in
interfaces/to enforce sentinel wrapping semantics and to pin the exact error message string. - Adds
cmd/wfctlhint emission helper + tests, and calls it from infra plan/apply failure paths.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| interfaces/iac_resource_driver.go | Adds the ErrImageNotInRegistry sentinel and documents cross-process matching requirements. |
| interfaces/iac_resource_driver_test.go | Extends sentinel tests and pins the error message string for gRPC string-match fallback. |
| cmd/wfctl/infra.go | Emits an actionable hint on infra plan failures when image-missing sentinel is detected. |
| cmd/wfctl/infra_image_presence_hint.go | Implements hint detection/emission (typed sentinel or fallback string match). |
| cmd/wfctl/infra_image_presence_hint_test.go | Tests typed detection, gRPC string-fallback detection, and no-op behavior. |
| cmd/wfctl/infra_apply.go | Emits an actionable hint when apply completes with per-resource errors (aggregated). |
⏱ Benchmark Results✅ No significant performance regressions detected. benchstat comparison (baseline → PR)
|
4 findings from copilot-pull-request-reviewer: 1. Hint text referenced a non-existent --image-sha flag. Replaced with accurate "Re-run the build/deploy workflow ... then re-dispatch this operation" — operator-driven recovery via the workflow that pushes images, plus whatever re-dispatch path the caller supports (e.g. tc2-cutover.yml's workflow_dispatch.inputs.image_sha). 2. errImageNotInRegistryMessage const duplicated the sentinel's message string (drift risk). Replaced with direct interfaces.ErrImageNotInRegistry.Error() call. The interfaces-side stability test remains the guardrail. 3 + 4. emitImageNotInRegistryHint was only emitted from the aggregated result.Errors[] path. provider.Apply can surface a top-level err (especially the gRPC plugin path), which bypassed the hint. Added hint emission immediately before each "return fmt.Errorf(\"apply: %w\", err)" site (both v1 and v2 dispatch paths in infra_apply.go). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
interfaces.ErrImageNotInRegistrysentinel used by IaC drivers when anAppSpec image SHA is absent from the registry (e.g. DOCR GC pruned it).
is the sentinel (or its message string survives a gRPC plugin boundary),
wfctl emits
::error::image not found in registry — Re-run the build/deploy workflow ...to stderr.This is Wave 1 — PR 1 of the DOCR retention + image-presence design. Wave 2 (declarative retention reconciler in DO plugin) is deferred per ADR 0003 in core-dump.
The exact sentinel message string
"iac: image tag or digest not found in registry"is load-bearing: the wfctl render layer matches it verbatim as the gRPC-boundary fallback (since structpb doesn't preserve sentinel identity). A unit test asserts the string is stable.Test plan
go test ./interfaces/ -run \"TestSentinels|TestErrImageNotInRegistry\"— sentinel round-trip + message-string stabilitygo test ./cmd/wfctl/ -run TestImageNotInRegistryHint— typed-error path, gRPC string-match fallback, unrelated-error no-op, nil-input no-opgo test ./...— full repo, no regressionsgo vet ./...cleangofmt -l .cleanSequenced PRs
Rollback
Ship v0.24.1 that no-ops
emitImageNotInRegistryHint. Do NOT retag v0.24.0 (Go module proxy is immutable; consumers' go.sum locks).