@@ 421,7 421,5 @@ modernc.org/sqlite v1.38.2 h1:Aclu7+tgjgcQVShZqim41Bbw9Cho0y/7WzYptXqkEek=
modernc.org/sqlite v1.38.2/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E=
sourcecraft.dev/bigbes/sr-ht-core v0.0.0-20260718185800-dd418a200152 h1:9kQC+tDO2CO8avlKadb9Z0if4a6vJuEK80+4zcb6/fU=
sourcecraft.dev/bigbes/sr-ht-core v0.0.0-20260718185800-dd418a200152/go.mod h1:Mu1Vx39ws/OTKWGoVERXvkdRSPLBdhuFTYv0ftVV31c=
-sourcecraft.dev/bigbes/sr-ht-ecore v0.0.0-20260808192241-9377c43a02ca h1:LCfxvF1VJl7djl7noAeXObfuY1csJr2OOFExwU4Y/N4=
-sourcecraft.dev/bigbes/sr-ht-ecore v0.0.0-20260808192241-9377c43a02ca/go.mod h1:KeoZjm+/nnsdtc1WxB7X/0EeC+Rggt2OkwJDEc6XWnw=
sourcecraft.dev/bigbes/sr-ht-ecore v0.0.0-20260808194355-f019dbe4ea3e h1:9yH4eagCWQMdFFbF+LQmvJjXWFdSDijhaxolrwfgatA=
sourcecraft.dev/bigbes/sr-ht-ecore v0.0.0-20260808194355-f019dbe4ea3e/go.mod h1:KeoZjm+/nnsdtc1WxB7X/0EeC+Rggt2OkwJDEc6XWnw=
@@ 5,6 5,8 @@ import (
"errors"
"fmt"
+ "go.bigb.es/auxilia/culpa"
+
"sourcecraft.dev/bigbes/sr-ht-spec/core"
"sourcecraft.dev/bigbes/sr-ht-spec/db"
"sourcecraft.dev/bigbes/sr-ht-spec/gitx"
@@ 143,8 145,15 @@ func (s *Service) mergeProposal(ctx context.Context, sp *Space, row *db.Proposal
// reconciler repairs (RepairMarkMerged). Surface it rather than
// reporting a failed merge — the merge commit is on the approved branch
// and reads already see it.
- return Proposal{}, fmt.Errorf("service: proposal %d merged to %s in %s but its row could not be updated "+
- "(the reconciler will repair it): %w", row.ID, short(mergedRev), sp.Ref, err)
+ //
+ // The hint rides on the error rather than sitting in this sentence
+ // because the sentence is one wrap away from being buried under
+ // another, while a culpa detail survives every wrap above it and comes
+ // out as its own field wherever this is finally logged.
+ return Proposal{}, culpa.WithHint(
+ culpa.Wrapf(err, "service: proposal %d merged to %s in %s but its row could not be updated",
+ row.ID, short(mergedRev), sp.Ref),
+ "the merge is on the approved branch; the reconciler repairs the row (RepairMarkMerged)")
}
// mergeProposal is the single merge point — both the public Merge and
@@ 195,7 204,15 @@ func mergeErr(err error, ref core.SpaceRef, proposalID int) error {
if errors.As(err, &stale) {
return fmt.Errorf("%w: proposal %d against %s: %w", ErrStale, proposalID, ref, err)
}
- return fmt.Errorf("service: merge proposal %d in %s: %w", proposalID, ref, err)
+ // Everything that is not staleness is a git or database failure: the
+ // surfaces answer it as a 500, and nobody but an operator ever reads it.
+ // culpa.Wrapf and not fmt.Errorf because this is the *outermost* wrap, which
+ // is the one the log sees — it carries a stacktrace and scribe.Err expands
+ // it. The message chain says "merge proposal 7"; the stack says which of the
+ // merge's dozen git calls produced it, which is the question a 500 here
+ // actually raises. errors.Is and errors.As still traverse it, so the
+ // sentinel mapping above and in the surfaces is unaffected.
+ return culpa.Wrapf(err, "service: merge proposal %d in %s", proposalID, ref)
}
// resolveProposalErr maps a db resolution failure (reject) onto this package's
@@ 208,6 225,8 @@ func resolveProposalErr(err error, proposalID int) error {
case errors.Is(err, db.ErrProposalNotOpen):
return fmt.Errorf("%w: proposal %d", ErrProposalNotOpen, proposalID)
default:
- return fmt.Errorf("service: reject proposal %d: %w", proposalID, err)
+ // A database failure, for the reason mergeErr gives: outermost wrap,
+ // operator-only, so it carries a stack.
+ return culpa.Wrapf(err, "service: reject proposal %d", proposalID)
}
}