From f3272bba3b1b325595179041d19f0155a059d9dc Mon Sep 17 00:00:00 2001 From: Antoine Toussaint Date: Fri, 9 Oct 2026 13:53:01 -0400 Subject: [PATCH 1/2] feat(contract): advertise deployment-composition-provenance/v1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit core v0.17.0 (core#752) carries the rendering composition's membership on the DeploymentRequest and judges dependency edges with it in DeployKustomize — the deployment path this agent uses. The host requires the live contract capability before it relies on that handoff, and core deliberately does not advertise adoption for an executor; without it every staging render stops at "deployment requires agent capability deployment-composition-provenance/v1". The advertisement now extends core's current contract with the capability; a test reads it straight from the handler. Co-Authored-By: Claude Fable 5.1 --- pkg/service/provenance_contract_test.go | 31 +++++++++++++++++++++++++ pkg/service/service.go | 11 +++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 pkg/service/provenance_contract_test.go diff --git a/pkg/service/provenance_contract_test.go b/pkg/service/provenance_contract_test.go new file mode 100644 index 0000000..29b4e1d --- /dev/null +++ b/pkg/service/provenance_contract_test.go @@ -0,0 +1,31 @@ +package service_test + +import ( + "context" + "slices" + "testing" + + "github.com/codefly-dev/core/agents/contract" + agentv0 "github.com/codefly-dev/core/generated/go/codefly/services/agent/v0" + "github.com/codefly-dev/core/resources" + + goservice "github.com/codefly-dev/service-go/pkg/service" +) + +// TestAdvertisementAdoptsDeploymentCompositionProvenance: the host requires the +// live contract capability before it trusts this agent to judge a deployment's +// dependency edges with the request's composition provenance (core#752). The +// deployment path here is core's DeployKustomize, which does; the +// advertisement must say so, and keep the base contract it extends. +func TestAdvertisementAdoptsDeploymentCompositionProvenance(t *testing.T) { + info, err := goservice.New(&resources.Agent{Kind: "codefly:service", Name: "go"}).GetAgentInformation(context.Background(), &agentv0.AgentInformationRequest{}) + if err != nil { + t.Fatalf("GetAgentInformation: %v", err) + } + if !slices.Contains(info.GetContract().GetCapabilities(), contract.DeploymentCompositionProvenance) { + t.Fatalf("contract capabilities %v do not advertise %s", info.GetContract().GetCapabilities(), contract.DeploymentCompositionProvenance) + } + if err := contract.Check(info.GetContract(), contract.ContainerRecoveryScope); err != nil { + t.Fatalf("the base contract is no longer satisfied: %v", err) + } +} diff --git a/pkg/service/service.go b/pkg/service/service.go index d829166..b5d46cd 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -9,6 +9,7 @@ import ( "path/filepath" "sync" + "github.com/codefly-dev/core/agents/contract" "github.com/codefly-dev/core/agents/services" agentv0 "github.com/codefly-dev/core/generated/go/codefly/services/agent/v0" "github.com/codefly-dev/core/languages" @@ -126,7 +127,7 @@ func New(agent *resources.Agent) *Service { // package boundaries — each specialization's binary embeds and renders // its own. func (s *Service) GetAgentInformation(_ context.Context, _ *agentv0.AgentInformationRequest) (*agentv0.AgentInformation, error) { - return services.Advertisement{ + advertisement := services.Advertisement{ Backends: runners.BackendSupport{ Local: func() bool { return languages.HasGoRuntime(nil) }, Nix: true, @@ -137,7 +138,13 @@ func (s *Service) GetAgentInformation(_ context.Context, _ *agentv0.AgentInforma ReadMe: "Generic Go service. Specializations add protocols.", Validation: ValidationCapabilities(), EffectiveInputsVersions: []uint32{1}, - }.Build(), nil + }.Build() + // The deployment path is core's DeployKustomize, which judges dependency + // edges with the request's composition provenance on this core; the host + // requires the live advertisement, not the linked core version. + advertisement.Contract = contract.Current() + advertisement.Contract.Capabilities = append(advertisement.Contract.Capabilities, contract.DeploymentCompositionProvenance) + return advertisement, nil } // ValidationCapabilities is the authoritative operation contract inherited by From a6964379d9bab979040fe426513bac6f5309ae2c Mon Sep 17 00:00:00 2001 From: Antoine Toussaint Date: Fri, 9 Oct 2026 13:57:07 -0400 Subject: [PATCH 2/2] test(qualification): the bootstrapped agent advertises composition provenance The exact-capabilities check pins every claim a host may act on; the agent now honours deployment-composition-provenance/v1, so the list names it. Co-Authored-By: Claude Fable 5.1 --- qualification_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qualification_test.go b/qualification_test.go index 0920049..989d4eb 100644 --- a/qualification_test.go +++ b/qualification_test.go @@ -92,8 +92,9 @@ func TestSelfBootstrapAdvertisesTheRuntimeContract(t *testing.T) { } // Exact, not "contains": a capability gained through a Core bump reaches // this advertisement whether or not this agent honours it, and every claim - // here is one a host is entitled to act on. - if capabilities := advertisement.GetContract().GetCapabilities(); !slices.Equal(capabilities, []string{contract.ContainerRecoveryScope}) { + // here is one a host is entitled to act on. Composition provenance is + // honoured: Deploy is core's DeployKustomize, which judges with it. + if capabilities := advertisement.GetContract().GetCapabilities(); !slices.Equal(capabilities, []string{contract.ContainerRecoveryScope, contract.DeploymentCompositionProvenance}) { t.Fatalf("advertised capabilities %v", capabilities) } if acknowledgement := headers.Get(recoveryscope.Header); !slices.Equal(acknowledgement, []string{scope + ":" + namespace}) {