From 8fa11137e734d3a10baa92082028f0033db0f731 Mon Sep 17 00:00:00 2001 From: Devesh Bhardwaj Date: Mon, 10 Aug 2026 00:54:45 +0530 Subject: [PATCH 1/2] SK-2963: warn at startup when a beta/dev build targets a Production vault Adds a WARN-level log, emitted once in NewSkyflow(), when the SDK's own version is non-GA (any -beta.N or -dev. suffix) and at least one configured vault resolves to common.PROD (its zero value / default). Also fixes the prerequisite this ticket calls out: constants.SDK_VERSION is a manually maintained literal that has never been wired to CI and isn't bumped per release, so it can't be trusted alone to detect a beta build. Added helpers.CurrentSDKVersion(), which reads the real version an importing consumer actually resolved via runtime/debug.ReadBuildInfo() (e.g. 'v2.1.0-beta.1'), falling back to constants.SDK_VERSION only when build info isn't meaningful (go test against this module's own source). - IsNonGaVersion() / AnyVaultIsProd() / CurrentSDKVersion() in internal/helpers: small, independently unit-tested functions. - New logs.BETA_BUILD_WARNING message. - client.go wires the check into NewSkyflow(), right before the existing CLIENT_INITIALIZED log; goes through logger.Warn so it respects the configured log level like every other warning in this SDK. Verified with a user-local Go 1.25.12 toolchain (none was preinstalled): go build/vet clean, gofmt clean on all touched code, and go test shows zero regressions - the same 1 (client) + 10 (internal/helpers) pre-existing failures reproduce identically on unmodified main (confirmed via git stash), all unrelated to this change (file-path/network-dependent bearer-token and file-upload tests). Reference implementation: skyflow-java (see that repo's devesh/SK-2963). Cross-SDK design tracked in SK-2963. Co-Authored-By: Claude Sonnet 5 --- v2/client/client.go | 12 +++++++ v2/client/client_test.go | 33 ++++++++++++++++++ v2/internal/helpers/helpers.go | 53 ++++++++++++++++++++++++++++ v2/internal/helpers/helpers_test.go | 54 +++++++++++++++++++++++++++++ v2/utils/messages/info_logs.go | 2 ++ 5 files changed, 154 insertions(+) diff --git a/v2/client/client.go b/v2/client/client.go index 627ba25..e7669e9 100644 --- a/v2/client/client.go +++ b/v2/client/client.go @@ -3,6 +3,7 @@ package client import ( "fmt" + "github.com/skyflowapi/skyflow-go/v2/internal/helpers" "github.com/skyflowapi/skyflow-go/v2/internal/validation" "github.com/skyflowapi/skyflow-go/v2/internal/vault/controller" vaultutils "github.com/skyflowapi/skyflow-go/v2/utils/common" @@ -37,6 +38,17 @@ func NewSkyflow(opts ...Option) (*Skyflow, *error.SkyflowError) { } } + sdkVersion := helpers.CurrentSDKVersion() + if helpers.IsNonGaVersion(sdkVersion) { + var vaultConfigs []vaultutils.VaultConfig + for _, svc := range client.vaultServices { + vaultConfigs = append(vaultConfigs, *svc.config) + } + if helpers.AnyVaultIsProd(vaultConfigs) { + logger.Warn(fmt.Sprintf(logs.BETA_BUILD_WARNING, sdkVersion)) + } + } + logger.Info(logs.CLIENT_INITIALIZED) return client, nil } diff --git a/v2/client/client_test.go b/v2/client/client_test.go index 8bb63c8..99e168a 100644 --- a/v2/client/client_test.go +++ b/v2/client/client_test.go @@ -1,6 +1,7 @@ package client import ( + "bytes" "fmt" "os" "testing" @@ -2166,3 +2167,35 @@ var _ = Describe("Skyflow lifecycle: after Vault/Detect/Connection activated", f }) }) }) + +// SK-2963: beta-build-in-prod warning. +// +// helpers.CurrentSDKVersion() falls back to the real, GA constants.SDK_VERSION whenever +// go test's own build info isn't a resolved module version (see helpers_test.go's +// "CurrentSDKVersion" spec) - so unlike a real beta-tagged consumer, NewSkyflow() in this +// suite can never actually take the "non-GA" branch. The logic that decides *whether* to +// warn (IsNonGaVersion, AnyVaultIsProd) is covered directly in helpers_test.go; this only +// proves the real wiring stays silent against today's real GA build, which is what every +// test run actually exercises. +var _ = Describe("Beta build warning wiring", func() { + var buf bytes.Buffer + + BeforeEach(func() { + buf.Reset() + logger.SetOutput(&buf) + logger.SetLogLevel(logger.WARN) + }) + AfterEach(func() { + logger.SetOutput(os.Stderr) + logger.SetLogLevel(logger.ERROR) + }) + + It("does not warn for the real GA build even against a PROD vault", func() { + _, err := NewSkyflow( + WithLogLevel(logger.WARN), + WithVaults(common.VaultConfig{VaultId: "v1", ClusterId: "cluster1", Env: common.PROD}), + ) + Expect(err).To(BeNil()) + Expect(buf.String()).ToNot(ContainSubstring("beta/pre-release build")) + }) +}) diff --git a/v2/internal/helpers/helpers.go b/v2/internal/helpers/helpers.go index a392ef4..685b08d 100644 --- a/v2/internal/helpers/helpers.go +++ b/v2/internal/helpers/helpers.go @@ -16,6 +16,7 @@ import ( "os" "regexp" "runtime" + "runtime/debug" "time" "github.com/skyflowapi/skyflow-go/v2/internal/generated/core" @@ -314,6 +315,58 @@ func GetURLWithEnv(env common.Env, clusterId string) string { return url } +// gaVersionPattern matches a clean public release (e.g. "v2.1.0"); anything else - +// "v2.1.0-beta.1", "v2.1.0-dev.abc1234" - is a non-GA build. +var gaVersionPattern = regexp.MustCompile(`^v?\d+\.\d+\.\d+$`) + +// IsNonGaVersion reports whether version is a beta/dev pre-release build rather than a +// plain public release. +func IsNonGaVersion(version string) bool { + return !gaVersionPattern.MatchString(version) +} + +// AnyVaultIsProd reports whether any of the given vault configs resolves to Env.PROD. +// PROD is Env's zero value (see utils/common.Env), so an unset Env already defaults to +// it, same as GetURLWithEnv's own default case above. +func AnyVaultIsProd(vaultConfigs []common.VaultConfig) bool { + for _, vaultConfig := range vaultConfigs { + if vaultConfig.Env == common.PROD { + return true + } + } + return false +} + +// skyflowGoModulePath must match the module directive in go.mod. +const skyflowGoModulePath = "github.com/skyflowapi/skyflow-go/v2" + +// CurrentSDKVersion returns the version of this module as actually resolved by the +// importing consumer's go.mod (e.g. "v2.1.0-beta.1"), read from the running binary's +// embedded build info. constants.SDK_VERSION is a manually maintained literal that isn't +// bumped per release (see internal/constants/constants.go), so on its own it can't be +// trusted to detect a beta/dev build - this reads the ground truth instead, falling back +// to the constant when build info isn't meaningful (e.g. `go test` against this module's +// own source, where Main.Version is always "(devel)"). +func CurrentSDKVersion() string { + info, ok := debug.ReadBuildInfo() + if !ok { + return constants.SDK_VERSION + } + if info.Main.Path == skyflowGoModulePath && isResolvedVersion(info.Main.Version) { + return info.Main.Version + } + for _, dep := range info.Deps { + if dep.Path == skyflowGoModulePath && isResolvedVersion(dep.Version) { + return dep.Version + } + } + return constants.SDK_VERSION +} + +func isResolvedVersion(version string) bool { + return version != "" && version != "(devel)" +} + func ParseTokenizeResponse(apiResponse vaultapis.V1TokenizeResponse) *common.TokenizeResponse { var tokens []string for _, record := range apiResponse.GetRecords() { diff --git a/v2/internal/helpers/helpers_test.go b/v2/internal/helpers/helpers_test.go index 6b31cc1..8520a9c 100644 --- a/v2/internal/helpers/helpers_test.go +++ b/v2/internal/helpers/helpers_test.go @@ -20,6 +20,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + constants "github.com/skyflowapi/skyflow-go/v2/internal/constants" vaultapis "github.com/skyflowapi/skyflow-go/v2/internal/generated" "github.com/skyflowapi/skyflow-go/v2/internal/generated/core" . "github.com/skyflowapi/skyflow-go/v2/internal/helpers" @@ -1867,3 +1868,56 @@ var _ = Describe("Deprecation warning logs", func() { }) }) }) + +// SK-2963: beta-build-in-prod warning. +var _ = Describe("IsNonGaVersion", func() { + It("treats a plain semver release as GA", func() { + Expect(IsNonGaVersion("v2.1.0")).To(BeFalse()) + Expect(IsNonGaVersion("2.11.3")).To(BeFalse()) + }) + It("treats a beta suffix as non-GA", func() { + Expect(IsNonGaVersion("v2.1.0-beta.1")).To(BeTrue()) + }) + It("treats a dev suffix as non-GA", func() { + Expect(IsNonGaVersion("v2.1.0-dev.abc1234")).To(BeTrue()) + }) + It("treats an empty or garbage string as non-GA", func() { + Expect(IsNonGaVersion("")).To(BeTrue()) + Expect(IsNonGaVersion("not-a-version")).To(BeTrue()) + }) +}) + +var _ = Describe("AnyVaultIsProd", func() { + It("returns false for an empty list", func() { + Expect(AnyVaultIsProd(nil)).To(BeFalse()) + Expect(AnyVaultIsProd([]common.VaultConfig{})).To(BeFalse()) + }) + It("returns false when no vault is PROD", func() { + configs := []common.VaultConfig{ + {VaultId: "v1", Env: common.DEV}, + {VaultId: "v2", Env: common.SANDBOX}, + {VaultId: "v3", Env: common.STAGE}, + } + Expect(AnyVaultIsProd(configs)).To(BeFalse()) + }) + It("returns true when one of several vaults is PROD", func() { + configs := []common.VaultConfig{ + {VaultId: "v1", Env: common.DEV}, + {VaultId: "v2", Env: common.PROD}, + } + Expect(AnyVaultIsProd(configs)).To(BeTrue()) + }) + It("treats an unset Env as PROD, matching GetURLWithEnv's own default", func() { + configs := []common.VaultConfig{{VaultId: "v1"}} + Expect(AnyVaultIsProd(configs)).To(BeTrue()) + }) +}) + +var _ = Describe("CurrentSDKVersion", func() { + It("falls back to constants.SDK_VERSION when build info isn't a resolved module version", func() { + // go test builds this module's own source, so debug.ReadBuildInfo().Main.Version + // is always "(devel)" here - this exercises (and documents) the fallback path; + // the real-consumer path can only be exercised by an actual downstream module. + Expect(CurrentSDKVersion()).To(Equal(constants.SDK_VERSION)) + }) +}) diff --git a/v2/utils/messages/info_logs.go b/v2/utils/messages/info_logs.go index b82fbb7..05856c9 100644 --- a/v2/utils/messages/info_logs.go +++ b/v2/utils/messages/info_logs.go @@ -105,4 +105,6 @@ const ( DEPRECATED_RESPONSE_KEY_SKYFLOW_ID_UPDATE = SDK_LOG_PREFIX + "Deprecated: response key 'skyflowId' is deprecated and will be removed in a future version. Use 'SkyflowId' instead." DEPRECATED_RESPONSE_KEY_TOKENIZED_DATA = SDK_LOG_PREFIX + "Deprecated: response key 'tokenized_data' is deprecated and will be removed in a future version. Use 'TokenizedData' instead." DEPRECATED_FIELD_REQUEST_INDEX = SDK_LOG_PREFIX + "Deprecated: field 'request_index' is deprecated and will be removed in a future version. Use 'RequestIndex' instead." + + BETA_BUILD_WARNING = SDK_LOG_PREFIX + "This is a beta/pre-release build of the Skyflow SDK (%s). Beta builds are intended for acceptance testing only - you appear to be connecting to a Production vault. Contact your Skyflow representative before using this build in Production." ) From 15b8fe11277a8d5395e29fba1447bb4bf78f6e4d Mon Sep 17 00:00:00 2001 From: Devesh Bhardwaj Date: Mon, 10 Aug 2026 02:16:43 +0530 Subject: [PATCH 2/2] SK-2963: fix goconst lint failure blocking CI golangci-lint's goconst check (min-occurrences: 1) rejects the inline "(devel)" string literal in isResolvedVersion. Hoist it to a named develVersion constant. Co-Authored-By: Claude Sonnet 5 --- v2/internal/helpers/helpers.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/v2/internal/helpers/helpers.go b/v2/internal/helpers/helpers.go index 685b08d..a1b603e 100644 --- a/v2/internal/helpers/helpers.go +++ b/v2/internal/helpers/helpers.go @@ -340,13 +340,18 @@ func AnyVaultIsProd(vaultConfigs []common.VaultConfig) bool { // skyflowGoModulePath must match the module directive in go.mod. const skyflowGoModulePath = "github.com/skyflowapi/skyflow-go/v2" +// develVersion is what debug.ReadBuildInfo() reports for Main.Version when the binary +// wasn't built as a proper versioned module dependency (e.g. `go test` run against this +// module's own source, or a local `go build` from within it) - not a meaningful version. +const develVersion = "(devel)" + // CurrentSDKVersion returns the version of this module as actually resolved by the // importing consumer's go.mod (e.g. "v2.1.0-beta.1"), read from the running binary's // embedded build info. constants.SDK_VERSION is a manually maintained literal that isn't // bumped per release (see internal/constants/constants.go), so on its own it can't be // trusted to detect a beta/dev build - this reads the ground truth instead, falling back // to the constant when build info isn't meaningful (e.g. `go test` against this module's -// own source, where Main.Version is always "(devel)"). +// own source, where Main.Version is always "(devel)", see develVersion below). func CurrentSDKVersion() string { info, ok := debug.ReadBuildInfo() if !ok { @@ -364,7 +369,7 @@ func CurrentSDKVersion() string { } func isResolvedVersion(version string) bool { - return version != "" && version != "(devel)" + return version != "" && version != develVersion } func ParseTokenizeResponse(apiResponse vaultapis.V1TokenizeResponse) *common.TokenizeResponse {