From 193494a12c64244ca13148f5d0a59b86dc06ec5d Mon Sep 17 00:00:00 2001 From: antoinetoussaint-byte <312536060+antoinetoussaint-byte@users.noreply.github.com> Date: Sun, 25 Oct 2026 19:44:56 -0400 Subject: [PATCH 1/2] fix(run): a binding to a module outside the run is withheld, not refused #1158 made native admission refuse the whole run when a module's binding targets a module the run does not select ("binding runtime-admission targets runtime, which has no selected local identity workload"). A local run composes a unit and only the modules it consumes, so every module playground composing a subset (a store's playground without runtime and tracing) could not start, and excluding the configurations did not help. The run now withholds such a binding from the module's delivered manifest, in renderDelivery, so admission and every ready delivery agree: the authority's document carries no grant through it, the authority refuses a call through it by name, and the run says so (" is not part of this run"). Nothing is granted in its place and no identity is declared for an unselected workload. A binding to a module the run selects but declares to no authority (the host's own) is still refused at admission, before any manifest is sent. Tests: a binding to a module outside the run is withheld at admission and on the ready delivery, every selected module is still admitted with its own workloads only, and a binding to a selected module is kept; the host case still refuses before sending (sabotage-checked: without the withholding the unselected case fails with the reported error). Co-Authored-By: Claude Opus 5.5 --- cmd/run/launcher.go | 10 ++++- cmd/run/launcher_admission.go | 49 ++++++++++++++++++--- cmd/run/launcher_admission_test.go | 71 +++++++++++++++++++++++++----- 3 files changed, 111 insertions(+), 19 deletions(-) diff --git a/cmd/run/launcher.go b/cmd/run/launcher.go index 2c1735e3..0bdcd770 100644 --- a/cmd/run/launcher.go +++ b/cmd/run/launcher.go @@ -872,7 +872,8 @@ func (cluster *launcher) deliverReadiness(ctx context.Context, module string, ea // renderDelivery renders a loaded module's manifest. An early delivery keeps // the page payload previously published, since deferral removes a page; a -// ready one names the bundle its caller built. +// ready one names the bundle its caller built. A binding to a module outside +// the run is withheld (withholdUnselectedBindings). func (cluster *launcher) renderDelivery(ctx context.Context, deliverer *gitops.NativeDeliverer, loaded *resources.Module, early bool, bundle *solutionhost.FrontendPayload) (*solutionhost.ModuleManifest, error) { module := loaded.Name services := make([]*resources.Service, 0, len(loaded.ServiceReferences)) @@ -899,10 +900,15 @@ func (cluster *launcher) renderDelivery(ctx context.Context, deliverer *gitops.N if err != nil { return nil, err } - return deliverer.Render(ctx, &gitops.NativeWorkload{ + manifest, err := deliverer.Render(ctx, &gitops.NativeWorkload{ Module: loaded, Services: services, Declared: cluster.declaredIdentities(module), Started: started, Carriers: carriers, FrontendBundle: bundle, DeferFrontend: early && bundle == nil, }) + if err != nil { + return nil, err + } + cluster.withholdUnselectedBindings(ctx, manifest) + return manifest, nil } // Caller holds delivery across rendering, generation reservation and Applied. diff --git a/cmd/run/launcher_admission.go b/cmd/run/launcher_admission.go index 072e4271..dc46f4ba 100644 --- a/cmd/run/launcher_admission.go +++ b/cmd/run/launcher_admission.go @@ -84,12 +84,7 @@ func (cluster *launcher) admitSelected(ctx context.Context) error { return err } defer cluster.delivery.Release(1) - modules := map[string]bool{} - for _, unique := range cluster.flow.LocalRuntimeServices() { - if identity, ok := cluster.identity(unique); ok && cluster.delivers(identity) { - modules[identity.Module] = true - } - } + modules, _ := cluster.runModules() names := make([]string, 0, len(modules)) for module := range modules { names = append(names, module) @@ -114,6 +109,9 @@ func (cluster *launcher) admitSelected(ctx context.Context) error { if err != nil { return err } + // renderDelivery withheld every binding to a module outside the run, so + // what remains targets the run; one whose target this run declares to + // no authority (the host's own module) could never be served. if manifest.Authority != nil { for _, binding := range manifest.Authority.Bindings { if !modules[binding.Audience] { @@ -130,3 +128,42 @@ func (cluster *launcher) admitSelected(ctx context.Context) error { } return nil } + +// runModules names the modules of the run's selected services (selected) and +// those whose manifests the run delivers (delivered): every selected module +// with an identity workload but the host's own. +func (cluster *launcher) runModules() (delivered, selected map[string]bool) { + delivered, selected = map[string]bool{}, map[string]bool{} + for _, unique := range cluster.flow.LocalRuntimeServices() { + if ref, err := resources.ParseServiceWithOptionalModule(unique); err == nil { + selected[ref.Module] = true + } + if identity, ok := cluster.identity(unique); ok && cluster.delivers(identity) { + delivered[identity.Module] = true + } + } + return delivered, selected +} + +// withholdUnselectedBindings drops from a manifest every binding addressed to +// a module this run does not select. A run composes a unit and only the +// modules it consumes, so such a binding is not a fault of the run: the +// delivered document carries no grant through it, and the authority refuses a +// call through it by name. Nothing is granted in its place. +func (cluster *launcher) withholdUnselectedBindings(ctx context.Context, manifest *solutionhost.ModuleManifest) { + if manifest == nil || manifest.Authority == nil { + return + } + _, selected := cluster.runModules() + kept := make([]solutionhost.AuthorityBinding, 0, len(manifest.Authority.Bindings)) + for _, binding := range manifest.Authority.Bindings { + if selected[binding.Audience] { + kept = append(kept, binding) + continue + } + wool.Get(ctx).In("launcher.deliver").Warn( + fmt.Sprintf("module %s binding %s is withheld from the delivered manifest: %s is not part of this run, so a call through it is refused", manifest.Alias, binding.ID, binding.Audience), + wool.Field("module", manifest.Alias), wool.Field("binding", binding.ID), wool.Field("target", binding.Audience)) + } + manifest.Authority.Bindings = kept +} diff --git a/cmd/run/launcher_admission_test.go b/cmd/run/launcher_admission_test.go index 0063e44f..7e29f4f5 100644 --- a/cmd/run/launcher_admission_test.go +++ b/cmd/run/launcher_admission_test.go @@ -202,19 +202,68 @@ func TestNativeAuthorityOrdersHostWithoutAuthoredDependency(t *testing.T) { require.Empty(t, order) } +// writeTargetBinding gives alpha one binding, read-target, addressed to the +// audience the composition names. +func writeTargetBinding(t *testing.T, cluster *launcher, audience string) { + t.Helper() + contract := "schema: codefly/module-contract/v1\nprincipal: alpha\nnamespaces: []\nqueues: []\ndestinations: []\nscope_ceilings: []\nbindings:\n - id: read-target\n operations: [invoke]\n audience: {from: target/read-audience}\n scope_ceiling:\n invoke:\n - resource_kind: target.item\n actions: [read]\n" + configDir := filepath.Join(cluster.composed().Dir(), "configurations", "local") + require.NoError(t, os.MkdirAll(configDir, 0700)) + require.NoError(t, os.WriteFile(filepath.Join(configDir, "target.env"), []byte("READ_AUDIENCE="+audience+"\n"), 0600)) + path := filepath.Join(cluster.composed().Dir(), "modules", "alpha", "module.contract.codefly.yaml") + require.NoError(t, os.WriteFile(path, []byte(contract), 0600)) +} + +// A binding to a module the run selects but declares to no authority (the +// host's own) could never be served: admission refuses the whole selection +// before sending any manifest. func TestAdmissionRefusesUndeliveredBindingBeforeSending(t *testing.T) { - for _, audience := range []string{"missing", "model"} { - t.Run(audience, func(t *testing.T) { + cluster, peer := admissionLauncher(t) + writeTargetBinding(t, cluster, "model") + err := cluster.admitSelected(t.Context()) + require.ErrorContains(t, err, "binding read-target targets model") + require.Empty(t, peer.snapshots(), "validate the entire selection before applying any manifest") +} + +// A run composes a unit and only the modules it consumes: a binding to a +// module outside the run does not stop startup. The delivered manifest +// carries no grant through it, every selected module is still admitted with +// its own workloads only, and the selected target's binding is kept. +func TestAdmissionWithholdsBindingToUnselectedModule(t *testing.T) { + for _, tc := range []struct { + audience string + kept bool + }{{"missing", false}, {"beta", true}} { + t.Run(tc.audience, func(t *testing.T) { cluster, peer := admissionLauncher(t) - contract := "schema: codefly/module-contract/v1\nprincipal: alpha\nnamespaces: []\nqueues: []\ndestinations: []\nscope_ceilings: []\nbindings:\n - id: read-target\n operations: [invoke]\n audience: {from: target/read-audience}\n scope_ceiling:\n invoke:\n - resource_kind: target.item\n actions: [read]\n" - configDir := filepath.Join(cluster.composed().Dir(), "configurations", "local") - require.NoError(t, os.MkdirAll(configDir, 0700)) - require.NoError(t, os.WriteFile(filepath.Join(configDir, "target.env"), []byte("READ_AUDIENCE="+audience+"\n"), 0600)) - path := filepath.Join(cluster.composed().Dir(), "modules", "alpha", "module.contract.codefly.yaml") - require.NoError(t, os.WriteFile(path, []byte(contract), 0600)) - err := cluster.admitSelected(t.Context()) - require.ErrorContains(t, err, "binding read-target targets "+audience) - require.Empty(t, peer.snapshots(), "validate the entire selection before applying any manifest") + writeTargetBinding(t, cluster, tc.audience) + require.NoError(t, cluster.admitSelected(t.Context())) + snapshots := peer.snapshots() + require.Len(t, snapshots, 2, "every selected module is admitted") + for _, m := range snapshots { + require.NotNil(t, m.Authority) + for _, w := range m.Workloads { + require.Equal(t, "api", w.Service, "an identity is declared for a selected workload only") + } + if m.Alias != "alpha" { + continue + } + var audiences []string + for _, binding := range m.Authority.Bindings { + audiences = append(audiences, binding.Audience) + } + if tc.kept { + require.Equal(t, []string{tc.audience}, audiences, "a binding to a selected module is delivered") + } else { + require.Empty(t, audiences, "a binding to a module outside the run carries no grant") + } + } + // The ready delivery renders through the same path and withholds it too. + cluster.running["alpha/api"] = true + require.NoError(t, cluster.deliverReadiness(t.Context(), "alpha", false)) + latest := peer.snapshots()[len(peer.snapshots())-1] + require.Equal(t, "alpha", latest.Alias) + require.Equal(t, tc.kept, len(latest.Authority.Bindings) == 1) }) } } From 4e3184ca271a02e4036af9fcb44ee15cc458769a Mon Sep 17 00:00:00 2001 From: antoinetoussaint-byte <312536060+antoinetoussaint-byte@users.noreply.github.com> Date: Sun, 25 Oct 2026 20:05:32 -0400 Subject: [PATCH 2/2] fix(run): deliver a binding outside the run as declared; refuse only the host Review r53 of 193494a1: withholding a binding to a module outside the run dropped what the local authority supports on purpose, an audience the run does not supervise (the default fixture's model stand-in, a playground's bridge to its model provider). The authority decides a binding at call time, so before #1158 such a binding was delivered and served. A binding is now delivered as declared whatever it targets. A binding to a module nothing composes grants nothing, and the authority refuses a call through it by name ("audience X is not composed"). Admission keeps #1158's refusal for exactly one target, the host's own module, which the run declares to no authority; a module selected only through services with no identity workload (a datastore) is no longer refused. The selection is read once per admission for both decisions, and a binding outside the run is reported once per module and binding, not on every start. Tests: a binding to a module outside the run, to an identity-less selected module and to a selected module is delivered unchanged, every selected module with an identity workload is admitted with its own workloads only, and the outside-the-run warning is logged exactly once across two admissions; the host binding is still refused before anything is sent. Sabotage-checked: #1158's broader refusal fails the outside and datastore cases, and dropping the once-guard fails the warning count. Co-Authored-By: Claude Opus 5.5 --- cmd/run/launcher.go | 13 ++--- cmd/run/launcher_admission.go | 81 ++++++++++++++---------------- cmd/run/launcher_admission_test.go | 75 ++++++++++++++------------- 3 files changed, 83 insertions(+), 86 deletions(-) diff --git a/cmd/run/launcher.go b/cmd/run/launcher.go index 0bdcd770..f86ee30a 100644 --- a/cmd/run/launcher.go +++ b/cmd/run/launcher.go @@ -94,6 +94,9 @@ type launcher struct { // delivered is the last manifest delivered per module, a withdrawal // included; protected by delivery rather than the short-lived state lock. delivered map[string]*solutionhost.ModuleManifest + // outsideTargets are the bindings to a module outside the run that + // admission has reported, so each is said once; protected by delivery. + outsideTargets map[string]bool // pendingWithdrawals are modules whose withdrawal the host has not applied // yet: retried on the next composition refresh, even with no gone roots. pendingWithdrawals map[string]bool @@ -872,8 +875,7 @@ func (cluster *launcher) deliverReadiness(ctx context.Context, module string, ea // renderDelivery renders a loaded module's manifest. An early delivery keeps // the page payload previously published, since deferral removes a page; a -// ready one names the bundle its caller built. A binding to a module outside -// the run is withheld (withholdUnselectedBindings). +// ready one names the bundle its caller built. func (cluster *launcher) renderDelivery(ctx context.Context, deliverer *gitops.NativeDeliverer, loaded *resources.Module, early bool, bundle *solutionhost.FrontendPayload) (*solutionhost.ModuleManifest, error) { module := loaded.Name services := make([]*resources.Service, 0, len(loaded.ServiceReferences)) @@ -900,15 +902,10 @@ func (cluster *launcher) renderDelivery(ctx context.Context, deliverer *gitops.N if err != nil { return nil, err } - manifest, err := deliverer.Render(ctx, &gitops.NativeWorkload{ + return deliverer.Render(ctx, &gitops.NativeWorkload{ Module: loaded, Services: services, Declared: cluster.declaredIdentities(module), Started: started, Carriers: carriers, FrontendBundle: bundle, DeferFrontend: early && bundle == nil, }) - if err != nil { - return nil, err - } - cluster.withholdUnselectedBindings(ctx, manifest) - return manifest, nil } // Caller holds delivery across rendering, generation reservation and Applied. diff --git a/cmd/run/launcher_admission.go b/cmd/run/launcher_admission.go index dc46f4ba..6a2f19de 100644 --- a/cmd/run/launcher_admission.go +++ b/cmd/run/launcher_admission.go @@ -84,7 +84,17 @@ func (cluster *launcher) admitSelected(ctx context.Context) error { return err } defer cluster.delivery.Release(1) - modules, _ := cluster.runModules() + // One snapshot of the selection decides both what is admitted and which + // binding targets are part of the run. + modules, selected := map[string]bool{}, map[string]bool{} + for _, unique := range cluster.flow.LocalRuntimeServices() { + if ref, err := resources.ParseServiceWithOptionalModule(unique); err == nil { + selected[ref.Module] = true + } + if identity, ok := cluster.identity(unique); ok && cluster.delivers(identity) { + modules[identity.Module] = true + } + } names := make([]string, 0, len(modules)) for module := range modules { names = append(names, module) @@ -109,15 +119,8 @@ func (cluster *launcher) admitSelected(ctx context.Context) error { if err != nil { return err } - // renderDelivery withheld every binding to a module outside the run, so - // what remains targets the run; one whose target this run declares to - // no authority (the host's own module) could never be served. - if manifest.Authority != nil { - for _, binding := range manifest.Authority.Bindings { - if !modules[binding.Audience] { - return fmt.Errorf("module %s binding %s targets %s, which has no selected local identity workload; authority admission requires its target declaration", module, binding.ID, binding.Audience) - } - } + if err := cluster.checkBindingTargets(ctx, module, manifest, selected); err != nil { + return err } manifests = append(manifests, manifest) } @@ -129,41 +132,35 @@ func (cluster *launcher) admitSelected(ctx context.Context) error { return nil } -// runModules names the modules of the run's selected services (selected) and -// those whose manifests the run delivers (delivered): every selected module -// with an identity workload but the host's own. -func (cluster *launcher) runModules() (delivered, selected map[string]bool) { - delivered, selected = map[string]bool{}, map[string]bool{} - for _, unique := range cluster.flow.LocalRuntimeServices() { - if ref, err := resources.ParseServiceWithOptionalModule(unique); err == nil { - selected[ref.Module] = true - } - if identity, ok := cluster.identity(unique); ok && cluster.delivers(identity) { - delivered[identity.Module] = true - } - } - return delivered, selected -} - -// withholdUnselectedBindings drops from a manifest every binding addressed to -// a module this run does not select. A run composes a unit and only the -// modules it consumes, so such a binding is not a fault of the run: the -// delivered document carries no grant through it, and the authority refuses a -// call through it by name. Nothing is granted in its place. -func (cluster *launcher) withholdUnselectedBindings(ctx context.Context, manifest *solutionhost.ModuleManifest) { - if manifest == nil || manifest.Authority == nil { - return +// checkBindingTargets decides, at admission, a module's bindings by where they +// point. A binding to the host's own module could never be served, since the +// run declares that module to no authority, so admission refuses it before +// any manifest is sent. Every other binding is delivered as declared: a run +// composes a unit and only the modules it consumes, and the authority decides +// a binding at call time, serving it when its audience is composed (by this +// run, or by the fixture's stand-in for a process the run does not supervise) +// and refusing a call through it by name when nothing composes it. A binding +// to a module outside the run is said once, since admission repeats per start. +// Caller holds delivery. +func (cluster *launcher) checkBindingTargets(ctx context.Context, module string, manifest *solutionhost.ModuleManifest, selected map[string]bool) error { + if manifest.Authority == nil { + return nil } - _, selected := cluster.runModules() - kept := make([]solutionhost.AuthorityBinding, 0, len(manifest.Authority.Bindings)) for _, binding := range manifest.Authority.Bindings { - if selected[binding.Audience] { - kept = append(kept, binding) + if binding.Audience == cluster.hostModule { + return fmt.Errorf("module %s binding %s targets %s, the host's own module, which this run declares to no authority; authority admission requires its target declaration", module, binding.ID, binding.Audience) + } + key := module + "/" + binding.ID + if selected[binding.Audience] || cluster.outsideTargets[key] { continue } - wool.Get(ctx).In("launcher.deliver").Warn( - fmt.Sprintf("module %s binding %s is withheld from the delivered manifest: %s is not part of this run, so a call through it is refused", manifest.Alias, binding.ID, binding.Audience), - wool.Field("module", manifest.Alias), wool.Field("binding", binding.ID), wool.Field("target", binding.Audience)) + if cluster.outsideTargets == nil { + cluster.outsideTargets = map[string]bool{} + } + cluster.outsideTargets[key] = true + wool.Get(ctx).In("launcher.admit").Warn( + fmt.Sprintf("module %s binding %s targets %s, which is not part of this run: it is delivered as declared, and the authority refuses a call through it (audience %q is not composed) unless something else composes %s, such as a fixture's stand-in", module, binding.ID, binding.Audience, binding.Audience, binding.Audience), + wool.Field("module", module), wool.Field("binding", binding.ID), wool.Field("target", binding.Audience)) } - manifest.Authority.Bindings = kept + return nil } diff --git a/cmd/run/launcher_admission_test.go b/cmd/run/launcher_admission_test.go index 7e29f4f5..536b476d 100644 --- a/cmd/run/launcher_admission_test.go +++ b/cmd/run/launcher_admission_test.go @@ -8,6 +8,7 @@ import ( "net" "os" "path/filepath" + "strings" "sync" "sync/atomic" "testing" @@ -25,6 +26,7 @@ import ( basev0 "github.com/codefly-dev/core/generated/go/codefly/base/v0" "github.com/codefly-dev/core/resources" "github.com/codefly-dev/core/solutionhost" + "github.com/codefly-dev/core/wool" "github.com/stretchr/testify/require" "google.golang.org/grpc" ) @@ -214,56 +216,57 @@ func writeTargetBinding(t *testing.T, cluster *launcher, audience string) { require.NoError(t, os.WriteFile(path, []byte(contract), 0600)) } -// A binding to a module the run selects but declares to no authority (the -// host's own) could never be served: admission refuses the whole selection +// A binding to the host's own module could never be served: the run declares +// that module to no authority, so admission refuses the whole selection // before sending any manifest. -func TestAdmissionRefusesUndeliveredBindingBeforeSending(t *testing.T) { +func TestAdmissionRefusesBindingToHostBeforeSending(t *testing.T) { cluster, peer := admissionLauncher(t) writeTargetBinding(t, cluster, "model") err := cluster.admitSelected(t.Context()) - require.ErrorContains(t, err, "binding read-target targets model") + require.ErrorContains(t, err, "binding read-target targets model, the host's own module") require.Empty(t, peer.snapshots(), "validate the entire selection before applying any manifest") } // A run composes a unit and only the modules it consumes: a binding to a -// module outside the run does not stop startup. The delivered manifest -// carries no grant through it, every selected module is still admitted with -// its own workloads only, and the selected target's binding is kept. -func TestAdmissionWithholdsBindingToUnselectedModule(t *testing.T) { - for _, tc := range []struct { - audience string - kept bool - }{{"missing", false}, {"beta", true}} { - t.Run(tc.audience, func(t *testing.T) { +// module outside the run, or to one selected only through services with no +// identity workload, does not stop startup. It is delivered as declared, for +// the authority to decide at call time; every selected module is admitted +// with its own workloads only; a binding outside the run is said once. +func TestAdmissionDeliversBindingsOutsideTheRunAsDeclared(t *testing.T) { + for _, audience := range []string{"missing", "store", "beta"} { + t.Run(audience, func(t *testing.T) { cluster, peer := admissionLauncher(t) - writeTargetBinding(t, cluster, tc.audience) - require.NoError(t, cluster.admitSelected(t.Context())) - snapshots := peer.snapshots() - require.Len(t, snapshots, 2, "every selected module is admitted") - for _, m := range snapshots { + flow, ok := cluster.flow.(*admissionFlow) + require.True(t, ok) + flow.selected = append(flow.selected, "store/db") // a datastore: selected, no identity workload + writeTargetBinding(t, cluster, audience) + capture := &refreshLogCapture{} + provider := wool.New(context.Background(), &wool.Resource{}).WithLogger(capture) + defer provider.Done() + ctx := provider.Inject(t.Context()) + for range 2 { // admission repeats on every start + require.NoError(t, cluster.admitSelected(ctx)) + } + admitted := map[string]bool{} + for _, m := range peer.snapshots() { + admitted[m.Alias] = true require.NotNil(t, m.Authority) - for _, w := range m.Workloads { - require.Equal(t, "api", w.Service, "an identity is declared for a selected workload only") - } + require.Len(t, m.Workloads, 1) + require.Equal(t, "api", m.Workloads[0].Service, "an identity is declared for a selected workload only") if m.Alias != "alpha" { continue } - var audiences []string - for _, binding := range m.Authority.Bindings { - audiences = append(audiences, binding.Audience) - } - if tc.kept { - require.Equal(t, []string{tc.audience}, audiences, "a binding to a selected module is delivered") - } else { - require.Empty(t, audiences, "a binding to a module outside the run carries no grant") - } + require.Len(t, m.Authority.Bindings, 1) + require.Equal(t, "read-target", m.Authority.Bindings[0].ID) + require.Equal(t, audience, m.Authority.Bindings[0].Audience, "the binding is delivered as declared") + } + require.Equal(t, map[string]bool{"alpha": true, "beta": true}, admitted, "only selected modules with an identity workload are admitted") + said := strings.Count(strings.Join(capture.messages, "\n"), "binding read-target targets "+audience+", which is not part of this run") + if audience == "missing" { + require.Equal(t, 1, said, "a binding outside the run is said, once") + } else { + require.Zero(t, said, "a binding to a selected module is part of the run") } - // The ready delivery renders through the same path and withholds it too. - cluster.running["alpha/api"] = true - require.NoError(t, cluster.deliverReadiness(t.Context(), "alpha", false)) - latest := peer.snapshots()[len(peer.snapshots())-1] - require.Equal(t, "alpha", latest.Alias) - require.Equal(t, tc.kept, len(latest.Authority.Bindings) == 1) }) } }