Documentation
¶
Overview ¶
Package resolve resolves action refs to commit SHAs, recursively discovers transitive dependencies, and verifies commit reachability.
Index ¶
- Constants
- type AncestryStatus
- type ImpostorError
- type Option
- type ReachabilityResult
- type ReachabilityStatus
- type Resolver
- func (r *Resolver) CheckAncestry(ctx context.Context, owner, repo, pinnedSHA, liveSHA string) (AncestryStatus, string)
- func (r *Resolver) CheckReachability(ctx context.Context, owner, repo, sha, ref string) ReachabilityResult
- func (r *Resolver) CheckReachabilityAll(ctx context.Context, deps []dep.Dependency) []ReachabilityResult
- func (r *Resolver) DiscoverContaining(ctx context.Context, owner, repo, sha, hintRef string) (tag, branch string, err error)
- func (r *Resolver) DiscoverContainingDefault(ctx context.Context, owner, repo, sha, hintRef, defaultBranch string) (tag, branch string, err error)
- func (r *Resolver) FireResolveProgress(done, total int)
- func (r *Resolver) GHClient() *ghapi.Client
- func (r *Resolver) GetBranchHead(ctx context.Context, owner, repo, name string) (ghapi.BranchHead, bool)
- func (r *Resolver) GetDefaultBranch(ctx context.Context, owner, repo string) string
- func (r *Resolver) Hostname() string
- func (r *Resolver) IsKnownTagObject(owner, repo, sha string) bool
- func (r *Resolver) LatestRef(ctx context.Context, owner, repo string) (string, error)
- func (r *Resolver) LikelyBranches(ctx context.Context, owner, repo, sha, ref, defaultBranch string) []ghapi.BranchHead
- func (r *Resolver) ListBranches(ctx context.Context, owner, repo string) ([]ghapi.BranchHead, error)
- func (r *Resolver) ListProtectedBranches(ctx context.Context, owner, repo string) []ghapi.BranchHead
- func (r *Resolver) ListReleaseBranches(ctx context.Context, owner, repo string) []ghapi.BranchHead
- func (r *Resolver) ListTagsForRepo(ctx context.Context, owner, repo string) ([]ghapi.TagEntry, error)
- func (r *Resolver) MatchingHeadRefs(ctx context.Context, owner, repo, prefix string) []ghapi.BranchHead
- func (r *Resolver) PeelTagObject(ctx context.Context, owner, repo, sha string) (commit string, ok bool)
- func (r *Resolver) RepoIDs(ctx context.Context, owner, repo string) (int64, int64, error)
- func (r *Resolver) ResolveAllRecursive(ctx context.Context, refs []parserlock.ActionRef) ([]dep.Dependency, dep.ParentMap, error)
- func (r *Resolver) ReverseLookup(ctx context.Context, deps []dep.Dependency) (map[string]string, error)
- func (r *Resolver) SeedBranchHints(deps []dep.Dependency)
- func (r *Resolver) SeedFromLockfile(deps []dep.Dependency)
Constants ¶
const DefaultMaxRecursionDepth = 10
DefaultMaxRecursionDepth matches the runner's composite action recursion limit.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AncestryStatus ¶
type AncestryStatus int
AncestryStatus represents whether a pinned SHA is a legitimate ancestor of the live SHA.
const ( // AncestryConfirmed means the pinned SHA is an ancestor of the live SHA. AncestryConfirmed AncestryStatus = iota // AncestryNotAncestor means the pinned SHA is NOT an ancestor — possible forgery. AncestryNotAncestor // AncestryUnknown means the check could not be completed (rate limit, API error). AncestryUnknown )
type ImpostorError ¶
type ImpostorError struct {
NWO string // owner/repo
Ref string // ref as written in the workflow
SHA string // resolved commit SHA
}
ImpostorError indicates a commit that is not reachable from any branch — a fork-network / impostor signal. It carries the offending action so callers can report which workflow is affected without abandoning the whole run.
func (*ImpostorError) Error ¶
func (e *ImpostorError) Error() string
type Option ¶
type Option func(*Resolver)
Option configures a Resolver at construction time. Pass to New.
func WithCheckReachabilityFunc ¶
func WithCheckReachabilityFunc(fn func(ctx context.Context, owner, repo, sha, ref string) (ReachabilityStatus, string)) Option
WithCheckReachabilityFunc overrides the default REST-based reachability check. Intended for tests that want deterministic branch-discovery results.
func WithProfile ¶
WithProfile attaches profiling instrumentation.
func WithSleepFn ¶
WithSleepFn overrides the context-aware sleep used for rate-limit waits.
func WithTransport ¶
func WithTransport(t http.RoundTripper) Option
WithTransport overrides the HTTP transport. Use in tests with httpmock.
type ReachabilityResult ¶
type ReachabilityResult struct {
Owner string
Repo string
Ref string
SHA string
DepKey string // full dependency key (e.g. "actions/cache/save@v4")
Status ReachabilityStatus
Detail string // human-readable detail (e.g. compare status or error)
// FullScanUsed is true when the commit was not found in the canonical
// "likely" branch set (default, protected, release/v*, literal ref,
// lockfile hint) and the check had to fall back to scanning every branch
// in the repo. Even when the commit is ultimately Reachable, a full-scan
// fallback means it is not on a canonical branch — a notable signal worth
// surfacing to the user.
FullScanUsed bool
}
ReachabilityResult holds the outcome of a single reachability check.
type ReachabilityStatus ¶
type ReachabilityStatus string
ReachabilityStatus represents the result of a commit reachability check.
const ( // Reachable means the SHA is confirmed on the ref's lineage. Reachable ReachabilityStatus = "reachable" // Unreachable means the SHA is confirmed NOT on the ref's lineage // (e.g. it exists only in a fork network). Unreachable ReachabilityStatus = "unreachable" // ReachabilityUnknown means the check could not be completed // (timeout, rate limit, API error). ReachabilityUnknown ReachabilityStatus = "unknown" )
type Resolver ¶
type Resolver struct {
// MaxRecursionDepth caps transitive composite action resolution depth.
MaxRecursionDepth int
// OnResolveProgress is called when a resolution batch makes progress.
OnResolveProgress func(done, total int)
// Pool is the shared worker pool for parallel resolution and reachability.
Pool *pinpool.Pool
// contains filtered or unexported fields
}
Resolver resolves action refs to commit SHAs.
func New ¶
New creates a Resolver for the given hostname and pool. Use With* options to inject a test transport, profiling, or test overrides.
func (*Resolver) CheckAncestry ¶
func (r *Resolver) CheckAncestry(ctx context.Context, owner, repo, pinnedSHA, liveSHA string) (AncestryStatus, string)
CheckAncestry uses the Compare API to test whether pinnedSHA is an ancestor of liveSHA. This detects lockfile forgery: if someone injects a SHA that was never in the ref's lineage, merge_base(pinned, live) ≠ pinned.
func (*Resolver) CheckReachability ¶
func (r *Resolver) CheckReachability(ctx context.Context, owner, repo, sha, ref string) ReachabilityResult
CheckReachability verifies that the pinned SHA is reachable from at least one branch of owner/repo, using the documented REST APIs (list-branches + compare for ancestry). This catches fork-network injection where a SHA exists in GitHub's shared object store but is not part of the canonical repository's history.
func (*Resolver) CheckReachabilityAll ¶
func (r *Resolver) CheckReachabilityAll(ctx context.Context, deps []dep.Dependency) []ReachabilityResult
CheckReachabilityAll runs reachability checks on a batch of dependencies, deduplicating by owner/repo/sha/ref.
func (*Resolver) DiscoverContaining ¶
func (r *Resolver) DiscoverContaining(ctx context.Context, owner, repo, sha, hintRef string) (tag, branch string, err error)
DiscoverContaining returns (tag, branch) for sha in owner/repo, using the documented REST APIs: list-branches (with compare for ancestry) and list-tags. Results are cached for the lifetime of the resolver.
Selection rules:
- If hintRef is one of the discovered tags it wins; otherwise the first tag (lexicographic) is picked. tag may be empty when no tag points at sha.
- Protected branches are searched first (hintRef → default → lex). If no protected branch contains sha, the search falls back to all branches in the same tier order.
- branch is REQUIRED to be non-empty; an error is returned otherwise (impostor / fork-network signal).
hintRef may be empty (e.g. for bare-SHA pins). The repo's default branch is discovered automatically via GET /repos/{owner}/{repo} (cached).
func (*Resolver) DiscoverContainingDefault ¶
func (r *Resolver) DiscoverContainingDefault(ctx context.Context, owner, repo, sha, hintRef, defaultBranch string) (tag, branch string, err error)
DiscoverContainingDefault is DiscoverContaining with an explicit hint at the repository's default branch (e.g. "main"). When the discovered branch set contains defaultBranch it is preferred over lexicographic ordering.
Branch search is two-phase. Phase 1 validates the likely/canonical set directly (literal ref, recorded hint branch, default branch, protected branches, release/v* branches) so a relevant branch is never missed because it sorts beyond the paginated listing cap. Phase 2 — a full protected-first then all-branches scan — runs only when phase 1 finds nothing. An impostor error is returned only if both phases fail to place the commit.
func (*Resolver) FireResolveProgress ¶
FireResolveProgress fires OnResolveProgress. Safe from multiple goroutines.
func (*Resolver) GetBranchHead ¶
func (r *Resolver) GetBranchHead(ctx context.Context, owner, repo, name string) (ghapi.BranchHead, bool)
GetBranchHead looks up a single branch by name.
func (*Resolver) GetDefaultBranch ¶
GetDefaultBranch returns the default branch name for owner/repo.
func (*Resolver) IsKnownTagObject ¶
IsKnownTagObject reports whether (owner, repo, sha) is already cached as an annotated tag object. Cache-only — never issues a network call.
func (*Resolver) LikelyBranches ¶
func (r *Resolver) LikelyBranches(ctx context.Context, owner, repo, sha, ref, defaultBranch string) []ghapi.BranchHead
LikelyBranches assembles the high-trust candidate set validated before any full branch scan: the literal ref (when it is a branch), the recorded lockfile hint branch, the default branch, protected branches and release/v* branches. Deduplicated by name; order is most-trusted first.
func (*Resolver) ListBranches ¶
func (r *Resolver) ListBranches(ctx context.Context, owner, repo string) ([]ghapi.BranchHead, error)
ListBranches returns all branches for owner/repo.
func (*Resolver) ListProtectedBranches ¶
func (r *Resolver) ListProtectedBranches(ctx context.Context, owner, repo string) []ghapi.BranchHead
ListProtectedBranches returns branches with protection rules enabled.
func (*Resolver) ListReleaseBranches ¶
ListReleaseBranches returns release/v* branches (the canonical action publication branches) by matching heads/v and heads/release. Best-effort; cached per owner/repo.
func (*Resolver) ListTagsForRepo ¶
func (r *Resolver) ListTagsForRepo(ctx context.Context, owner, repo string) ([]ghapi.TagEntry, error)
ListTagsForRepo returns all tags for owner/repo.
func (*Resolver) MatchingHeadRefs ¶
func (r *Resolver) MatchingHeadRefs(ctx context.Context, owner, repo, prefix string) []ghapi.BranchHead
MatchingHeadRefs returns branches whose name starts with prefix.
func (*Resolver) PeelTagObject ¶
func (r *Resolver) PeelTagObject(ctx context.Context, owner, repo, sha string) (commit string, ok bool)
PeelTagObject reports whether sha is an annotated tag object in owner/repo and, if so, the commit SHA it dereferences to. Tag-of-tag chains are peeled server-side. Returns ok=false for lightweight tags, plain commits, unknown SHAs, or any lookup failure (fail open).
func (*Resolver) ResolveAllRecursive ¶
func (r *Resolver) ResolveAllRecursive(ctx context.Context, refs []parserlock.ActionRef) ([]dep.Dependency, dep.ParentMap, error)
ResolveAllRecursive resolves action refs and recursively discovers transitive dependencies from composite actions by reading their action.yml via GraphQL. The returned ParentMap (child dep key → parent dep keys) is owned by the caller and safe to mutate or hold across concurrent resolver calls.
func (*Resolver) ReverseLookup ¶
func (r *Resolver) ReverseLookup(ctx context.Context, deps []dep.Dependency) (map[string]string, error)
ReverseLookup performs a reverse lookup (SHA → containing tag/branch) for every entry in deps via DiscoverContaining, populates dep.Tag and dep.Branch, and computes the canonical @ref. When the canonical ref differs from dep.Ref the change is recorded in the returned rewrites map and dep.Ref is updated in place.
func (*Resolver) SeedBranchHints ¶
func (r *Resolver) SeedBranchHints(deps []dep.Dependency)
SeedBranchHints records a branch-of-record for each dep so subsequent containing-branch scans try that branch first. Hints from a previous lockfile are advisory: a miss falls through to a full branch scan.
func (*Resolver) SeedFromLockfile ¶
func (r *Resolver) SeedFromLockfile(deps []dep.Dependency)
SeedFromLockfile pre-warms the resolution and reachability caches so repeat runs skip redundant API calls. Do NOT call with --rescan: seeding would hide ref movement and skip reachability checks.