Documentation
¶
Overview ¶
Package checks implements the structural, misleading-sha, and resolver-bound validators run against parsed workflows.
Index ¶
- Constants
- func AddContainerImage(wr *WorkflowReport, img uses.Image)
- func ImageHeadline(img uses.Image) string
- func NewPrewarmedResolver(r *resolve.Resolver, live []dep.Dependency) *prewarmedResolver
- func NotInHistoryDetail(locked, nwo, ref, live string) string
- func PruneKey(nwo, ref, sha string) string
- func PrunesDependency(f Finding, acceptMoved, relock bool) bool
- func RetryAdvice(at time.Time, rateLimited, anonymous bool) string
- func TagCommitMismatchRemediation(holder string) string
- func UnreachableRemediation(holder, nwo string) string
- func VerifiedKey(key, sha string) string
- type Category
- type CheckResolver
- type Confidence
- type Deferred
- type Finding
- type InventoryEntry
- type ParsedWorkflow
- type PruneEffect
- type Report
- type Severity
- type WorkflowReport
Constants ¶
const AcceptMovedRemediation = "verify it, then run `gh actions-lock --accept-moved`"
AcceptMovedRemediation is the single next step for a pin the runner can't trust: --relock refuses these, so the override must be explicit.
Variables ¶
This section is empty.
Functions ¶
func AddContainerImage ¶ added in v0.3.0
func AddContainerImage(wr *WorkflowReport, img uses.Image)
AddContainerImage records img on wr's container-image finding for its image, adding the finding on first use, so each image is reported once per workflow with every place it's used.
func ImageHeadline ¶ added in v0.3.0
ImageHeadline says what is unpinned about img. Uses with the same headline share one finding.
func NewPrewarmedResolver ¶
func NewPrewarmedResolver(r *resolve.Resolver, live []dep.Dependency) *prewarmedResolver
NewPrewarmedResolver primes the adapter with the live resolution of refs. Pass live==nil when ResolveAllRecursive failed; checks that need a ref will fail open.
func NotInHistoryDetail ¶ added in v0.2.0
NotInHistoryDetail describes a locked commit missing from upstream history. It can't tell a force-push from a forged lockfile, so it names both. live, when known, is where ref points now.
func PruneKey ¶ added in v0.3.0
PruneKey is the identity planning prunes a recorded entry by: the lockfile's canonical pin key, which folds owner/repo but keeps a ref's case, since refs are case-sensitive, and the commit, folded. A full-SHA ref names a commit, so it folds too.
func PrunesDependency ¶ added in v0.3.0
PrunesDependency reports whether planning drops f's dependency from the recorded inventory rather than writing the entry back verbatim.
func RetryAdvice ¶ added in v0.3.0
RetryAdvice is the next step for a check an outage or rate limit stopped.
func TagCommitMismatchRemediation ¶ added in v0.3.0
TagCommitMismatchRemediation is the next step for an exact-tag entry locked behind its tag. holder (NWO@ref) is the composite that pins it, or "" for the workflow's own use. --relock rebuilds a composite's closure from its manifest, so it repairs a held entry too; only the workflow's own use can instead move to another release.
func UnreachableRemediation ¶ added in v0.3.0
UnreachableRemediation is the next step for a pin whose recorded ref no longer vouches for its commit. --accept-moved repairs only an entry the workflow alone holds; one a composite holds keeps the composite's recorded entry, so only changing that composite (holder, as NWO@ref) helps.
func VerifiedKey ¶ added in v0.3.0
VerifiedKey identifies a commit this run checked for an on-disk key.
Types ¶
type Category ¶
type Category string
Category classifies the state of a workflow or action dependency. The string values are part of the schema surfaced to consumers (JSON output, doc URL slugs); the frozen-strings test guards against accidental renames.
const ( // NotPinned means the workflow has action refs but no // corresponding dependencies entry in the lockfile. NotPinned Category = "not-pinned" // MissingRef means a `uses:` ref (tag, branch, or commit) does not exist // upstream, so there is nothing to pin. Only a source change fixes it. MissingRef Category = "ref-missing" // InvalidWorkflow means a workflow file failed to load, so none of its // actions could be checked. InvalidWorkflow Category = "invalid-workflow" // ShaAsRef means a dependency is pinned to a bare SHA with no // human-readable tag ref alongside it. ShaAsRef Category = "sha-as-ref" // RefChanged means the workflow uses: ref was manually changed // (e.g. v6.2.0 → v6) and the lockfile no longer matches. RefChanged Category = "ref-changed" // RefMoved means the upstream tag now resolves to a different // SHA than what the lockfile has recorded. RefMoved Category = "ref-moved" // TagCommitMismatch means a lock entry keyed by an exact release tag // (v4.2.1) locks an older commit in the tag's history rather than the // commit the tag points at. An exact tag names one commit, so the // publisher re-pointed it, or the lockfile was edited to roll it back. // Floating refs (v4, main) are sticky and report ref-moved instead. TagCommitMismatch Category = "tag-commit-mismatch" // Stale means the lockfile pins a dependency for a workflow that no // uses: in that workflow references. Stale Category = "stale" // MisleadingSHA means a ref looks like a SHA but resolves to a // different commit. MisleadingSHA Category = "misleading-sha" // UnreachablePin means the pinned SHA is not an ancestor of, and // not reachable from, the ref's current head. The pin points at a // commit that was never in the ref's history or was dropped by an // upstream history rewrite (squash/force-push) — or the lockfile // entry was tampered with. The check can't distinguish those, so it // fails closed without asserting an attack. UnreachablePin Category = "unreachable-pin" // RepoRenamed means a locked dependency's repository was renamed or // transferred but kept its repo ID. The runner follows the redirect; // fix mode rewrites `uses:` to the new name and keeps the locked commit. // Inside a remote composite the pin keeps the name the composite uses. RepoRenamed Category = "repo-renamed" // RepoReplaced means a locked dependency's name resolves to a different // repo ID. The runner rejects the pin, and re-locking under the old name // cannot fix it. RepoReplaced Category = "repo-replaced" // That covers both deleted repositories and ones the token can't see. RepoUnavailable Category = "repo-unavailable" // Valid means the dependency is pinned and verified. Valid Category = "valid" // RunOnly means the workflow has no action refs (only run: // steps), so pinning is not applicable. RunOnly Category = "run-only" // AncestryUnknown means the Compare API didn't answer whether the // pinned SHA is in the ref's history (an outage or rate limit). We // know the SHAs differ but can't classify the move as benign-but-known // (ref-moved) vs. tampered (unreachable-pin), so it blocks as a // retryable error. AncestryUnknown Category = "ancestry-unknown" // ReachabilityUnknown means GitHub didn't answer whether a pin is // still valid (an outage or rate limit). It blocks as a retryable // error rather than treating the dep as verified. ReachabilityUnknown Category = "reachability-unknown" // OnboardingRequired means a `check --no-onboard` run encountered a // workflow (or an action within one) that has no existing entry in the // lockfile. Under --no-onboard the tool refuses to add new entries: the // workflow/action is skipped and surfaced rather than silently pinned. // Already-tracked entries are still re-pinned. The operator must onboard // explicitly (run `gh actions-lock` without --no-onboard) to add it. OnboardingRequired Category = "onboarding-required" // VersionRef is an informational nudge: a dependency is pinned with a // ref that is not a full semver tag (e.g. v4, v3.1, main). Full semver // tags (v4.2.1) each resolve to exactly one commit, making the lock // comment durable across re-pins. VersionRef Category = "version-ref" // LocalAction is a non-blocking warning for a local `./…` action. It // names the runner workspace, so it is never followed or locked, and its // own dependencies are not covered by the lockfile. LocalAction Category = "local-action" // StaleWorkflow is an informational, repo-level notice: the lockfile // records a workflow entry whose file no longer exists on disk. On a // default (full-directory) fix run these are pruned automatically; in // read-only mode they are surfaced but not removed. Non-blocking. StaleWorkflow Category = "stale-workflow" // SelfRepositoryAction means the workflow references a same-repo action via // the self repository `$/…` syntax. These resolve against the defining // repo at the running ref, so they are inherently pinned and need no // lockfile entry — always in compliance. SelfRepositoryAction Category = "self-repository-action" // InvalidSelfRepositoryRef means a `$/…` reference is malformed or its // target cannot be inspected. This includes a forbidden `@ref` suffix. InvalidSelfRepositoryRef Category = "invalid-self-repository-ref" // FreshTag is an informational, non-blocking security nudge: an action was // pinned to a tag younger than its configured cooldown, or than 3 days when // none is configured. Cooldown does not filter what gets pinned, so this // is how operators learn they locked a very recent release. FreshTag Category = "fresh-tag" // CooldownConfigIgnored is an informational, non-blocking notice that the // repository's Dependabot cooldown block sets keys this tool does not yet // honor (e.g. semver-*-days or include/exclude). Surfaced so the ignored // configuration is never silent. CooldownConfigIgnored Category = "cooldown-config-ignored" // IncompleteLockfile means a locked action's manifest at its locked // commit doesn't match its lockfile entry: it uses an action the entry // doesn't record, or it is missing. A plain run doesn't repair it. IncompleteLockfile Category = "incomplete-lockfile" // ContainerImage is a non-blocking warning: a workflow runs a container // image by tag, which the lockfile can't pin. It never changes validity. ContainerImage Category = "container-image" )
func (Category) DocURL ¶ added in v0.3.0
DocURL returns the troubleshooting docs section for c, or "" when c has none.
func (Category) IsInconclusive ¶
IsInconclusive reports whether c represents a diagnostic that couldn't reach a verdict (network/rate-limit fallback). Consumers (e.g. Dependabot FindingMapper) treat them as "scan inconclusive, retry" rather than "lockfile is bad"; Finding.Retryable says the same in JSON.
type CheckResolver ¶
type CheckResolver interface {
// ResolveRef returns the live SHA for owner/repo@ref. ok=false means
// the resolver could not answer (network failure, unknown ref); checks
// fail open on that.
ResolveRef(owner, repo, ref string) (sha string, ok bool)
// PeelTagObject reports whether a hex SHA names an annotated tag
// object (or chain of tag-of-tag) and, if so, returns the commit OID
// it ultimately points at.
PeelTagObject(ctx context.Context, owner, repo, sha string) (commit string, ok bool)
// CheckAncestry asks whether candidate is an ancestor of head and
// returns a short human-readable detail alongside the status — the
// rate-limit or compare-base detail callers surface to operators — and
// the API error behind a result GitHub didn't answer (see
// resolve.Resolver.CheckAncestryCause).
CheckAncestry(ctx context.Context, owner, repo, candidate, head string) (resolve.AncestryStatus, string, error)
}
CheckResolver is the surface the resolver-bound checks need. The production implementation is *prewarmedResolver; tests use stubs.
type Confidence ¶
type Confidence string
Confidence is how certain the producer is the finding is real, modeled on zizmor's audit output.
const ( // ConfidenceLow marks a signal that could not be fully verified // (resolver failure, reachability inconclusive). ConfidenceLow Confidence = "low" // ConfidenceMedium marks a signal inferred from a fallback // (tag-object peel, ancestry unknown due to rate limit). ConfidenceMedium Confidence = "medium" // ConfidenceHigh marks a signal resting on authoritative data // (exact SHA comparison, upstream reachability answer). ConfidenceHigh Confidence = "high" )
type Deferred ¶ added in v0.3.0
type Deferred struct {
// Key is the entry's on-disk key (canonical NWO@ref).
Key string
// Ref and Commit are what the entry records.
Ref string
Commit string
// Finding is the finding as it blocks when the entry stays as recorded.
Finding Finding
// Restored marks a finding put back on its workflow.
Restored bool
}
Deferred is a finding on a recorded lock entry that parity excused or accepted because the run should replace or remove the entry. It holds only if the write the run plans does that; otherwise Finding, as it would block with the entry left as recorded, stands.
type Finding ¶
type Finding struct {
// WorkflowPath is the path to the workflow file.
WorkflowPath string
// Category of the finding.
Category Category
// Severity of the finding.
Severity Severity
// Confidence of the finding — see the Confidence type docs. Always
// populated at construction; an empty value is a bug and the
// no-empty-confidence test will catch it.
Confidence Confidence
// ActionRef is the action reference this finding relates to (nil for workflow-level findings).
ActionRef *parserlock.ActionRef
// Remediable is true for workflow-level findings that pinning can repair.
Remediable bool
// Dependency is the existing pinned dep if any.
Dependency *dep.Dependency
// ParentNWO is the dep key of the direct action that pulls in this transitive dep (empty if direct).
ParentNWO string
// LocalPath is the `./…` value a LocalAction warning names.
LocalPath string
// LocalRemoteUses are the remote actions the LocalPath composite uses in
// the checkout. The runner needs a pin for each, which the lockfile
// can't record, so the CLI blocks on them.
LocalRemoteUses []string
// Detail is a human-readable explanation.
Detail string
// Remediation describes what the check command can do about it.
Remediation string
// ObservedSHA is the SHA the resolver got at scan time, recorded when
// it differs from the pinned SHA (e.g. ref-moved, misleading-sha,
// unreachable-pin).
ObservedSHA string
// RecommendedTag is the most recent stable tag whose commit is
// reachable from a branch, populated for unreachable-SHA findings
// when one can be found. Empty otherwise.
RecommendedTag string
// RecommendedSHA is the commit SHA the recommended tag points to.
RecommendedSHA string
// RecommendedSearched is true when the release walk ran for this
// finding (regardless of outcome). Lets renderers distinguish
// "we didn't look" from "we looked and found nothing."
RecommendedSearched bool
// RefMissing marks a ref-missing or unreachable-pin finding whose ref
// definitively does not exist upstream. Terminal output titles it
// separately and skips the release and doc links.
RefMissing bool
// RefGone marks an unreachable-pin finding whose checked ref is not a
// tag or branch, including a full-SHA key's recorded ref, which
// RefMissing leaves unset. It only picks the terminal title.
RefGone bool
// RefRefused marks an unreachable-pin finding whose ref name the Actions
// backend refuses by policy, so it was never looked up. It only picks
// the terminal title.
RefRefused bool
// TagMoved marks a ref-moved warning from parity: the exact-version tag
// a full-SHA key records moved ahead of its locked commit. Unlike a
// sticky mutable ref, the terminal shows it; the JSON category is
// unchanged.
TagMoved bool
// RecordedRef is the lock entry ref a parity finding on a recorded pin
// checked; empty for a ref this run resolved. FreshRef marks the
// latter, which the lockfile never recorded.
RecordedRef string
FreshRef bool
// TermDetail, when set, replaces Detail on the terminal: a short form
// without request URLs. --json and the run log keep Detail.
TermDetail string
// TrackedAction marks a not-pinned bare SHA whose owner/repo the
// workflow already locks at another ref, or whose entry records its own
// SHA as its ref. It is a change to a tracked action, not onboarding,
// so --no-onboard lets it through to be resolved and history-checked.
TrackedAction bool
// Reresolve asks planning to drop this finding's recorded dependency
// from the inventory and resolve it afresh (see PrunesDependency).
Reresolve bool
// Retryable marks a check that ended without an answer from GitHub (an
// outage or rate limit): the entry is unverified, so it blocks until a
// re-run succeeds. See MarkRetryable.
Retryable bool
// RetryReason says why, e.g. "GitHub API unreachable (connection
// refused)"; RetryAt is the earliest time GitHub said a retry could
// succeed (zero when it didn't say), and RateLimited marks a rate limit.
RetryReason string
RetryAt time.Time
RateLimited bool
// AnonymousLimited marks a rate limit on the anonymous github.com
// public fallback of a *.ghe.com run, which a tenant token can't raise.
AnonymousLimited bool
// ImageUses are the places a container-image finding's image is used.
ImageUses []uses.Image
}
Finding represents a single diagnosed issue (or clean bill) for a workflow.
func RunChecks ¶
func RunChecks(ctx context.Context, pw ParsedWorkflow, lf parserlock.File, r CheckResolver) []Finding
RunChecks evaluates all enabled validators against the given parsed workflow and returns findings in catalog order. The lockfile snapshot scopes the structural checks; the resolver enables the resolver-bound checks (misleading-sha, ref-moved, forgery). When r is nil, resolver-bound checks are skipped silently.
Returned findings have their primitive fields populated, plus ActionRef (for direct uses) and Dependency (for ref-tied entries). ParentNWO is attached by the caller (diagnoseOneParsed) because it needs lookup tables runChecks doesn't carry.
func (Finding) DocURL ¶
DocURL returns the docs section for f's category. It's derived rather than stored so a finding whose category is rewritten later (e.g. by the --no-onboard gate) still links its own section.
func (*Finding) IsRemediableNotPinned ¶ added in v0.1.6
IsRemediableNotPinned reports whether pinning can resolve this finding.
func (*Finding) IsValid ¶
IsValid reports whether f leaves the verdict valid. Severity alone decides: the JSON `valid` field and the exit code must agree with the severities consumers see, so a blocking finding has to be emitted as an error.
func (*Finding) IsWarning ¶
IsWarning returns true for findings that should render as warnings (not errors). An error-severity finding never does: it blocks the run.
func (*Finding) MarkRefMoved ¶ added in v0.3.0
func (f *Finding) MarkRefMoved()
MarkRefMoved makes f, a ref-moved check of f.Dependency whose ref now resolves to f.ObservedSHA, the warning for a commit confirmed in the ref's history.
func (*Finding) MarkRetryable ¶ added in v0.3.0
MarkRetryable makes f a blocking, retryable finding for a check that failed with cause, a transient error.
type InventoryEntry ¶
type InventoryEntry struct {
Dep dep.Dependency
File string
Direct bool
// Parents lists the dep keys of parent composite actions that pull in this
// transitive dependency. Empty for direct dependencies.
Parents []string
}
InventoryEntry describes a single dependency with context.
type ParsedWorkflow ¶
type ParsedWorkflow struct {
Path string
// Refs are all remote dependency roots attributed to the workflow. This
// includes refs found inside in-repo `$/…` actions.
Refs []parserlock.ActionRef
// RewriteRefs are the refs that pinning may rewrite in source: the
// workflow YAML plus the in-repo `$/…` action files it reaches.
RewriteRefs []parserlock.ActionRef
// SelfActionFiles are the in-repo action definition files reached through
// step-level `$/…` refs. They are rewritten alongside the workflow.
SelfActionFiles []string
// SelfActionRefs are the remote refs found specifically inside SelfActionFiles.
SelfActionRefs []parserlock.ActionRef
// SHASpellings maps each `uses:` value that spells a full SHA in
// uppercase, as the source has it, to its lowercase form, which Refs
// carry so lockfile lookups see one spelling.
SHASpellings map[string]string
// LocalPaths are the workflow's own `./…` uses.
LocalPaths []string
// NestedLocalUses are `./…` uses inside the in-repo `$/…` actions it
// reaches, with the action that contains each.
NestedLocalUses []workflowfile.LocalUse
SelfRepositoryRefs []string
// SelfRepositoryRefErrs holds malformed `$/…@ref` values (the invalid form).
SelfRepositoryRefErrs []string
SelfRepositoryResolutionErrs []string
ExistingDeps []dep.Dependency
// Images are the mutable container images the workflow runs directly.
Images []uses.Image
LoadErr error
DepsErr error
// Resolved, when true, instructs DiagnoseParsed to run this
// workflow's diagnostics with a nil resolver. Network-bound checks
// (ref-moved) are skipped and the engine relies on
// purely structural validation against the on-disk lockfile. Caller
// is asserting "this workflow is already fully resolved" — typically
// set on the fast path when every direct ref in the workflow is
// already recorded in the lockfile.
Resolved bool
}
ParsedWorkflow holds the per-workflow parse result that both phases need. LoadErr / DepsErr capture early failures so DiagnoseParsed can surface them as findings without re-loading the file.
func (ParsedWorkflow) IsFullyRecorded ¶
func (pw ParsedWorkflow) IsFullyRecorded() bool
IsFullyRecorded returns true when every direct ref has a matching lockfile entry — the steady-state happy path.
func (ParsedWorkflow) PartitionRefs ¶
func (pw ParsedWorkflow) PartitionRefs() (recorded, unrecorded []parserlock.ActionRef)
PartitionRefs splits refs into recorded (matching a lockfile entry by NWO@Ref or NWO@SHA) and unrecorded (need network resolution). When an error prevented loading refs or deps, everything is unrecorded.
func (ParsedWorkflow) RecordedDeps ¶
func (pw ParsedWorkflow) RecordedDeps(recorded []parserlock.ActionRef) []dep.Dependency
RecordedDeps returns the subset of ExistingDeps whose NWO@Ref or NWO@SHA matches one of the given recorded refs.
type PruneEffect ¶ added in v0.3.0
type PruneEffect int
PruneEffect is what planning does with a recorded entry a finding prunes from the workflow's inventory. Either way the recorded entry itself stays in the lock while another entry's `uses:` lists it; only a composite whose manifest the run read has its edges replaced (see lockfile.State.Set).
const ( // NotPruned: planning writes the entry back verbatim. NotPruned PruneEffect = iota // Reresolved: planning re-resolves the workflow's use under the same // key, overwriting the entry, unless narrowing re-keys a full-SHA use. Reresolved // Dropped: the workflow's use leaves the entry's key, because the // workflow no longer uses it or it re-resolves under a new name. Dropped )
func PruneEffectOf ¶ added in v0.3.0
func PruneEffectOf(f Finding, acceptMoved, relock bool) PruneEffect
PruneEffectOf reports what planning does with f's dependency.
relock prunes ref-moved and tag-commit-mismatch deps, so a ref that moved ahead can be re-pinned without accepting a possibly-tampered unreachable pin; acceptMoved prunes unreachable-pin deps too. A same-ID redirect is pruned so it re-resolves under the canonical name.
type Report ¶
type Report struct {
Workflows []WorkflowReport
// RepoFindings are findings that apply to the repository as a whole
// (not to any individual workflow).
RepoFindings []Finding
// Verified holds the on-disk keys and commits this run resolved afresh
// and checked with no blocking finding, by VerifiedKey.
Verified map[string]bool
// RetriedFor is how long the run waited between retries of requests
// GitHub didn't answer.
RetriedFor time.Duration
}
Report aggregates all workflow reports for a check run.
func (*Report) RestoreDeferred ¶ added in v0.3.0
func (r *Report) RestoreDeferred()
RestoreDeferred puts every deferred finding not yet restored back on its workflow. A run that writes nothing leaves each deferred entry as recorded, so its finding stands; call this on every such path before reporting.
func (*Report) WorkflowsNeedingAttention ¶
func (r *Report) WorkflowsNeedingAttention() []WorkflowReport
WorkflowsNeedingAttention returns only workflows with actionable findings.
type Severity ¶
type Severity string
Severity indicates how serious a finding is if it represents a real problem. Pair with Confidence to express how strongly the tool stands behind the call.
const ( // SeverityOK means the finding represents a clean state — no // action needed. SeverityOK Severity = "ok" // SeverityInfo is purely informational and does not require // action. SeverityInfo Severity = "info" // SeverityWarning indicates a concern worth surfacing but not // blocking on. SeverityWarning Severity = "warning" // SeverityError indicates a blocking issue the operator must // resolve. SeverityError Severity = "error" )
type WorkflowReport ¶
type WorkflowReport struct {
Path string
Findings []Finding
// SkipCommit prevents terminal parse failures from entering the write phase.
SkipCommit bool
// BlockingResolverError indicates that diagnosis classified a resolver error as blocking.
BlockingResolverError bool
// ActionRefs are all remote dependency roots attributed to the workflow,
// including refs found inside in-repo `$/…` actions.
ActionRefs []parserlock.ActionRef
// RewriteRefs are the refs eligible for source rewriting: workflow YAML
// plus the in-repo `$/…` action files listed in SelfActionFiles.
RewriteRefs []parserlock.ActionRef
// SHASpellings are the source spellings of uppercase full-SHA refs
// (see ParsedWorkflow).
SHASpellings map[string]string
// SelfActionFiles are in-repo action definition files reached via `$/…`.
SelfActionFiles []string
// SelfActionRefs are the remote refs found specifically inside SelfActionFiles.
SelfActionRefs []parserlock.ActionRef
// Deps are the existing pinned dependencies (nil if not pinned).
Deps []dep.Dependency
// Inventory lists all dependencies with direct/transitive classification.
Inventory []InventoryEntry
// Deferred are findings parity excused or accepted on the strength of
// a write the run has yet to plan.
Deferred []Deferred
}
WorkflowReport aggregates all findings for a single workflow file.
func (*WorkflowReport) CountByCategory ¶
func (r *WorkflowReport) CountByCategory(c Category) int
CountByCategory returns the number of findings with the given category.
func (*WorkflowReport) IsValid ¶
func (wr *WorkflowReport) IsValid() bool
IsValid returns true if no findings represent integrity violations.
func (*WorkflowReport) NeedsAttention ¶
func (r *WorkflowReport) NeedsAttention() bool
NeedsAttention returns true if this workflow has any non-OK findings.
func (*WorkflowReport) Restore ¶ added in v0.3.0
func (wr *WorkflowReport) Restore(i int)
Restore puts d's finding back on wr, replacing the warning that accepted it, once.