From 2cba015a4b4cbda8abfd744a5a8201e4d392ef79 Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Mon, 25 May 2026 01:03:54 -0400 Subject: [PATCH] feat: apply provider conformance evidence --- protocol/types.go | 37 ++++++++++++++++++++++++++ protocol/types_test.go | 60 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) diff --git a/protocol/types.go b/protocol/types.go index b3fe46b..789efef 100644 --- a/protocol/types.go +++ b/protocol/types.go @@ -1920,6 +1920,43 @@ func (e ProviderConformanceEvidence) Validate() error { return errors.Join(errs...) } +func (c *ProviderContract) ApplyProviderConformanceEvidence(evidence ProviderConformanceEvidence) error { + if c == nil { + return errors.New("provider contract is nil") + } + if err := evidence.Validate(); err != nil { + return err + } + if evidence.PluginID != c.PluginID || + evidence.ProviderID != c.ProviderID || + evidence.ContractID != c.ContractID || + evidence.Version != c.Version { + return fmt.Errorf("provider conformance evidence %q does not match provider contract tuple", evidence.ID) + } + for i := range c.RuntimeContract.Profiles { + if c.RuntimeContract.Profiles[i].ID != evidence.RuntimeProfileID { + continue + } + c.RuntimeContract.Profiles[i].UpstreamClientConformance = UpstreamClientConformanceRealClient + c.RuntimeContract.Profiles[i].UpstreamClientEvidenceRef = evidence.EvidenceRef + c.RuntimeContract.Profiles[i].UpstreamClientEvidenceDigest = evidence.EvidenceDigest + if !containsString(c.RuntimeContract.Profiles[i].ConformanceProfiles, evidence.ConformanceProfile) { + c.RuntimeContract.Profiles[i].ConformanceProfiles = append(c.RuntimeContract.Profiles[i].ConformanceProfiles, evidence.ConformanceProfile) + } + return nil + } + return fmt.Errorf("provider conformance evidence %q runtime profile %q does not match provider contract", evidence.ID, evidence.RuntimeProfileID) +} + +func containsString(values []string, target string) bool { + for _, value := range values { + if value == target { + return true + } + } + return false +} + type ProviderUpstreamClientRequirement struct { ProtocolVersion string `json:"protocol_version"` PluginID string `json:"plugin_id"` diff --git a/protocol/types_test.go b/protocol/types_test.go index ec214e1..323995e 100644 --- a/protocol/types_test.go +++ b/protocol/types_test.go @@ -2,6 +2,7 @@ package protocol_test import ( "encoding/json" + "slices" "strings" "testing" "time" @@ -728,6 +729,65 @@ func TestProviderConformanceEvidenceRequiresArtifactDigestAndObservation(t *test } } +func TestProviderContractAppliesProviderConformanceEvidence(t *testing.T) { + contract := validBatchProviderContract() + profile := contract.RuntimeContract.Profiles[0] + evidence := protocol.ProviderConformanceEvidence{ + ProtocolVersion: protocol.Version, + ID: "example-real-client-evidence", + PluginID: contract.PluginID, + ProviderID: contract.ProviderID, + ContractID: contract.ContractID, + Version: contract.Version, + RuntimeProfileID: profile.ID, + ConformanceProfile: "upstream-client-v1", + UpstreamClientName: "example-client", + UpstreamClientVersion: "1.2.3", + EvidenceRef: "artifact://providers/example/evidence/upstream-client-v1", + EvidenceDigest: protocol.CanonicalHash("evidence"), + ObservedAt: time.Now().UTC(), + } + + if err := contract.ApplyProviderConformanceEvidence(evidence); err != nil { + t.Fatalf("apply evidence: %v", err) + } + got := contract.RuntimeContract.Profiles[0] + if got.UpstreamClientConformance != protocol.UpstreamClientConformanceRealClient || + got.UpstreamClientEvidenceRef != evidence.EvidenceRef || + got.UpstreamClientEvidenceDigest != evidence.EvidenceDigest { + t.Fatalf("runtime profile evidence was not applied: %+v", got) + } + if !slices.Contains(got.ConformanceProfiles, evidence.ConformanceProfile) { + t.Fatalf("runtime profile missing conformance profile %q: %+v", evidence.ConformanceProfile, got.ConformanceProfiles) + } + if err := contract.ApplyProviderConformanceEvidence(evidence); err != nil { + t.Fatalf("reapply evidence: %v", err) + } + if got := contract.RuntimeContract.Profiles[0].ConformanceProfiles; countString(got, evidence.ConformanceProfile) != 1 { + t.Fatalf("runtime profile duplicated conformance profile %q: %+v", evidence.ConformanceProfile, got) + } + + evidence.PluginID = "workflow-plugin-other" + if err := contract.ApplyProviderConformanceEvidence(evidence); err == nil || !strings.Contains(err.Error(), "does not match provider contract tuple") { + t.Fatalf("expected tuple mismatch error, got %v", err) + } + evidence.PluginID = contract.PluginID + evidence.RuntimeProfileID = "missing-runtime" + if err := contract.ApplyProviderConformanceEvidence(evidence); err == nil || !strings.Contains(err.Error(), "runtime profile") { + t.Fatalf("expected runtime profile mismatch error, got %v", err) + } +} + +func countString(values []string, target string) int { + count := 0 + for _, value := range values { + if value == target { + count++ + } + } + return count +} + func validBatchProviderContract() protocol.ProviderContract { return protocol.ProviderContract{ ProtocolVersion: protocol.Version,