diff --git a/core/services/nodes/registry.go b/core/services/nodes/registry.go index d6fd59662..1e5d52db0 100644 --- a/core/services/nodes/registry.go +++ b/core/services/nodes/registry.go @@ -1159,11 +1159,28 @@ func requireCurrentRevision(tx *gorm.DB, modelName, revision string) error { return err } if state.ConfigRevision != revision { - return ErrStaleModelConfigRevision + // Name both sides. "stale model config revision" on its own says only + // that two hashes differ, which leaves an operator no way to tell an + // edited configuration from a revision that is not reproducible for one + // unchanged file. + return fmt.Errorf("%w (request carries %s, controller holds %s)", + ErrStaleModelConfigRevision, shortRevision(revision), shortRevision(state.ConfigRevision)) } return nil } +// shortRevision trims a revision for log and error output. The full value is a +// sha256 hex digest; the leading bytes identify it well enough to compare two. +func shortRevision(revision string) string { + if revision == "" { + return "(none)" + } + if len(revision) > 12 { + return revision[:12] + } + return revision +} + func validateRevisionWrite(modelName, revision string, revisionRequired bool) error { if modelName == "" { return fmt.Errorf("model name is required") diff --git a/core/services/nodes/revision_error_detail_test.go b/core/services/nodes/revision_error_detail_test.go new file mode 100644 index 000000000..25be6866a --- /dev/null +++ b/core/services/nodes/revision_error_detail_test.go @@ -0,0 +1,24 @@ +package nodes + +import ( + "errors" + "fmt" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Stale revision error detail", func() { + It("keeps errors.Is matching so callers can still classify it", func() { + err := fmt.Errorf("%w (request carries %s, controller holds %s)", + ErrStaleModelConfigRevision, shortRevision("aaaabbbbccccdddd"), shortRevision("1111222233334444")) + Expect(errors.Is(err, ErrStaleModelConfigRevision)).To(BeTrue()) + Expect(err.Error()).To(ContainSubstring("stale model config revision")) + }) + + It("names both revisions so an operator can tell which side moved", func() { + Expect(shortRevision("aaaabbbbccccdddd")).To(Equal("aaaabbbbcccc")) + Expect(shortRevision("short")).To(Equal("short")) + Expect(shortRevision("")).To(Equal("(none)")) + }) +})