diff --git a/pkg/attestation/crafter/materials/chainloop_ai_agent_config.go b/pkg/attestation/crafter/materials/chainloop_ai_agent_config.go index 283469b10..d639fb41b 100644 --- a/pkg/attestation/crafter/materials/chainloop_ai_agent_config.go +++ b/pkg/attestation/crafter/materials/chainloop_ai_agent_config.go @@ -29,6 +29,8 @@ import ( "github.com/rs/zerolog" ) +var annotationAIAgentName = api.CreateAnnotation("material.aiagent.name") + type ChainloopAIAgentConfigCrafter struct { *crafterCommon backend *casclient.CASBackend @@ -69,5 +71,15 @@ func (c *ChainloopAIAgentConfigCrafter) Craft(ctx context.Context, artifactPath return nil, err } + // Extract agent name from the validated JSON and surface it as an annotation + var envelope struct { + Agent struct { + Name string `json:"name"` + } `json:"agent"` + } + if err := json.Unmarshal(f, &envelope); err == nil && envelope.Agent.Name != "" { + material.Annotations[annotationAIAgentName] = envelope.Agent.Name + } + return material, nil } diff --git a/pkg/attestation/crafter/materials/chainloop_ai_agent_config_test.go b/pkg/attestation/crafter/materials/chainloop_ai_agent_config_test.go index 9f082db7d..4c33ae4d3 100644 --- a/pkg/attestation/crafter/materials/chainloop_ai_agent_config_test.go +++ b/pkg/attestation/crafter/materials/chainloop_ai_agent_config_test.go @@ -25,6 +25,8 @@ import ( schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" "github.com/chainloop-dev/chainloop/internal/aiagentconfig" "github.com/chainloop-dev/chainloop/internal/schemavalidators" + "github.com/chainloop-dev/chainloop/pkg/casclient" + mUploader "github.com/chainloop-dev/chainloop/pkg/casclient/mocks" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -260,3 +262,49 @@ func TestChainloopAIAgentConfigCrafter_RejectsExtraFields(t *testing.T) { require.Error(t, err) assert.Contains(t, err.Error(), "AI agent config validation failed") } + +func TestChainloopAIAgentConfigCrafter_AgentNameAnnotation(t *testing.T) { + testCases := []struct { + name string + filePath string + expectedAgentName string + }{ + { + name: "claude agent", + filePath: "./testdata/ai-agent-config-claude.json", + expectedAgentName: "claude", + }, + { + name: "cursor agent", + filePath: "./testdata/ai-agent-config-cursor.json", + expectedAgentName: "cursor", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + logger := zerolog.Nop() + schema := &schemaapi.CraftingSchema_Material{ + Name: "test", + Type: schemaapi.CraftingSchema_Material_CHAINLOOP_AI_AGENT_CONFIG, + } + + uploader := mUploader.NewUploader(t) + uploader.On("UploadFile", context.TODO(), tc.filePath). + Return(&casclient.UpDownStatus{ + Digest: "deadbeef", + Filename: tc.filePath, + }, nil) + + backend := &casclient.CASBackend{Uploader: uploader} + + crafter, err := NewChainloopAIAgentConfigCrafter(schema, backend, &logger) + require.NoError(t, err) + + got, err := crafter.Craft(context.TODO(), tc.filePath) + require.NoError(t, err) + + assert.Equal(t, tc.expectedAgentName, got.Annotations[annotationAIAgentName]) + }) + } +} diff --git a/pkg/attestation/crafter/materials/testdata/ai-agent-config-claude.json b/pkg/attestation/crafter/materials/testdata/ai-agent-config-claude.json new file mode 100644 index 000000000..3e4baefdf --- /dev/null +++ b/pkg/attestation/crafter/materials/testdata/ai-agent-config-claude.json @@ -0,0 +1,14 @@ +{ + "schema_version": "0.1", + "agent": {"name": "claude"}, + "config_hash": "abc123", + "captured_at": "2026-03-13T10:00:00Z", + "config_files": [ + { + "path": "CLAUDE.md", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "size": 42, + "content": "IyBQcm9qZWN0IFJ1bGVz" + } + ] +} diff --git a/pkg/attestation/crafter/materials/testdata/ai-agent-config-cursor.json b/pkg/attestation/crafter/materials/testdata/ai-agent-config-cursor.json new file mode 100644 index 000000000..af09b9b8e --- /dev/null +++ b/pkg/attestation/crafter/materials/testdata/ai-agent-config-cursor.json @@ -0,0 +1,14 @@ +{ + "schema_version": "0.1", + "agent": {"name": "cursor"}, + "config_hash": "def456", + "captured_at": "2026-03-13T10:00:00Z", + "config_files": [ + { + "path": ".cursor/rules/coding.md", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "size": 20, + "content": "IyBDb2RpbmcgUnVsZXM=" + } + ] +}