Skip to content

Commit 1371a49

Browse files
Merge delivery/through-the-composition-interface: Codex's follow-ups after the #1128 squash
Brings in ba15066 (native host federation), efb61fd (pre-start admission of selected workloads) and eee37c1 (hop routes through composition configuration; authority at cac1f6b). 625e66f's tree is the squash 672416b, so only those three commits' changes are merged. Conflicts are resolved keeping both sets of fixes: - One composition connection per run (compositionHost) instead of the per-attempt CloseTransport. - A ready delivery builds its frontend before the delivery lock. Admission builds nothing and keeps the payload already published. - nativeHostServices uses the fail-closed HostImplements. - NativeWorkload.Declared/DeferFrontend and the runtime-start seam are kept, now that admission consumes them. - The admission tests name the host's delivery endpoint. - All three authority libraries are pinned at cac1f6b. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 parents 348d3cf + eee37c1 commit 1371a49

45 files changed

Lines changed: 2545 additions & 270 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎AGENTS.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
- **Module:** `github.com/codefly-dev/cli`
1010
- **Repository:** `https://github.com/codefly-dev/cli`
11-
- **Language:** Go 1.27.2 (`go.mod` is the source of truth — see [Bump the Go version](docs/runbooks/bump-go-version.md))
11+
- **Language:** Go 1.27.3 (`go.mod` is the source of truth — see [Bump the Go version](docs/runbooks/bump-go-version.md))
1212

1313
The CLI is the primary interface for codefly. It orchestrates agent lifecycles, manages the
1414
daemon, runs services with their dependency graphs, and exposes an MCP server for AI tool

