@@ -228,17 +228,16 @@ func planWorkflow(ctx context.Context, wr checks.WorkflowReport, opts PlanOption
228228 }
229229 parentMap = dep .RekeyParentMap (parentMap , canonicalRekeys )
230230
231- // Narrow mutable version tags to patch tags, and resolve bare-SHA refs
232- // to a symbolic tag when one exists.
231+ // Narrow mutable version tags to exact patch tags.
233232 status ("pinning " + wr .Path )
234233 rewrites := make (map [string ]string )
235- narrowedNWOs := make (map [string ]bool ) // NWOs where narrowing chose a tag
234+ preservedDeps := make (map [int ]bool )
236235
237- narrowDirectDeps (ctx , opts , deps , rewriteTracker , rewrites , narrowedNWOs )
236+ narrowDirectDeps (ctx , opts , deps , rewriteTracker , rewrites , preservedDeps )
238237
239238 // ReverseLookup canonicalizes each dep's ref while preserving the tags
240239 // narrowing chose and transitive deps' declared refs.
241- rlRewrites , lookupIssues , err := reverseLookupRewrites (ctx , opts , wr , deps , rewriteTracker , narrowedNWOs )
240+ rlRewrites , lookupIssues , err := reverseLookupRewrites (ctx , opts , wr , deps , rewriteTracker , preservedDeps )
242241 if err != nil {
243242 return planResult {}, err
244243 }
@@ -308,8 +307,7 @@ func planWorkflow(ctx context.Context, wr checks.WorkflowReport, opts PlanOption
308307 SelfActionFiles : wr .SelfActionFiles ,
309308 })
310309 } else if len (wplans ) == 0 {
311- // No rewrites and no plan entry yet — still include the workflow
312- // so EnsureSentinel can be applied during commit.
310+ // Keep the workflow in the plan so its lockfile entry is updated.
313311 wplans = append (wplans , WorkflowPlan {Path : wr .Path , SelfActionFiles : wr .SelfActionFiles })
314312 }
315313
@@ -404,10 +402,9 @@ func unresolvedEntries(wr checks.WorkflowReport, unrecordedRefs []parserlock.Act
404402 return out
405403}
406404
407- // narrowDirectDeps rewrites direct deps' mutable refs to precise tags (bare SHA
408- // or partial/non-semver ref -> full patch tag), leaving transitive deps verbatim.
409- // Each rewrite mutates deps[i].Ref and records the old->new uses and narrowed NWO.
410- func narrowDirectDeps (ctx context.Context , opts PlanOptions , deps []dep.Dependency , directTracker lockfile.DirectTracker , rewrites map [string ]string , narrowedNWOs map [string ]bool ) {
405+ // narrowDirectDeps rewrites direct partial semver refs to exact patch tags,
406+ // leaving bare SHA and transitive refs for reverse lookup.
407+ func narrowDirectDeps (ctx context.Context , opts PlanOptions , deps []dep.Dependency , directTracker lockfile.DirectTracker , rewrites map [string ]string , preservedDeps map [int ]bool ) {
411408 if opts .Tagger == nil {
412409 return
413410 }
@@ -425,29 +422,11 @@ func narrowDirectDeps(ctx context.Context, opts PlanOptions, deps []dep.Dependen
425422 continue
426423 }
427424
428- // Bare-SHA refs: find a tag pointing at the same commit.
429- // Skip if --no-narrow — the user wants to keep their commit SHA as-is.
430- // Mark narrowedNWOs so ReverseLookup also preserves the SHA ref.
425+ // ReverseLookup owns bare-SHA normalization unless --no-narrow protects it.
431426 if parserlock .IsFullSha (dep .Ref ) {
432427 if opts .NoNarrow {
433- narrowedNWOs [strings .ToLower (dep .NWO )] = true
434- continue
428+ preservedDeps [i ] = true
435429 }
436- patchTag , err := opts .Tagger .BestPatchTagForSHA (ctx , owner , repo , dep .SHA )
437- if err != nil {
438- continue
439- }
440- if patchTag == "" {
441- patchTag , err = opts .Tagger .BestAncestorTag (ctx , owner , repo , dep .SHA )
442- if err != nil || patchTag == "" {
443- continue
444- }
445- }
446- oldUses := dep .NWO + "@" + dep .Ref
447- newUses := dep .NWO + "@" + patchTag
448- rewrites [oldUses ] = newUses
449- dep .Ref = patchTag
450- narrowedNWOs [strings .ToLower (dep .NWO )] = true
451430 continue
452431 }
453432
@@ -469,37 +448,30 @@ func narrowDirectDeps(ctx context.Context, opts PlanOptions, deps []dep.Dependen
469448 continue
470449 }
471450
472- patchTag , err := opts .Tagger .BestPatchTagForSHA (ctx , owner , repo , dep .SHA )
473- if err != nil {
451+ patchTag , err := opts .Tagger .BestPatchTagForSHA (ctx , owner , repo , dep .SHA , dep . Ref )
452+ if err != nil || patchTag == "" {
474453 continue
475454 }
476- // No exact tag match - if the repo publishes semver releases,
477- // walk back to the latest tag that's an ancestor of this SHA.
478- if patchTag == "" {
479- patchTag , err = opts .Tagger .BestAncestorTag (ctx , owner , repo , dep .SHA )
480- if err != nil || patchTag == "" {
481- continue
482- }
483- }
484455 oldUses := dep .NWO + "@" + dep .Ref
485456 newUses := dep .NWO + "@" + patchTag
486457 rewrites [oldUses ] = newUses
487458 dep .Ref = patchTag
488- narrowedNWOs [ nwoLower ] = true
459+ preservedDeps [ i ] = true
489460 }
490461}
491462
492463// reverseLookupRewrites canonicalizes dep refs via ReverseLookup (SHA -> tag/
493464// branch), restoring refs that narrowing or a transitive dep already fixed.
494465// Returns the rewrites map, indices of unresolvable deps, and any hard error.
495- func reverseLookupRewrites (ctx context.Context , opts PlanOptions , wr checks.WorkflowReport , deps []dep.Dependency , directTracker lockfile.DirectTracker , narrowedNWOs map [string ]bool ) (map [string ]string , []resolve.LookupIssue , error ) {
466+ func reverseLookupRewrites (ctx context.Context , opts PlanOptions , wr checks.WorkflowReport , deps []dep.Dependency , directTracker lockfile.DirectTracker , preservedDeps map [int ]bool ) (map [string ]string , []resolve.LookupIssue , error ) {
496467 // Save narrowed refs before ReverseLookup - it may overwrite dep.Ref
497468 // with a branch name, but we want to keep the semver tag narrowing chose.
498- narrowedRefs := make (map [int ]string )
469+ preservedRefs := make (map [int ]string )
470+ preservedKeys := make (map [string ]bool )
499471 for i := range deps {
500- nwo := strings . ToLower ( deps [i ]. NWO )
501- if narrowedNWOs [ nwo ] {
502- narrowedRefs [ i ] = deps [i ].Ref
472+ if preservedDeps [i ] {
473+ preservedRefs [ i ] = deps [ i ]. Ref
474+ preservedKeys [ deps [i ].Key ()] = true
503475 }
504476 }
505477
@@ -519,7 +491,7 @@ func reverseLookupRewrites(ctx context.Context, opts PlanOptions, wr checks.Work
519491 return nil , nil , fmt .Errorf ("reverse lookup: %w" , err )
520492 }
521493 // Restore narrowed refs that ReverseLookup may have overwritten.
522- for i , ref := range narrowedRefs {
494+ for i , ref := range preservedRefs {
523495 deps [i ].Ref = ref
524496 }
525497 // Restore transitive deps' declared refs — we don't own the composite's
@@ -540,11 +512,8 @@ func reverseLookupRewrites(ctx context.Context, opts PlanOptions, wr checks.Work
540512 if transitiveRewriteKeys [k ] {
541513 continue
542514 }
543- if at := strings .Index (k , "@" ); at > 0 {
544- nwo := strings .ToLower (k [:at ])
545- if narrowedNWOs [nwo ] {
546- continue
547- }
515+ if preservedKeys [k ] {
516+ continue
548517 }
549518 rewrites [k ] = v
550519 }
@@ -747,17 +716,10 @@ func narrowVerifiedEntries(ctx context.Context, entries []Entry, opts PlanOption
747716 if sv .IsFull () {
748717 continue
749718 }
750- // Try exact tag match, then ancestor fallback.
751- patchTag , err := opts .Tagger .BestPatchTagForSHA (ctx , owner , repo , e .SHA )
752- if err != nil {
719+ patchTag , err := opts .Tagger .BestPatchTagForSHA (ctx , owner , repo , e .SHA , e .Ref )
720+ if err != nil || patchTag == "" {
753721 continue
754722 }
755- if patchTag == "" {
756- patchTag , err = opts .Tagger .BestAncestorTag (ctx , owner , repo , e .SHA )
757- if err != nil || patchTag == "" {
758- continue
759- }
760- }
761723 oldRef := e .Ref
762724 oldUses := e .NWO + "@" + oldRef
763725 newUses := e .NWO + "@" + patchTag
0 commit comments