From 500f9119f2931df3b6d7899264de881c9f6b9baf Mon Sep 17 00:00:00 2001 From: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Date: Thu, 8 Oct 2026 15:16:07 -0400 Subject: [PATCH] refactor(fixture)!: the fixture API leaves the runtime root Every service that imported `codefly` to resolve its own address also linked a GitHub API client and an HTTP artifact downloader it never calls: codefly-base -> sdk-go -> core/composition -> go-github/v89 The whole reason was fixture.go, 98 lines in the ROOT package, needing core/composition for three types -- and composition carries GitHubResolver, which downloads release artifacts. Resolving a seeded identity by role is a TEST-time concern; a service binary has no business carrying the code that fetches release assets from GitHub. It moves to sdk-go/fixture: FixtureSelection becomes fixture.Selection, codefly.Fixture() becomes fixture.Selected(), codefly.WithFixture() becomes fixture.With(). Nothing is lost. Breaking, and safe: a search across codefly-dev and obin-ai finds NO consumer of the fixture API outside sdk-go's own test. Proven, not assumed -- in a generated service, `go list -deps .` (the packages the BINARY links) now matches neither core/composition nor go-github, while core/resources is still linked for address resolution. The generated service's go.mod no longer names go-github at all, which is what service-go's manifest-guard refuses (codefly-dev/service-go#128). Co-Authored-By: Claude Opus 5 (1M context) --- codefly.go | 13 ++++------ codefly_test.go | 4 ++-- fixture.go => fixture/fixture.go | 28 ++++++++++++++++++---- fixture_test.go => fixture/fixture_test.go | 23 +++++++++++------- 4 files changed, 45 insertions(+), 23 deletions(-) rename fixture.go => fixture/fixture.go (73%) rename fixture_test.go => fixture/fixture_test.go (86%) diff --git a/codefly.go b/codefly.go index 819cd81..e6a115c 100644 --- a/codefly.go +++ b/codefly.go @@ -237,15 +237,10 @@ func ServiceVersion() string { return os.Getenv(resources.VersionPrefix) } -// Fixture returns the fixture selected by the Codefly runtime. Product code -// must not depend on the runtime's environment-variable representation. -func Fixture() FixtureSelection { - return FixtureSelection(os.Getenv(resources.FixturePrefix)) -} - -func WithFixture(fixture string) bool { - return resources.Match(string(Fixture()), fixture) -} +// The fixture API moved to the sdk-go/fixture package: it was the only thing in +// this root package that needed core/composition, and composition carries a +// GitHub release resolver, so every service importing codefly for its own +// address linked a GitHub API client it never calls. Use fixture.Selected(). // Environment returns the Codefly environment selected for this process. The // representation is owned by the SDK; product code must not read its carrier. diff --git a/codefly_test.go b/codefly_test.go index 049b53b..20d84f3 100644 --- a/codefly_test.go +++ b/codefly_test.go @@ -305,8 +305,8 @@ func TestWorkspaceConfigurationUsesSDKBoundary(t *testing.T) { _, err = codefly.For(ctx).WorkspaceValue("security", "missing") assert.Error(t, err) assert.True(t, codefly.IsLocal()) - assert.Equal(t, "dogfood", string(codefly.Fixture())) - assert.True(t, codefly.WithFixture("dogfood")) + // The fixture API moved to sdk-go/fixture, so it is covered there; this + // test keeps the rest of the runtime identity it was always about. assert.Equal(t, "scoped", codefly.ScopedAuthSecret()) } diff --git a/fixture.go b/fixture/fixture.go similarity index 73% rename from fixture.go rename to fixture/fixture.go index bd83c86..4737619 100644 --- a/fixture.go +++ b/fixture/fixture.go @@ -1,4 +1,13 @@ -package codefly +// Package fixture resolves the fixture the Codefly runtime selected against the +// manifests of the packages the workspace composes. +// +// It lives OUT of sdk-go's root package deliberately. It is the only thing in +// the root that needed core/composition, and composition carries a GitHub +// release resolver — so every service that imported `codefly` for its own +// address linked a GitHub API client and an HTTP artifact downloader it never +// calls. Resolving a seeded identity by role is a TEST-time concern; a service +// binary has no business carrying the code that downloads release artifacts. +package fixture import ( "context" @@ -12,11 +21,11 @@ import ( "github.com/codefly-dev/core/resources" ) -// FixtureSelection is the fixture the Codefly runtime selected for this +// Selection is the fixture the Codefly runtime selected for this // process. It resolves against the manifests of the packages the workspace // composes, so a test authenticates as a seeded identity by role instead of // hardcoding one. -type FixtureSelection string +type Selection string // Principal returns the identity the selected fixture seeds for role. Resolving // by role means a renamed or dropped principal fails here, against the package @@ -27,7 +36,7 @@ type FixtureSelection string // is invisible here: an unresolved name reports those modules, and a name that // a local module also declares resolves to the local one without reporting the // ambiguity. -func (fixture FixtureSelection) Principal(ctx context.Context, role string) (*composition.FixturePrincipal, error) { +func (fixture Selection) Principal(ctx context.Context, role string) (*composition.FixturePrincipal, error) { name := strings.TrimSpace(string(fixture)) if name == "" { return nil, errors.New("resolve fixture principal: no fixture is selected") @@ -96,3 +105,14 @@ func composedPackages(ctx context.Context) ([]*composition.PackageManifest, []st } return manifests, pinned, nil } + +// Selected is the fixture the Codefly runtime chose for this process. Product +// code must not depend on the runtime's environment-variable representation. +func Selected() Selection { + return Selection(os.Getenv(resources.FixturePrefix)) +} + +// With reports whether the selected fixture matches name. +func With(name string) bool { + return resources.Match(string(Selected()), name) +} diff --git a/fixture_test.go b/fixture/fixture_test.go similarity index 86% rename from fixture_test.go rename to fixture/fixture_test.go index e8125d0..6f39666 100644 --- a/fixture_test.go +++ b/fixture/fixture_test.go @@ -1,12 +1,13 @@ -package codefly_test +package fixture_test import ( + "os" "path/filepath" "testing" "github.com/codefly-dev/core/composition" "github.com/codefly-dev/core/resources" - codefly "github.com/codefly-dev/sdk-go" + "github.com/codefly-dev/sdk-go/fixture" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -53,7 +54,7 @@ func TestFixturePrincipalResolvesByRole(t *testing.T) { composingWorkspace(t) t.Setenv(resources.FixturePrefix, "dev-admin") - principal, err := codefly.Fixture().Principal(t.Context(), "super_admin") + principal, err := fixture.Selected().Principal(t.Context(), "super_admin") require.NoError(t, err) assert.Equal(t, "dev-admin", principal.ID) @@ -65,7 +66,7 @@ func TestFixturePrincipalUnknownRoleNamesSeededRoles(t *testing.T) { composingWorkspace(t) t.Setenv(resources.FixturePrefix, "dev-admin") - _, err := codefly.Fixture().Principal(t.Context(), "owner") + _, err := fixture.Selected().Principal(t.Context(), "owner") require.ErrorIs(t, err, composition.ErrUnknownPrincipal) assert.Contains(t, err.Error(), "super_admin, member") @@ -75,7 +76,7 @@ func TestFixturePrincipalUnknownFixtureNamesAvailableFixtures(t *testing.T) { composingWorkspace(t) t.Setenv(resources.FixturePrefix, "typo") - _, err := codefly.Fixture().Principal(t.Context(), "super_admin") + _, err := fixture.Selected().Principal(t.Context(), "super_admin") require.ErrorIs(t, err, composition.ErrUnknownFixture) assert.Contains(t, err.Error(), "dev-admin") @@ -85,7 +86,7 @@ func TestFixturePrincipalWithoutSelectedFixture(t *testing.T) { composingWorkspace(t) t.Setenv(resources.FixturePrefix, "") - _, err := codefly.Fixture().Principal(t.Context(), "super_admin") + _, err := fixture.Selected().Principal(t.Context(), "super_admin") require.Error(t, err) assert.Contains(t, err.Error(), "no fixture is selected") @@ -100,7 +101,7 @@ func TestFixturePrincipalResolvesWhenOneDirectoryIsReferencedTwice(t *testing.T) t.Chdir(root) t.Setenv(resources.FixturePrefix, "dev-admin") - principal, err := codefly.Fixture().Principal(t.Context(), "super_admin") + principal, err := fixture.Selected().Principal(t.Context(), "super_admin") require.NoError(t, err) assert.Equal(t, "dev-admin", principal.ID) @@ -113,7 +114,7 @@ func TestFixturePrincipalNamesPinnedModulesItCannotRead(t *testing.T) { t.Chdir(root) t.Setenv(resources.FixturePrefix, "dev-admin") - _, err := codefly.Fixture().Principal(t.Context(), "super_admin") + _, err := fixture.Selected().Principal(t.Context(), "super_admin") require.ErrorIs(t, err, composition.ErrUnknownFixture) // The MODULE NAME and the fact that it is pinned, not merely some @@ -130,3 +131,9 @@ func TestFixturePrincipalNamesPinnedModulesItCannotRead(t *testing.T) { "and must say that it is PINNED, which is the diagnosis: an unknown-fixture "+ "error here has meant a pinned module the SDK never read, not a wrong name") } + +func writeFile(t *testing.T, path string, content string) { + t.Helper() + require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o755)) + require.NoError(t, os.WriteFile(path, []byte(content), 0o600)) +}