laskoviymishka commented on code in PR #1427:
URL: https://github.com/apache/iceberg-go/pull/1427#discussion_r3553319684


##########
table/snapshot_producers.go:
##########
@@ -812,125 +886,207 @@ func (sp *snapshotProducer) writeAddedManifest(content 
iceberg.ManifestContent,
 }
 
 func (sp *snapshotProducer) summary(props iceberg.Properties) (Summary, error) 
{
+       delta, err := sp.accumulateSummaryDelta(nil)
+       if err != nil {
+               return Summary{}, err
+       }
+
+       var previousSnapshot *Snapshot
+       if sp.parentSnapshotID > 0 {
+               previousSnapshot, err = 
sp.txn.meta.SnapshotByID(sp.parentSnapshotID)
+               if err != nil {
+                       return Summary{}, fmt.Errorf("summary: lookup parent 
snapshot %d: %w", sp.parentSnapshotID, err)
+               }
+       }
+
+       var previousSummary iceberg.Properties
+       if previousSnapshot != nil && previousSnapshot.Summary != nil {
+               previousSummary = previousSnapshot.Summary.Properties
+       }
+
+       return sp.rebaseSummary(delta, previousSummary, props)
+}
+
+// summaryOnRetry recomputes the snapshot summary against the fresh parent for 
an
+// OCC retry. Additions are always counted; a removal of a delete file / 
deletion
+// vector is counted only when it is still present on parent (present.counts),
+// because a peer may have already dropped it — replaying the captured removal
+// delta on top of a fresh parent that no longer counts the file undercounts
+// total-delete-files. Removed data files are always counted: a successful 
retry
+// has already asserted they are present (assertRemovedFilesPresent).
+func (sp *snapshotProducer) summaryOnRetry(props iceberg.Properties, parent 
*Snapshot, present *removedFilePresence) (Summary, error) {
+       delta, err := sp.accumulateSummaryDelta(present.counts)
+       if err != nil {
+               return Summary{}, err
+       }
+
+       var previousSummary iceberg.Properties
+       if parent != nil && parent.Summary != nil {
+               previousSummary = parent.Summary.Properties
+       }
+
+       return sp.rebaseSummary(delta, previousSummary, props)
+}
+
+// accumulateSummaryDelta builds the added/removed file delta for this 
producer.
+// countDeleteRemoval, when non-nil, gates removal of delete files / deletion
+// vectors (returning false skips that removal); data-file removals are always
+// counted. nil counts every removal (the initial commit).
+func (sp *snapshotProducer) accumulateSummaryDelta(countDeleteRemoval 
func(iceberg.DataFile) bool) (iceberg.Properties, error) {
        var ssc SnapshotSummaryCollector
        partitionSummaryLimit := sp.txn.meta.props.
                GetInt(WritePartitionSummaryLimitKey, 
WritePartitionSummaryLimitDefault)
        ssc.setPartitionSummaryLimit(partitionSummaryLimit)
 
-       var err error
        currentSchema := sp.txn.meta.CurrentSchema()
        partitionSpec, err := sp.txn.meta.CurrentSpec()
        if err != nil || partitionSpec == nil {
-               return Summary{}, fmt.Errorf("could not get current partition 
spec: %w", err)
+               return nil, fmt.Errorf("could not get current partition spec: 
%w", err)
        }
        for _, df := range sp.addedFiles {
                if err = ssc.addFile(df, currentSchema, 
sp.spec(int(df.SpecID()))); err != nil {
-                       return Summary{}, err
+                       return nil, err
                }
        }
        for _, df := range sp.addedDeleteFiles {
                if err = ssc.addFile(df, currentSchema, 
sp.spec(int(df.SpecID()))); err != nil {
-                       return Summary{}, err
+                       return nil, err
                }
        }
 
-       if len(sp.deletedFiles) > 0 {
-               specs := sp.txn.meta.specs
-               for _, df := range sp.deletedFiles {
-                       if err = ssc.removeFile(df, currentSchema, 
specs[df.SpecID()]); err != nil {
-                               return Summary{}, err
-                       }
+       specs := sp.txn.meta.specs
+       for _, df := range sp.deletedFiles {
+               if err = ssc.removeFile(df, currentSchema, specs[df.SpecID()]); 
err != nil {
+                       return nil, err
                }
        }
-
-       if len(sp.deletedDeleteFiles) > 0 {
-               specs := sp.txn.meta.specs
-               for _, df := range sp.deletedDeleteFiles {
-                       if err = ssc.removeFile(df, currentSchema, 
specs[df.SpecID()]); err != nil {
-                               return Summary{}, err
-                       }
+       for _, df := range sp.deletedDeleteFiles {
+               if countDeleteRemoval != nil && !countDeleteRemoval(df) {
+                       continue
                }
-       }
-
-       if len(sp.deletedDVsByRef) > 0 {
-               specs := sp.txn.meta.specs
-               for _, df := range sp.deletedDVsByRef {
-                       if err = ssc.removeFile(df, currentSchema, 
specs[df.SpecID()]); err != nil {
-                               return Summary{}, err
-                       }
+               if err = ssc.removeFile(df, currentSchema, specs[df.SpecID()]); 
err != nil {
+                       return nil, err
                }
        }
-
-       var previousSnapshot *Snapshot
-       if sp.parentSnapshotID > 0 {
-               previousSnapshot, err = 
sp.txn.meta.SnapshotByID(sp.parentSnapshotID)
-               if err != nil {
-                       return Summary{}, fmt.Errorf("summary: lookup parent 
snapshot %d: %w", sp.parentSnapshotID, err)
+       for _, df := range sp.deletedDVsByRef {
+               if countDeleteRemoval != nil && !countDeleteRemoval(df) {
+                       continue
+               }
+               if err = ssc.removeFile(df, currentSchema, specs[df.SpecID()]); 
err != nil {
+                       return nil, err
                }
        }
 
-       var previousSummary iceberg.Properties
-       if previousSnapshot != nil && previousSnapshot.Summary != nil {
-               previousSummary = previousSnapshot.Summary.Properties
-       }
+       return ssc.build(), nil
+}
 
-       summaryProps := ssc.build()
-       maps.Copy(summaryProps, props)
+func (sp *snapshotProducer) rebaseSummary(delta, previousSummary, props 
iceberg.Properties) (Summary, error) {
+       maps.Copy(delta, props)
 
        return updateSnapshotSummaries(Summary{
                Operation:  sp.op,
-               Properties: summaryProps,
+               Properties: delta,
        }, previousSummary)
 }
 
-// computeOwnManifests returns the subset of allManifests that were written
-// by this producer (i.e. not inherited from the parent snapshot). These are
-// preserved across OCC retry attempts when the manifest list is rebuilt
-// against a fresh parent.
-func (sp *snapshotProducer) computeOwnManifests(allManifests 
[]iceberg.ManifestFile) ([]iceberg.ManifestFile, error) {
-       if sp.parentSnapshotID <= 0 {
-               // No parent means all manifests are new — nothing to exclude.
-               return allManifests, nil
+// removedFilePresence records which of a producer's to-be-removed delete files
+// and deletion vectors are still live on the retry's fresh parent. counts is 
the
+// gate passed to accumulateSummaryDelta so the retry summary only subtracts
+// removals the parent still counts.
+type removedFilePresence struct {
+       deleteFiles map[string]struct{} // pos/eq delete FilePath()
+       dvRefs      map[string]struct{} // deletion-vector ReferencedDataFile()
+}
+
+func (p *removedFilePresence) counts(df iceberg.DataFile) bool {
+       if isDeletionVector(df) {
+               ref := df.ReferencedDataFile()
+               if ref == nil {
+                       return false
+               }
+               _, ok := p.dvRefs[*ref]
+
+               return ok
        }
+       _, ok := p.deleteFiles[df.FilePath()]
 
-       parent, err := sp.txn.meta.SnapshotByID(sp.parentSnapshotID)
-       if err != nil {
-               return nil, fmt.Errorf("computeOwnManifests: lookup parent 
snapshot %d: %w", sp.parentSnapshotID, err)
+       return ok
+}
+
+// checkRemovedFiles scans parent once and (1) fails terminally if any data 
file
+// this producer intends to rewrite is no longer live — a concurrent writer
+// already removed it, so proceeding would leave our replacement beside the
+// concurrent result and duplicate rows (Java's failMissingDeletePaths) — and
+// (2) returns which removed delete files / deletion vectors are still present 
so
+// the retry summary does not double-subtract a peer's prior removal.
+func (sp *snapshotProducer) checkRemovedFiles(parent *Snapshot) 
(*removedFilePresence, error) {

Review Comment:
   `rebuildFn` calls this unconditionally on every retry, including for 
`fastAppendFiles`/`mergeAppendFiles`, which never populate any of the 
removed-file maps. For those producers we now read `parent.Manifests` and 
iterate every entry on each attempt just to conclude there's nothing to check.
   
   That's a scaling regression precisely where it hurts — under append 
contention the retry loop turns into repeated full-table manifest scans, where 
the old path only re-read the parent's manifest list. Quick fix, short-circuit 
at the top before touching `parent.Manifests`:
   
   ```go
   if len(sp.deletedFiles) == 0 && len(sp.deletedDeleteFiles) == 0 && 
len(sp.deletedDVsByRef) == 0 {
       return present, nil
   }
   ```
   
   Longer term it'd be nice to share one parent scan across 
`checkRemovedFiles`, `deletedEntries`, and `existingManifests` — an overwrite 
retry currently walks the same entries up to three times — but the early-out is 
the part I'd want for this PR.



##########
table/snapshot_producers.go:
##########
@@ -812,125 +886,207 @@ func (sp *snapshotProducer) writeAddedManifest(content 
iceberg.ManifestContent,
 }
 
 func (sp *snapshotProducer) summary(props iceberg.Properties) (Summary, error) 
{
+       delta, err := sp.accumulateSummaryDelta(nil)
+       if err != nil {
+               return Summary{}, err
+       }
+
+       var previousSnapshot *Snapshot
+       if sp.parentSnapshotID > 0 {
+               previousSnapshot, err = 
sp.txn.meta.SnapshotByID(sp.parentSnapshotID)
+               if err != nil {
+                       return Summary{}, fmt.Errorf("summary: lookup parent 
snapshot %d: %w", sp.parentSnapshotID, err)
+               }
+       }
+
+       var previousSummary iceberg.Properties
+       if previousSnapshot != nil && previousSnapshot.Summary != nil {
+               previousSummary = previousSnapshot.Summary.Properties
+       }
+
+       return sp.rebaseSummary(delta, previousSummary, props)
+}
+
+// summaryOnRetry recomputes the snapshot summary against the fresh parent for 
an
+// OCC retry. Additions are always counted; a removal of a delete file / 
deletion
+// vector is counted only when it is still present on parent (present.counts),
+// because a peer may have already dropped it — replaying the captured removal
+// delta on top of a fresh parent that no longer counts the file undercounts
+// total-delete-files. Removed data files are always counted: a successful 
retry
+// has already asserted they are present (assertRemovedFilesPresent).
+func (sp *snapshotProducer) summaryOnRetry(props iceberg.Properties, parent 
*Snapshot, present *removedFilePresence) (Summary, error) {
+       delta, err := sp.accumulateSummaryDelta(present.counts)
+       if err != nil {
+               return Summary{}, err
+       }
+
+       var previousSummary iceberg.Properties
+       if parent != nil && parent.Summary != nil {
+               previousSummary = parent.Summary.Properties
+       }
+
+       return sp.rebaseSummary(delta, previousSummary, props)
+}
+
+// accumulateSummaryDelta builds the added/removed file delta for this 
producer.
+// countDeleteRemoval, when non-nil, gates removal of delete files / deletion
+// vectors (returning false skips that removal); data-file removals are always
+// counted. nil counts every removal (the initial commit).
+func (sp *snapshotProducer) accumulateSummaryDelta(countDeleteRemoval 
func(iceberg.DataFile) bool) (iceberg.Properties, error) {
        var ssc SnapshotSummaryCollector
        partitionSummaryLimit := sp.txn.meta.props.
                GetInt(WritePartitionSummaryLimitKey, 
WritePartitionSummaryLimitDefault)
        ssc.setPartitionSummaryLimit(partitionSummaryLimit)
 
-       var err error
        currentSchema := sp.txn.meta.CurrentSchema()
        partitionSpec, err := sp.txn.meta.CurrentSpec()
        if err != nil || partitionSpec == nil {
-               return Summary{}, fmt.Errorf("could not get current partition 
spec: %w", err)
+               return nil, fmt.Errorf("could not get current partition spec: 
%w", err)
        }
        for _, df := range sp.addedFiles {
                if err = ssc.addFile(df, currentSchema, 
sp.spec(int(df.SpecID()))); err != nil {
-                       return Summary{}, err
+                       return nil, err
                }
        }
        for _, df := range sp.addedDeleteFiles {
                if err = ssc.addFile(df, currentSchema, 
sp.spec(int(df.SpecID()))); err != nil {
-                       return Summary{}, err
+                       return nil, err
                }
        }
 
-       if len(sp.deletedFiles) > 0 {
-               specs := sp.txn.meta.specs
-               for _, df := range sp.deletedFiles {
-                       if err = ssc.removeFile(df, currentSchema, 
specs[df.SpecID()]); err != nil {
-                               return Summary{}, err
-                       }
+       specs := sp.txn.meta.specs
+       for _, df := range sp.deletedFiles {
+               if err = ssc.removeFile(df, currentSchema, specs[df.SpecID()]); 
err != nil {
+                       return nil, err
                }
        }
-
-       if len(sp.deletedDeleteFiles) > 0 {
-               specs := sp.txn.meta.specs
-               for _, df := range sp.deletedDeleteFiles {
-                       if err = ssc.removeFile(df, currentSchema, 
specs[df.SpecID()]); err != nil {
-                               return Summary{}, err
-                       }
+       for _, df := range sp.deletedDeleteFiles {
+               if countDeleteRemoval != nil && !countDeleteRemoval(df) {
+                       continue
                }
-       }
-
-       if len(sp.deletedDVsByRef) > 0 {
-               specs := sp.txn.meta.specs
-               for _, df := range sp.deletedDVsByRef {
-                       if err = ssc.removeFile(df, currentSchema, 
specs[df.SpecID()]); err != nil {
-                               return Summary{}, err
-                       }
+               if err = ssc.removeFile(df, currentSchema, specs[df.SpecID()]); 
err != nil {
+                       return nil, err
                }
        }
-
-       var previousSnapshot *Snapshot
-       if sp.parentSnapshotID > 0 {
-               previousSnapshot, err = 
sp.txn.meta.SnapshotByID(sp.parentSnapshotID)
-               if err != nil {
-                       return Summary{}, fmt.Errorf("summary: lookup parent 
snapshot %d: %w", sp.parentSnapshotID, err)
+       for _, df := range sp.deletedDVsByRef {
+               if countDeleteRemoval != nil && !countDeleteRemoval(df) {
+                       continue
+               }
+               if err = ssc.removeFile(df, currentSchema, specs[df.SpecID()]); 
err != nil {
+                       return nil, err
                }
        }
 
-       var previousSummary iceberg.Properties
-       if previousSnapshot != nil && previousSnapshot.Summary != nil {
-               previousSummary = previousSnapshot.Summary.Properties
-       }
+       return ssc.build(), nil
+}
 
-       summaryProps := ssc.build()
-       maps.Copy(summaryProps, props)
+func (sp *snapshotProducer) rebaseSummary(delta, previousSummary, props 
iceberg.Properties) (Summary, error) {
+       maps.Copy(delta, props)
 
        return updateSnapshotSummaries(Summary{
                Operation:  sp.op,
-               Properties: summaryProps,
+               Properties: delta,
        }, previousSummary)
 }
 
-// computeOwnManifests returns the subset of allManifests that were written
-// by this producer (i.e. not inherited from the parent snapshot). These are
-// preserved across OCC retry attempts when the manifest list is rebuilt
-// against a fresh parent.
-func (sp *snapshotProducer) computeOwnManifests(allManifests 
[]iceberg.ManifestFile) ([]iceberg.ManifestFile, error) {
-       if sp.parentSnapshotID <= 0 {
-               // No parent means all manifests are new — nothing to exclude.
-               return allManifests, nil
+// removedFilePresence records which of a producer's to-be-removed delete files
+// and deletion vectors are still live on the retry's fresh parent. counts is 
the
+// gate passed to accumulateSummaryDelta so the retry summary only subtracts
+// removals the parent still counts.
+type removedFilePresence struct {
+       deleteFiles map[string]struct{} // pos/eq delete FilePath()
+       dvRefs      map[string]struct{} // deletion-vector ReferencedDataFile()
+}
+
+func (p *removedFilePresence) counts(df iceberg.DataFile) bool {
+       if isDeletionVector(df) {
+               ref := df.ReferencedDataFile()
+               if ref == nil {
+                       return false
+               }
+               _, ok := p.dvRefs[*ref]
+
+               return ok
        }
+       _, ok := p.deleteFiles[df.FilePath()]
 
-       parent, err := sp.txn.meta.SnapshotByID(sp.parentSnapshotID)
-       if err != nil {
-               return nil, fmt.Errorf("computeOwnManifests: lookup parent 
snapshot %d: %w", sp.parentSnapshotID, err)
+       return ok
+}
+
+// checkRemovedFiles scans parent once and (1) fails terminally if any data 
file
+// this producer intends to rewrite is no longer live — a concurrent writer
+// already removed it, so proceeding would leave our replacement beside the
+// concurrent result and duplicate rows (Java's failMissingDeletePaths) — and
+// (2) returns which removed delete files / deletion vectors are still present 
so
+// the retry summary does not double-subtract a peer's prior removal.
+func (sp *snapshotProducer) checkRemovedFiles(parent *Snapshot) 
(*removedFilePresence, error) {
+       present := &removedFilePresence{
+               deleteFiles: map[string]struct{}{},
+               dvRefs:      map[string]struct{}{},
+       }
+
+       missingData := make(map[string]struct{}, len(sp.deletedFiles))
+       for path := range sp.deletedFiles {
+               missingData[path] = struct{}{}
        }
+
        if parent == nil {
-               return nil, fmt.Errorf("%w: computeOwnManifests parent id %d", 
ErrSnapshotNotFound, sp.parentSnapshotID)
+               return sp.missingDataError(present, missingData)
        }
 
-       parentManifests, err := parent.Manifests(sp.io)
+       manifests, err := parent.Manifests(sp.io)
        if err != nil {
-               return nil, fmt.Errorf("computeOwnManifests: read parent 
manifests: %w", err)
+               return nil, err
+       }
+       for _, m := range manifests {
+               for entry, err := range sp.iterManifestEntries(m, true) {
+                       if err != nil {
+                               return nil, err
+                       }
+                       df := entry.DataFile()
+                       if m.ManifestContent() == iceberg.ManifestContentData {
+                               delete(missingData, df.FilePath())
+
+                               continue
+                       }
+                       if ref := df.ReferencedDataFile(); isDeletionVector(df) 
&& ref != nil {

Review Comment:
   Two things on this branch. A deletion vector with a nil 
`ReferencedDataFile()` falls into the `else` and gets matched against 
`deletedDeleteFiles` by path — the wrong map for a DV. Low-probability (a v3 DV 
should always carry a ref) but it silently misclassifies instead of erroring; 
I'd split `isDeletionVector(df) && ref == nil` into its own case that skips or 
errors.
   
   The other is a question on the matching itself: presence is keyed off the 
referenced data file, not the DV's own path. If a peer superseded the same data 
file's DV with a new one (same ref, different path), we'd see "some DV for this 
ref still exists" and report our stale DV as present — which `summaryOnRetry` 
would then count as a live removal for a DV that's no longer physically in the 
manifest. I couldn't fully trace whether an isolation validator rejects this 
first. If it doesn't, keying presence off the exact DV path (falling back to 
ref only for the summary decision) plus a concurrent-DV-replacement test would 
settle it. wdyt?



##########
table/snapshot_producers.go:
##########
@@ -812,125 +886,207 @@ func (sp *snapshotProducer) writeAddedManifest(content 
iceberg.ManifestContent,
 }
 
 func (sp *snapshotProducer) summary(props iceberg.Properties) (Summary, error) 
{
+       delta, err := sp.accumulateSummaryDelta(nil)
+       if err != nil {
+               return Summary{}, err
+       }
+
+       var previousSnapshot *Snapshot
+       if sp.parentSnapshotID > 0 {
+               previousSnapshot, err = 
sp.txn.meta.SnapshotByID(sp.parentSnapshotID)
+               if err != nil {
+                       return Summary{}, fmt.Errorf("summary: lookup parent 
snapshot %d: %w", sp.parentSnapshotID, err)
+               }
+       }
+
+       var previousSummary iceberg.Properties
+       if previousSnapshot != nil && previousSnapshot.Summary != nil {
+               previousSummary = previousSnapshot.Summary.Properties
+       }
+
+       return sp.rebaseSummary(delta, previousSummary, props)
+}
+
+// summaryOnRetry recomputes the snapshot summary against the fresh parent for 
an
+// OCC retry. Additions are always counted; a removal of a delete file / 
deletion
+// vector is counted only when it is still present on parent (present.counts),
+// because a peer may have already dropped it — replaying the captured removal
+// delta on top of a fresh parent that no longer counts the file undercounts
+// total-delete-files. Removed data files are always counted: a successful 
retry
+// has already asserted they are present (assertRemovedFilesPresent).
+func (sp *snapshotProducer) summaryOnRetry(props iceberg.Properties, parent 
*Snapshot, present *removedFilePresence) (Summary, error) {
+       delta, err := sp.accumulateSummaryDelta(present.counts)
+       if err != nil {
+               return Summary{}, err
+       }
+
+       var previousSummary iceberg.Properties
+       if parent != nil && parent.Summary != nil {
+               previousSummary = parent.Summary.Properties
+       }
+
+       return sp.rebaseSummary(delta, previousSummary, props)
+}
+
+// accumulateSummaryDelta builds the added/removed file delta for this 
producer.
+// countDeleteRemoval, when non-nil, gates removal of delete files / deletion
+// vectors (returning false skips that removal); data-file removals are always
+// counted. nil counts every removal (the initial commit).
+func (sp *snapshotProducer) accumulateSummaryDelta(countDeleteRemoval 
func(iceberg.DataFile) bool) (iceberg.Properties, error) {
        var ssc SnapshotSummaryCollector
        partitionSummaryLimit := sp.txn.meta.props.
                GetInt(WritePartitionSummaryLimitKey, 
WritePartitionSummaryLimitDefault)
        ssc.setPartitionSummaryLimit(partitionSummaryLimit)
 
-       var err error
        currentSchema := sp.txn.meta.CurrentSchema()
        partitionSpec, err := sp.txn.meta.CurrentSpec()
        if err != nil || partitionSpec == nil {
-               return Summary{}, fmt.Errorf("could not get current partition 
spec: %w", err)
+               return nil, fmt.Errorf("could not get current partition spec: 
%w", err)
        }
        for _, df := range sp.addedFiles {
                if err = ssc.addFile(df, currentSchema, 
sp.spec(int(df.SpecID()))); err != nil {
-                       return Summary{}, err
+                       return nil, err
                }
        }
        for _, df := range sp.addedDeleteFiles {
                if err = ssc.addFile(df, currentSchema, 
sp.spec(int(df.SpecID()))); err != nil {
-                       return Summary{}, err
+                       return nil, err
                }
        }
 
-       if len(sp.deletedFiles) > 0 {
-               specs := sp.txn.meta.specs
-               for _, df := range sp.deletedFiles {
-                       if err = ssc.removeFile(df, currentSchema, 
specs[df.SpecID()]); err != nil {
-                               return Summary{}, err
-                       }
+       specs := sp.txn.meta.specs
+       for _, df := range sp.deletedFiles {
+               if err = ssc.removeFile(df, currentSchema, specs[df.SpecID()]); 
err != nil {
+                       return nil, err
                }
        }
-
-       if len(sp.deletedDeleteFiles) > 0 {
-               specs := sp.txn.meta.specs
-               for _, df := range sp.deletedDeleteFiles {
-                       if err = ssc.removeFile(df, currentSchema, 
specs[df.SpecID()]); err != nil {
-                               return Summary{}, err
-                       }
+       for _, df := range sp.deletedDeleteFiles {
+               if countDeleteRemoval != nil && !countDeleteRemoval(df) {
+                       continue
                }
-       }
-
-       if len(sp.deletedDVsByRef) > 0 {
-               specs := sp.txn.meta.specs
-               for _, df := range sp.deletedDVsByRef {
-                       if err = ssc.removeFile(df, currentSchema, 
specs[df.SpecID()]); err != nil {
-                               return Summary{}, err
-                       }
+               if err = ssc.removeFile(df, currentSchema, specs[df.SpecID()]); 
err != nil {
+                       return nil, err
                }
        }
-
-       var previousSnapshot *Snapshot
-       if sp.parentSnapshotID > 0 {
-               previousSnapshot, err = 
sp.txn.meta.SnapshotByID(sp.parentSnapshotID)
-               if err != nil {
-                       return Summary{}, fmt.Errorf("summary: lookup parent 
snapshot %d: %w", sp.parentSnapshotID, err)
+       for _, df := range sp.deletedDVsByRef {
+               if countDeleteRemoval != nil && !countDeleteRemoval(df) {
+                       continue
+               }
+               if err = ssc.removeFile(df, currentSchema, specs[df.SpecID()]); 
err != nil {
+                       return nil, err
                }
        }
 
-       var previousSummary iceberg.Properties
-       if previousSnapshot != nil && previousSnapshot.Summary != nil {
-               previousSummary = previousSnapshot.Summary.Properties
-       }
+       return ssc.build(), nil
+}
 
-       summaryProps := ssc.build()
-       maps.Copy(summaryProps, props)
+func (sp *snapshotProducer) rebaseSummary(delta, previousSummary, props 
iceberg.Properties) (Summary, error) {
+       maps.Copy(delta, props)
 
        return updateSnapshotSummaries(Summary{
                Operation:  sp.op,
-               Properties: summaryProps,
+               Properties: delta,
        }, previousSummary)
 }
 
-// computeOwnManifests returns the subset of allManifests that were written
-// by this producer (i.e. not inherited from the parent snapshot). These are
-// preserved across OCC retry attempts when the manifest list is rebuilt
-// against a fresh parent.
-func (sp *snapshotProducer) computeOwnManifests(allManifests 
[]iceberg.ManifestFile) ([]iceberg.ManifestFile, error) {
-       if sp.parentSnapshotID <= 0 {
-               // No parent means all manifests are new — nothing to exclude.
-               return allManifests, nil
+// removedFilePresence records which of a producer's to-be-removed delete files
+// and deletion vectors are still live on the retry's fresh parent. counts is 
the
+// gate passed to accumulateSummaryDelta so the retry summary only subtracts
+// removals the parent still counts.
+type removedFilePresence struct {
+       deleteFiles map[string]struct{} // pos/eq delete FilePath()
+       dvRefs      map[string]struct{} // deletion-vector ReferencedDataFile()
+}
+
+func (p *removedFilePresence) counts(df iceberg.DataFile) bool {
+       if isDeletionVector(df) {
+               ref := df.ReferencedDataFile()
+               if ref == nil {
+                       return false
+               }
+               _, ok := p.dvRefs[*ref]
+
+               return ok
        }
+       _, ok := p.deleteFiles[df.FilePath()]
 
-       parent, err := sp.txn.meta.SnapshotByID(sp.parentSnapshotID)
-       if err != nil {
-               return nil, fmt.Errorf("computeOwnManifests: lookup parent 
snapshot %d: %w", sp.parentSnapshotID, err)
+       return ok
+}
+
+// checkRemovedFiles scans parent once and (1) fails terminally if any data 
file
+// this producer intends to rewrite is no longer live — a concurrent writer
+// already removed it, so proceeding would leave our replacement beside the
+// concurrent result and duplicate rows (Java's failMissingDeletePaths) — and
+// (2) returns which removed delete files / deletion vectors are still present 
so
+// the retry summary does not double-subtract a peer's prior removal.
+func (sp *snapshotProducer) checkRemovedFiles(parent *Snapshot) 
(*removedFilePresence, error) {
+       present := &removedFilePresence{
+               deleteFiles: map[string]struct{}{},
+               dvRefs:      map[string]struct{}{},
+       }
+
+       missingData := make(map[string]struct{}, len(sp.deletedFiles))
+       for path := range sp.deletedFiles {
+               missingData[path] = struct{}{}
        }
+
        if parent == nil {
-               return nil, fmt.Errorf("%w: computeOwnManifests parent id %d", 
ErrSnapshotNotFound, sp.parentSnapshotID)
+               return sp.missingDataError(present, missingData)
        }
 
-       parentManifests, err := parent.Manifests(sp.io)
+       manifests, err := parent.Manifests(sp.io)
        if err != nil {
-               return nil, fmt.Errorf("computeOwnManifests: read parent 
manifests: %w", err)
+               return nil, err
+       }
+       for _, m := range manifests {
+               for entry, err := range sp.iterManifestEntries(m, true) {
+                       if err != nil {
+                               return nil, err
+                       }
+                       df := entry.DataFile()
+                       if m.ManifestContent() == iceberg.ManifestContentData {
+                               delete(missingData, df.FilePath())
+
+                               continue
+                       }
+                       if ref := df.ReferencedDataFile(); isDeletionVector(df) 
&& ref != nil {
+                               if _, want := sp.deletedDVsByRef[*ref]; want {
+                                       present.dvRefs[*ref] = struct{}{}
+                               }
+                       } else if _, want := 
sp.deletedDeleteFiles[df.FilePath()]; want {
+                               present.deleteFiles[df.FilePath()] = struct{}{}
+                       }
+               }
        }
 
-       inherited := make(map[string]bool, len(parentManifests))
-       for _, m := range parentManifests {
-               inherited[m.FilePath()] = true
+       return sp.missingDataError(present, missingData)
+}
+
+func (sp *snapshotProducer) missingDataError(present *removedFilePresence, 
missingData map[string]struct{}) (*removedFilePresence, error) {
+       if len(missingData) == 0 {
+               return present, nil
        }
 
-       own := make([]iceberg.ManifestFile, 0, len(allManifests))
-       for _, m := range allManifests {
-               if !inherited[m.FilePath()] {
-                       own = append(own, m)
-               }
+       missing := make([]string, 0, len(missingData))
+       for path := range missingData {
+               missing = append(missing, path)
        }
+       sort.Strings(missing)

Review Comment:
   We already import `slices` and target Go 1.25, so `slices.Sort(missing)` 
drops the `sort` import entirely. While we're here, `%v` on a `[]string` prints 
Go slice syntax (`[path1 path2]`) in a user-facing error — `strings.Join` or 
`%q` reads cleaner.



##########
table/snapshot_producers.go:
##########
@@ -812,125 +886,207 @@ func (sp *snapshotProducer) writeAddedManifest(content 
iceberg.ManifestContent,
 }
 
 func (sp *snapshotProducer) summary(props iceberg.Properties) (Summary, error) 
{
+       delta, err := sp.accumulateSummaryDelta(nil)
+       if err != nil {
+               return Summary{}, err
+       }
+
+       var previousSnapshot *Snapshot
+       if sp.parentSnapshotID > 0 {
+               previousSnapshot, err = 
sp.txn.meta.SnapshotByID(sp.parentSnapshotID)
+               if err != nil {
+                       return Summary{}, fmt.Errorf("summary: lookup parent 
snapshot %d: %w", sp.parentSnapshotID, err)
+               }
+       }
+
+       var previousSummary iceberg.Properties
+       if previousSnapshot != nil && previousSnapshot.Summary != nil {
+               previousSummary = previousSnapshot.Summary.Properties
+       }
+
+       return sp.rebaseSummary(delta, previousSummary, props)
+}
+
+// summaryOnRetry recomputes the snapshot summary against the fresh parent for 
an
+// OCC retry. Additions are always counted; a removal of a delete file / 
deletion
+// vector is counted only when it is still present on parent (present.counts),
+// because a peer may have already dropped it — replaying the captured removal
+// delta on top of a fresh parent that no longer counts the file undercounts
+// total-delete-files. Removed data files are always counted: a successful 
retry
+// has already asserted they are present (assertRemovedFilesPresent).

Review Comment:
   `assertRemovedFilesPresent` doesn't exist — the function that does this is 
`checkRemovedFiles`. Worth fixing so the comment doesn't send someone hunting 
for a symbol that isn't there.



##########
table/snapshot_producers.go:
##########
@@ -812,125 +886,207 @@ func (sp *snapshotProducer) writeAddedManifest(content 
iceberg.ManifestContent,
 }
 
 func (sp *snapshotProducer) summary(props iceberg.Properties) (Summary, error) 
{
+       delta, err := sp.accumulateSummaryDelta(nil)
+       if err != nil {
+               return Summary{}, err
+       }
+
+       var previousSnapshot *Snapshot
+       if sp.parentSnapshotID > 0 {
+               previousSnapshot, err = 
sp.txn.meta.SnapshotByID(sp.parentSnapshotID)
+               if err != nil {
+                       return Summary{}, fmt.Errorf("summary: lookup parent 
snapshot %d: %w", sp.parentSnapshotID, err)
+               }
+       }
+
+       var previousSummary iceberg.Properties
+       if previousSnapshot != nil && previousSnapshot.Summary != nil {
+               previousSummary = previousSnapshot.Summary.Properties
+       }
+
+       return sp.rebaseSummary(delta, previousSummary, props)
+}
+
+// summaryOnRetry recomputes the snapshot summary against the fresh parent for 
an
+// OCC retry. Additions are always counted; a removal of a delete file / 
deletion
+// vector is counted only when it is still present on parent (present.counts),
+// because a peer may have already dropped it — replaying the captured removal
+// delta on top of a fresh parent that no longer counts the file undercounts
+// total-delete-files. Removed data files are always counted: a successful 
retry
+// has already asserted they are present (assertRemovedFilesPresent).
+func (sp *snapshotProducer) summaryOnRetry(props iceberg.Properties, parent 
*Snapshot, present *removedFilePresence) (Summary, error) {
+       delta, err := sp.accumulateSummaryDelta(present.counts)
+       if err != nil {
+               return Summary{}, err
+       }
+
+       var previousSummary iceberg.Properties
+       if parent != nil && parent.Summary != nil {
+               previousSummary = parent.Summary.Properties
+       }
+
+       return sp.rebaseSummary(delta, previousSummary, props)
+}
+
+// accumulateSummaryDelta builds the added/removed file delta for this 
producer.
+// countDeleteRemoval, when non-nil, gates removal of delete files / deletion
+// vectors (returning false skips that removal); data-file removals are always
+// counted. nil counts every removal (the initial commit).
+func (sp *snapshotProducer) accumulateSummaryDelta(countDeleteRemoval 
func(iceberg.DataFile) bool) (iceberg.Properties, error) {
        var ssc SnapshotSummaryCollector
        partitionSummaryLimit := sp.txn.meta.props.
                GetInt(WritePartitionSummaryLimitKey, 
WritePartitionSummaryLimitDefault)
        ssc.setPartitionSummaryLimit(partitionSummaryLimit)
 
-       var err error
        currentSchema := sp.txn.meta.CurrentSchema()
        partitionSpec, err := sp.txn.meta.CurrentSpec()
        if err != nil || partitionSpec == nil {
-               return Summary{}, fmt.Errorf("could not get current partition 
spec: %w", err)
+               return nil, fmt.Errorf("could not get current partition spec: 
%w", err)
        }
        for _, df := range sp.addedFiles {
                if err = ssc.addFile(df, currentSchema, 
sp.spec(int(df.SpecID()))); err != nil {
-                       return Summary{}, err
+                       return nil, err
                }
        }
        for _, df := range sp.addedDeleteFiles {
                if err = ssc.addFile(df, currentSchema, 
sp.spec(int(df.SpecID()))); err != nil {
-                       return Summary{}, err
+                       return nil, err
                }
        }
 
-       if len(sp.deletedFiles) > 0 {
-               specs := sp.txn.meta.specs
-               for _, df := range sp.deletedFiles {
-                       if err = ssc.removeFile(df, currentSchema, 
specs[df.SpecID()]); err != nil {
-                               return Summary{}, err
-                       }
+       specs := sp.txn.meta.specs
+       for _, df := range sp.deletedFiles {
+               if err = ssc.removeFile(df, currentSchema, specs[df.SpecID()]); 
err != nil {
+                       return nil, err
                }
        }
-
-       if len(sp.deletedDeleteFiles) > 0 {
-               specs := sp.txn.meta.specs
-               for _, df := range sp.deletedDeleteFiles {
-                       if err = ssc.removeFile(df, currentSchema, 
specs[df.SpecID()]); err != nil {
-                               return Summary{}, err
-                       }
+       for _, df := range sp.deletedDeleteFiles {
+               if countDeleteRemoval != nil && !countDeleteRemoval(df) {
+                       continue
                }
-       }
-
-       if len(sp.deletedDVsByRef) > 0 {
-               specs := sp.txn.meta.specs
-               for _, df := range sp.deletedDVsByRef {
-                       if err = ssc.removeFile(df, currentSchema, 
specs[df.SpecID()]); err != nil {
-                               return Summary{}, err
-                       }
+               if err = ssc.removeFile(df, currentSchema, specs[df.SpecID()]); 
err != nil {
+                       return nil, err
                }
        }
-
-       var previousSnapshot *Snapshot
-       if sp.parentSnapshotID > 0 {
-               previousSnapshot, err = 
sp.txn.meta.SnapshotByID(sp.parentSnapshotID)
-               if err != nil {
-                       return Summary{}, fmt.Errorf("summary: lookup parent 
snapshot %d: %w", sp.parentSnapshotID, err)
+       for _, df := range sp.deletedDVsByRef {
+               if countDeleteRemoval != nil && !countDeleteRemoval(df) {
+                       continue
+               }
+               if err = ssc.removeFile(df, currentSchema, specs[df.SpecID()]); 
err != nil {
+                       return nil, err
                }
        }
 
-       var previousSummary iceberg.Properties
-       if previousSnapshot != nil && previousSnapshot.Summary != nil {
-               previousSummary = previousSnapshot.Summary.Properties
-       }
+       return ssc.build(), nil
+}
 
-       summaryProps := ssc.build()
-       maps.Copy(summaryProps, props)
+func (sp *snapshotProducer) rebaseSummary(delta, previousSummary, props 
iceberg.Properties) (Summary, error) {
+       maps.Copy(delta, props)
 
        return updateSnapshotSummaries(Summary{
                Operation:  sp.op,
-               Properties: summaryProps,
+               Properties: delta,
        }, previousSummary)
 }
 
-// computeOwnManifests returns the subset of allManifests that were written
-// by this producer (i.e. not inherited from the parent snapshot). These are
-// preserved across OCC retry attempts when the manifest list is rebuilt
-// against a fresh parent.
-func (sp *snapshotProducer) computeOwnManifests(allManifests 
[]iceberg.ManifestFile) ([]iceberg.ManifestFile, error) {
-       if sp.parentSnapshotID <= 0 {
-               // No parent means all manifests are new — nothing to exclude.
-               return allManifests, nil
+// removedFilePresence records which of a producer's to-be-removed delete files
+// and deletion vectors are still live on the retry's fresh parent. counts is 
the
+// gate passed to accumulateSummaryDelta so the retry summary only subtracts
+// removals the parent still counts.
+type removedFilePresence struct {
+       deleteFiles map[string]struct{} // pos/eq delete FilePath()
+       dvRefs      map[string]struct{} // deletion-vector ReferencedDataFile()
+}
+
+func (p *removedFilePresence) counts(df iceberg.DataFile) bool {
+       if isDeletionVector(df) {
+               ref := df.ReferencedDataFile()
+               if ref == nil {
+                       return false
+               }
+               _, ok := p.dvRefs[*ref]
+
+               return ok
        }
+       _, ok := p.deleteFiles[df.FilePath()]
 
-       parent, err := sp.txn.meta.SnapshotByID(sp.parentSnapshotID)
-       if err != nil {
-               return nil, fmt.Errorf("computeOwnManifests: lookup parent 
snapshot %d: %w", sp.parentSnapshotID, err)
+       return ok
+}
+
+// checkRemovedFiles scans parent once and (1) fails terminally if any data 
file
+// this producer intends to rewrite is no longer live — a concurrent writer
+// already removed it, so proceeding would leave our replacement beside the
+// concurrent result and duplicate rows (Java's failMissingDeletePaths) — and
+// (2) returns which removed delete files / deletion vectors are still present 
so
+// the retry summary does not double-subtract a peer's prior removal.
+func (sp *snapshotProducer) checkRemovedFiles(parent *Snapshot) 
(*removedFilePresence, error) {
+       present := &removedFilePresence{
+               deleteFiles: map[string]struct{}{},
+               dvRefs:      map[string]struct{}{},
+       }
+
+       missingData := make(map[string]struct{}, len(sp.deletedFiles))
+       for path := range sp.deletedFiles {
+               missingData[path] = struct{}{}
        }
+
        if parent == nil {
-               return nil, fmt.Errorf("%w: computeOwnManifests parent id %d", 
ErrSnapshotNotFound, sp.parentSnapshotID)
+               return sp.missingDataError(present, missingData)
        }
 
-       parentManifests, err := parent.Manifests(sp.io)
+       manifests, err := parent.Manifests(sp.io)
        if err != nil {
-               return nil, fmt.Errorf("computeOwnManifests: read parent 
manifests: %w", err)
+               return nil, err
+       }
+       for _, m := range manifests {
+               for entry, err := range sp.iterManifestEntries(m, true) {
+                       if err != nil {
+                               return nil, err
+                       }
+                       df := entry.DataFile()
+                       if m.ManifestContent() == iceberg.ManifestContentData {
+                               delete(missingData, df.FilePath())
+
+                               continue
+                       }
+                       if ref := df.ReferencedDataFile(); isDeletionVector(df) 
&& ref != nil {
+                               if _, want := sp.deletedDVsByRef[*ref]; want {
+                                       present.dvRefs[*ref] = struct{}{}
+                               }
+                       } else if _, want := 
sp.deletedDeleteFiles[df.FilePath()]; want {
+                               present.deleteFiles[df.FilePath()] = struct{}{}
+                       }
+               }
        }
 
-       inherited := make(map[string]bool, len(parentManifests))
-       for _, m := range parentManifests {
-               inherited[m.FilePath()] = true
+       return sp.missingDataError(present, missingData)
+}
+
+func (sp *snapshotProducer) missingDataError(present *removedFilePresence, 
missingData map[string]struct{}) (*removedFilePresence, error) {
+       if len(missingData) == 0 {
+               return present, nil
        }
 
-       own := make([]iceberg.ManifestFile, 0, len(allManifests))
-       for _, m := range allManifests {
-               if !inherited[m.FilePath()] {
-                       own = append(own, m)
-               }
+       missing := make([]string, 0, len(missingData))
+       for path := range missingData {
+               missing = append(missing, path)
        }
+       sort.Strings(missing)
 
-       return own, nil
+       return nil, fmt.Errorf("%w: %d data file(s) to rewrite are no longer on 
the branch head, e.g. %v",
+               ErrCommitDiverged, len(missing), missing[:min(len(missing), 5)])

Review Comment:
   This abort — `ErrCommitDiverged` when a rewrite target is already gone from 
head — is the one behavior in this PR that changes what callers see. Previously 
they got silent resurrection; now they get a terminal, non-retryable error so 
they can re-plan. That's the right call and matches Java's 
`failMissingDeletePaths`, but nothing exercises it — 
`TestSummaryOnRetry_Skips/SubtractsPresentDeleteFile` cover the delete-file 
presence gating, not the `missingDataError` branch.
   
   A unit test calling `checkRemovedFiles` with a parent missing one of 
`sp.deletedFiles` and asserting `errors.Is(err, ErrCommitDiverged)` would lock 
this down. An end-to-end scenario where the peer removes the exact file writer 
A is replacing, asserting the commit fails diverged rather than resurrecting, 
would be even better.



##########
table/occ_scenario_test.go:
##########
@@ -451,6 +451,129 @@ func (s *OCCScenarioTestSuite) 
TestV3MergeAppendRowLineageAfterOCCRetry() {
        }
 }
 
+// TestReplaceFilesNoResurrectionAfterConflict is a regression test for the OCC
+// retry-replay resurrection bug. When a replace/overwrite commit retries after
+// a conflict, the rebuild must re-apply the file removal against the FRESH
+// parent — not graft attempt-0's stale "own" manifests onto a fresh parent
+// that still lists the removed files. Otherwise the removed files come back
+// alive next to their replacement and their rows are duplicated.
+//
+// Scenario:
+//   - Base: file0 = rows [1,2,3] committed.
+//   - Concurrent peer: appends filePeer = row [4]; catalog head is file0+peer.
+//   - Writer A (stale base = only file0) replaces file0 with a compacted file
+//     holding the same rows [1,2,3]. First attempt conflicts; the retry 
rebases
+//     onto the fresh head.
+//
+// Correct: live rows = {1,2,3,4} (compacted + peer), file0 dropped → 4 rows.
+// Buggy:   file0 resurrected → rows [1,2,3] duplicated → 7 rows, 3 live files.
+func (s *OCCScenarioTestSuite) TestReplaceFilesNoResurrectionAfterConflict() {
+       props := iceberg.Properties{
+               table.CommitMinRetryWaitMsKey:      "0",
+               table.CommitMaxRetryWaitMsKey:      "0",
+               table.CommitTotalRetryTimeoutMsKey: "60000",
+               table.CommitNumRetriesKey:          "2",
+               // Snapshot isolation lets the replace commit past the 
concurrent
+               // append the same way compaction's rewrite semantics do; the 
bug we
+               // are guarding lives in the retry rebuild, not the validator.
+               table.WriteUpdateIsolationLevelKey: 
string(table.IsolationSnapshot),
+       }
+
+       localFS := func(_ context.Context) (iceio.IO, error) { return 
iceio.LocalFS{}, nil }
+
+       // Step 1: base table with file0 = rows [1,2,3].
+       baseTbl, cat := s.makeTable(0, props)
+       arrowSc, err := table.SchemaToArrowSchema(baseTbl.Schema(), nil, false, 
false)
+       s.Require().NoError(err)
+
+       file0Path := s.location + "/data/file0.parquet"
+       writeParquetFile(s.T(), file0Path, arrowSc,
+               `[{"id":1,"val":"a"},{"id":2,"val":"b"},{"id":3,"val":"c"}]`)
+
+       tx := baseTbl.NewTransaction()
+       s.Require().NoError(tx.AddFiles(s.ctx, []string{file0Path}, nil, false))
+       _, err = tx.Commit(s.ctx)
+       s.Require().NoError(err)
+       metaBase := cat.current
+
+       // Step 2: a concurrent peer appends filePeer = row [4] onto metaBase.
+       peerCat := &occScenarioCatalog{current: metaBase, location: s.location}
+       peerTbl := table.New(baseTbl.Identifier(), metaBase,
+               s.location+"/metadata/base.metadata.json", localFS, peerCat)
+
+       peerPath := s.location + "/data/file_peer.parquet"
+       writeParquetFile(s.T(), peerPath, arrowSc, `[{"id":4,"val":"d"}]`)
+
+       ptx := peerTbl.NewTransaction()
+       s.Require().NoError(ptx.AddFiles(s.ctx, []string{peerPath}, nil, false))
+       _, err = ptx.Commit(s.ctx)
+       s.Require().NoError(err)
+       metaPeer := peerCat.current
+
+       // Step 3: Writer A replaces file0 -> compacted, starting from the stale
+       // metaBase; its catalog is at metaPeer and returns one 409 before 
success.
+       catA := &occScenarioCatalog{current: metaPeer, conflictsLeft: 1, 
location: s.location}

Review Comment:
   Every OCC scenario here uses `conflictsLeft: 1`, so we only ever exercise a 
single rebuild. The reused-added-content path is most interesting across more 
than one retry — that's where orphaned-manifest churn and cumulative removal 
tracking would show up if the reuse logic were wrong.
   
   A `conflictsLeft: 2` variant (two distinct peer commits) asserting the same 
no-resurrection invariant would lock in retry idempotency, which is the 
property the added-content reuse is really claiming. Nice-to-have, not blocking.



##########
table/snapshot_producers.go:
##########
@@ -1115,17 +1271,13 @@ func (sp *snapshotProducer) commit(ctx context.Context) 
(_ []Update, _ []Require
                        rebuilt.AddedRows = &addedRows
                }
 
-               // Recompute snapshot summary against the fresh parent so that 
totals
-               // (total-records, total-data-files, total-files-size) are not 
regressed
-               // to the stale values captured at attempt 0. The per-operation 
delta
-               // (added-data-files, added-records, etc.) is preserved in
-               // capturedSnapshot.Summary and is replayed on top of the fresh 
base.
+               // Recompute the snapshot summary against the fresh parent so 
totals are
+               // not regressed to the stale attempt-0 values. Removals of 
delete files
+               // / DVs are gated by present (see checkRemovedFiles) so a 
peer's prior
+               // removal is not subtracted twice, which would undercount
+               // total-delete-files.
                if freshParent != nil && freshParent.Summary != nil && 
capturedSnapshot.Summary != nil {
-                       deltaSummary := Summary{
-                               Operation:  capturedSnapshot.Summary.Operation,
-                               Properties: 
maps.Clone(capturedSnapshot.Summary.Properties),
-                       }
-                       if s, sumErr := updateSnapshotSummaries(deltaSummary, 
freshParent.Summary.Properties); sumErr == nil {
+                       if s, sumErr := sp.summaryOnRetry(sp.snapshotProps, 
freshParent, present); sumErr == nil {

Review Comment:
   If `summaryOnRetry` errors, we silently keep the stale attempt-0 summary — 
`sumErr` is discarded. The old `updateSnapshotSummaries` path swallowed its 
error too so this isn't a regression, but this PR reworks exactly this code and 
it's a natural spot to fix. I'd return the error from `rebuildFn` so the commit 
loop surfaces it rather than committing a summary we know is wrong.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to