‎cmd/doctor_federation_test.go‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package cmd
2+
3+
import (
4+
"strings"
5+
"testing"
6+
7+
"github.com/codefly-dev/cli/pkg/environments"
8+
"github.com/codefly-dev/core/resources"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestDoctorReportsNativeFederationWithoutRequestingAFile(t *testing.T) {
13+
workspace := testWorkspaceYAML + `environments:
14+
- name: local
15+
host:
16+
coordinate: owner/local/host
17+
component: backend
18+
domain: owner
19+
audience: host
20+
trust_domain: host.local
21+
delivery: backend/api/grpc
22+
`
23+
for _, delivery := range []string{"backend/api/grpc", "other/api/grpc"} {
24+
t.Run(delivery, func(t *testing.T) {
25+
dir := singleServiceWorkspace(t, strings.ReplaceAll(workspace, "backend/api/grpc", delivery), []string{"federation"}, nil)
26+
report := runReadiness(t, workspaceReadinessOptions{dir: dir})
27+
if delivery == "other/api/grpc" {
28+
requireCode(t, report, codeConfigurationMissing, "fail")
29+
return
30+
}
31+
requireNoCode(t, report, codeConfigurationMissing)
32+
requireNoCode(t, report, codeConfigurationDirMissing)
33+
require.Contains(t, reportJSON(t, report), "container runs and deployments still require authored federation")
34+
})
35+
}
36+
}
37+
38+
func TestDoctorNativeFederationDoesNotCoverAnotherConsumer(t *testing.T) {
39+
host := &resources.Service{Name: "api", WorkspaceConfigurationDependencies: []string{"federation"}}
40+
host.WithModule("backend")
41+
other := &resources.Service{Name: "worker", WorkspaceConfigurationDependencies: []string{"federation"}}
42+
other.WithModule("backend")
43+
env := &environments.Environment{Host: &environments.EnvironmentHost{Coordinate: "owner/local/host", Domain: "owner", Delivery: "backend/api/grpc"}}
44+
require.True(t, nativeFederationCoversScope(env, []*resources.Service{host}))
45+
require.False(t, nativeFederationCoversScope(env, []*resources.Service{host, other}))
46+
require.False(t, nativeFederationCoversScope(env, nil))
47+
}

‎cmd/doctor_workspace.go‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"os/exec"
1010
"path/filepath"
1111
"regexp"
12+
"slices"
1213
"sort"
1314
"strings"
1415
"sync"
@@ -1090,6 +1091,14 @@ func checkConfigurationSources(ctx context.Context, ws *resources.Workspace, env
10901091
if provided != nil {
10911092
declared := ownConfigurationNames(wsCfgDir)
10921093
byName, own, composed := classifyProvidedConfigurations(provided, declared)
1094+
// Doctor has no selected runtime. Report native-only supply as a warning,
1095+
// and exempt no other service that also requires the authored group.
1096+
if byName[environments.FederationConfiguration] == nil && provided.Ambiguous[environments.FederationConfiguration] == nil &&
1097+
nativeFederationCoversScope(env, scope) {
1098+
required = slices.DeleteFunc(required, func(name string) bool { return name == environments.FederationConfiguration })
1099+
report.add("", "workspace configuration federation", "warn",
1100+
"derived from host for the native delivery service; container runs and deployments still require authored federation values", "")
1101+
}
10931102
reportWorkspaceConfigurationDirectory(wsCfgDir, relCfgDir, required, byName, own, report)
10941103
if len(composed) > 0 {
10951104
report.add("", "composed module configurations", "ok",
@@ -1117,6 +1126,26 @@ func checkConfigurationSources(ctx context.Context, ws *resources.Workspace, env
11171126
return append(toResolve, checkServiceConfigurationSources(ctx, env, profiles, profile, scope, report)...), provided
11181127
}
11191128

1129+
// nativeFederationCoversScope requires every declaring consumer to be the
1130+
// delivery host. A generated host group never satisfies a sibling's requirement.
1131+
func nativeFederationCoversScope(env *environments.Environment, scope []*resources.Service) bool {
1132+
if env == nil || env.DeclaresLocalK3d() {
1133+
return false
1134+
}
1135+
found := false
1136+
for _, service := range scope {
1137+
if !slices.Contains(service.WorkspaceConfigurationDependencies, environments.FederationConfiguration) {
1138+
continue
1139+
}
1140+
group, err := environments.NativeHostFederation(env.Host, service)
1141+
if err != nil || group == nil {
1142+
return false
1143+
}
1144+
found = true
1145+
}
1146+
return found
1147+
}
1148+
11201149
// reportWorkspaceConfigurationDirectory reports the workspace's own
11211150
// configuration directory. Only what nobody provides — a name no module
11221151
// ships, or one two modules disagree on — is what the directory would have to

‎cmd/run/artifact_cache_test.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ func TestTheDeliveryHostReceivesItsFederationSurfaceFromTheHostBlock(t *testing.
267267
require.NoError(t, err)
268268
require.Empty(t, groups, "a host that does not declare federation is handed none of it")
269269

270-
_, err = federationGroup(&environments.EnvironmentHost{Coordinate: "obin/local/dev"})
270+
_, err = environments.NativeHostFederation(&environments.EnvironmentHost{Coordinate: "obin/local/dev", Delivery: "saas/accounts/rest"}, accounts)
271271
require.ErrorContains(t, err, "host.domain")
272272
}
273273

‎cmd/run/extend.go‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,14 @@ func watchForExtensions(ctx context.Context, flow *orchestration.Flow, baseline
7272
case <-ctx.Done():
7373
return
7474
case <-signals:
75+
workspace, loadErr := common.LoadWorkspace(ctx)
76+
if loadErr == nil {
77+
loadErr = flow.CheckRoutingSnapshot(ctx, workspace)
78+
}
79+
if loadErr != nil {
80+
cli.Warning("the running composition was not changed: %v", loadErr)
81+
continue
82+
}
7583
if err := withdrawDroppedRoots(ctx, flow, pinned); err != nil {
7684
cli.Warning("composition withdrawal is incomplete; the next SIGUSR1 retries it: %v", err)
7785
continue
@@ -122,6 +130,9 @@ func withdrawDroppedRoots(ctx context.Context, flow *orchestration.Flow, pinned
122130
if err != nil {
123131
return fmt.Errorf("reload the workspace: %w", err)
124132
}
133+
if err = flow.CheckRoutingSnapshot(ctx, workspace); err != nil {
134+
return err
135+
}
125136
roots, err := CompositionRoots(ctx, workspace)
126137
if err != nil {
127138
return err
@@ -155,6 +166,9 @@ func extendRunningWorkspace(ctx context.Context, flow *orchestration.Flow, basel
155166
if err != nil {
156167
return fmt.Errorf("reload the workspace: %w", err)
157168
}
169+
if err = flow.CheckRoutingSnapshot(ctx, workspace); err != nil {
170+
return err
171+
}
158172
roots, err := CompositionRoots(ctx, workspace)
159173
if err != nil {
160174
return err

0 commit comments

Comments
 (0)