Skip to content

Commit 6da6e08

Browse files
committed
move to top level
Signed-off-by: Jose I. Paris <jiparis@chainloop.dev>
1 parent 5c791a2 commit 6da6e08

5 files changed

Lines changed: 46 additions & 80 deletions

File tree

internal/aiagentconfig/aiagentconfig.go

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,6 @@
1515

1616
package aiagentconfig
1717

18-
const (
19-
// EvidenceID is the identifier for the AI agent config evidence
20-
EvidenceID = "CHAINLOOP_AI_AGENT_CONFIG"
21-
// EvidenceSchemaURL is the URL to the JSON schema for AI agent config
22-
EvidenceSchemaURL = "https://schemas.chainloop.dev/aiagentconfig/0.1/ai-agent-config.schema.json"
23-
)
24-
2518
// Agent identifies the AI agent provider
2619
type Agent struct {
2720
Name string `json:"name"`
@@ -43,8 +36,8 @@ type ConfigFile struct {
4336
Base64Content string `json:"base64_content"`
4437
}
4538

46-
// Data is the payload inside the evidence envelope
47-
type Data struct {
39+
// Evidence is the AI agent configuration payload
40+
type Evidence struct {
4841
SchemaVersion string `json:"schema_version"`
4942
Agent Agent `json:"agent"`
5043
ConfigHash string `json:"config_hash"`
@@ -56,19 +49,3 @@ type Data struct {
5649
MCPServers any `json:"mcp_servers,omitempty"`
5750
Subagents any `json:"subagents,omitempty"`
5851
}
59-
60-
// Evidence is the full envelope matching the custom evidence format
61-
type Evidence struct {
62-
ID string `json:"chainloop.material.evidence.id"`
63-
Schema string `json:"schema"`
64-
Data Data `json:"data"`
65-
}
66-
67-
// NewEvidence creates a new Evidence instance with the standard envelope
68-
func NewEvidence(data Data) *Evidence {
69-
return &Evidence{
70-
ID: EvidenceID,
71-
Schema: EvidenceSchemaURL,
72-
Data: data,
73-
}
74-
}

internal/aiagentconfig/builder.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ import (
2727
"time"
2828
)
2929

30-
// BuildEvidence reads discovered files and constructs the evidence payload.
30+
// Build reads discovered files and constructs the AI agent config payload.
3131
// rootDir is the base directory, filePaths are relative to rootDir.
3232
// gitCtx may be nil if not in a git repository.
33-
func BuildEvidence(rootDir string, filePaths []string, gitCtx *GitContext) (*Evidence, error) {
33+
func Build(rootDir string, filePaths []string, gitCtx *GitContext) (*Evidence, error) {
3434
// Resolve rootDir to its real path so symlink comparisons are reliable
3535
realRoot, err := filepath.EvalSymlinks(rootDir)
3636
if err != nil {
@@ -76,7 +76,7 @@ func BuildEvidence(rootDir string, filePaths []string, gitCtx *GitContext) (*Evi
7676
})
7777
}
7878

79-
data := Data{
79+
data := Evidence{
8080
SchemaVersion: "v1alpha",
8181
Agent: Agent{Name: "claude"},
8282
ConfigHash: computeCombinedHash(hashes),
@@ -85,7 +85,7 @@ func BuildEvidence(rootDir string, filePaths []string, gitCtx *GitContext) (*Evi
8585
ConfigFiles: configFiles,
8686
}
8787

88-
return NewEvidence(data), nil
88+
return &data, nil
8989
}
9090

9191
// computeCombinedHash sorts individual hashes, concatenates them, and hashes the result.

internal/aiagentconfig/builder_test.go

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import (
3131
"github.com/stretchr/testify/require"
3232
)
3333

34-
func TestBuildEvidence(t *testing.T) {
34+
func TestBuild(t *testing.T) {
3535
rootDir := t.TempDir()
3636

3737
// Create test files
@@ -47,35 +47,30 @@ func TestBuildEvidence(t *testing.T) {
4747
CommitSHA: "abc123",
4848
}
4949

50-
evidence, err := BuildEvidence(rootDir, []string{"CLAUDE.md", ".claude/settings.json"}, gitCtx)
50+
data, err := Build(rootDir, []string{"CLAUDE.md", ".claude/settings.json"}, gitCtx)
5151
require.NoError(t, err)
5252

53-
// Verify envelope
54-
assert.Equal(t, EvidenceID, evidence.ID)
55-
assert.Equal(t, EvidenceSchemaURL, evidence.Schema)
56-
57-
// Verify data
58-
assert.Equal(t, "v1alpha", evidence.Data.SchemaVersion)
59-
assert.Equal(t, "claude", evidence.Data.Agent.Name)
60-
assert.NotEmpty(t, evidence.Data.CapturedAt)
61-
assert.NotEmpty(t, evidence.Data.ConfigHash)
53+
assert.Equal(t, "v1alpha", data.SchemaVersion)
54+
assert.Equal(t, "claude", data.Agent.Name)
55+
assert.NotEmpty(t, data.CapturedAt)
56+
assert.NotEmpty(t, data.ConfigHash)
6257

6358
// Verify git context
64-
require.NotNil(t, evidence.Data.GitContext)
65-
assert.Equal(t, "https://github.com/org/repo", evidence.Data.GitContext.Repository)
66-
assert.Equal(t, "abc123", evidence.Data.GitContext.CommitSHA)
59+
require.NotNil(t, data.GitContext)
60+
assert.Equal(t, "https://github.com/org/repo", data.GitContext.Repository)
61+
assert.Equal(t, "abc123", data.GitContext.CommitSHA)
6762

6863
// Verify config files
69-
require.Len(t, evidence.Data.ConfigFiles, 2)
64+
require.Len(t, data.ConfigFiles, 2)
7065

71-
cf1 := evidence.Data.ConfigFiles[0]
66+
cf1 := data.ConfigFiles[0]
7267
assert.Equal(t, "CLAUDE.md", cf1.Path)
7368
assert.Equal(t, int64(len(file1Content)), cf1.Size)
7469
hash1 := sha256.Sum256(file1Content)
7570
assert.Equal(t, hex.EncodeToString(hash1[:]), cf1.SHA256)
7671
assert.Equal(t, base64.StdEncoding.EncodeToString(file1Content), cf1.Base64Content)
7772

78-
cf2 := evidence.Data.ConfigFiles[1]
73+
cf2 := data.ConfigFiles[1]
7974
assert.Equal(t, ".claude/settings.json", cf2.Path)
8075
hash2 := sha256.Sum256(file2Content)
8176
assert.Equal(t, hex.EncodeToString(hash2[:]), cf2.SHA256)
@@ -87,53 +82,58 @@ func TestBuildEvidence(t *testing.T) {
8782
}
8883
sort.Strings(hashes)
8984
combined := sha256.Sum256([]byte(strings.Join(hashes, "")))
90-
assert.Equal(t, hex.EncodeToString(combined[:]), evidence.Data.ConfigHash)
85+
assert.Equal(t, hex.EncodeToString(combined[:]), data.ConfigHash)
9186
}
9287

93-
func TestBuildEvidenceWithoutGitContext(t *testing.T) {
88+
func TestBuildWithoutGitContext(t *testing.T) {
9489
rootDir := t.TempDir()
9590
require.NoError(t, os.WriteFile(filepath.Join(rootDir, "CLAUDE.md"), []byte("content"), 0o600))
9691

97-
evidence, err := BuildEvidence(rootDir, []string{"CLAUDE.md"}, nil)
92+
data, err := Build(rootDir, []string{"CLAUDE.md"}, nil)
9893
require.NoError(t, err)
9994

100-
assert.Nil(t, evidence.Data.GitContext)
101-
assert.Len(t, evidence.Data.ConfigFiles, 1)
95+
assert.Nil(t, data.GitContext)
96+
assert.Len(t, data.ConfigFiles, 1)
10297
}
10398

104-
func TestBuildEvidenceJSONFormat(t *testing.T) {
99+
func TestBuildJSONFormat(t *testing.T) {
105100
rootDir := t.TempDir()
106101
require.NoError(t, os.WriteFile(filepath.Join(rootDir, "CLAUDE.md"), []byte("content"), 0o600))
107102

108-
evidence, err := BuildEvidence(rootDir, []string{"CLAUDE.md"}, nil)
103+
data, err := Build(rootDir, []string{"CLAUDE.md"}, nil)
109104
require.NoError(t, err)
110105

111-
// Verify it marshals to valid JSON with the correct envelope field
112-
jsonData, err := json.Marshal(evidence)
106+
// Verify it marshals to valid JSON with top-level fields (no envelope)
107+
jsonData, err := json.Marshal(data)
113108
require.NoError(t, err)
114109

115110
var raw map[string]any
116111
require.NoError(t, json.Unmarshal(jsonData, &raw))
117112

118-
assert.Equal(t, EvidenceID, raw["chainloop.material.evidence.id"])
119-
assert.Equal(t, EvidenceSchemaURL, raw["schema"])
120-
assert.NotNil(t, raw["data"])
113+
assert.NotNil(t, raw["schema_version"])
114+
assert.NotNil(t, raw["agent"])
115+
assert.NotNil(t, raw["config_hash"])
116+
assert.NotNil(t, raw["captured_at"])
117+
assert.NotNil(t, raw["config_files"])
118+
// Ensure no envelope fields
119+
assert.Nil(t, raw["chainloop.material.evidence.id"])
120+
assert.Nil(t, raw["data"])
121121
}
122122

123-
func TestBuildEvidenceRejectsSymlinksEscapingRoot(t *testing.T) {
123+
func TestBuildRejectsSymlinksEscapingRoot(t *testing.T) {
124124
rootDir := t.TempDir()
125125
outsideDir := t.TempDir()
126126

127127
// Create a file outside rootDir and a symlink pointing to it
128128
require.NoError(t, os.WriteFile(filepath.Join(outsideDir, "secret.txt"), []byte("secret"), 0o600))
129129
require.NoError(t, os.Symlink(filepath.Join(outsideDir, "secret.txt"), filepath.Join(rootDir, "CLAUDE.md")))
130130

131-
_, err := BuildEvidence(rootDir, []string{"CLAUDE.md"}, nil)
131+
_, err := Build(rootDir, []string{"CLAUDE.md"}, nil)
132132
require.Error(t, err)
133133
assert.Contains(t, err.Error(), "path escapes root directory via symlink")
134134
}
135135

136-
func TestBuildEvidenceRejectsSymlinkedParentDir(t *testing.T) {
136+
func TestBuildRejectsSymlinkedParentDir(t *testing.T) {
137137
rootDir := t.TempDir()
138138
outsideDir := t.TempDir()
139139

@@ -145,16 +145,16 @@ func TestBuildEvidenceRejectsSymlinkedParentDir(t *testing.T) {
145145
// Symlink .claude -> outside directory
146146
require.NoError(t, os.Symlink(outsideClaude, filepath.Join(rootDir, ".claude")))
147147

148-
_, err := BuildEvidence(rootDir, []string{".claude/settings.json"}, nil)
148+
_, err := Build(rootDir, []string{".claude/settings.json"}, nil)
149149
require.Error(t, err)
150150
assert.Contains(t, err.Error(), "path escapes root directory via symlink")
151151
}
152152

153-
func TestBuildEvidenceAllowsRegularFilesInRoot(t *testing.T) {
153+
func TestBuildAllowsRegularFilesInRoot(t *testing.T) {
154154
rootDir := t.TempDir()
155155
require.NoError(t, os.WriteFile(filepath.Join(rootDir, "CLAUDE.md"), []byte("content"), 0o600))
156156

157-
evidence, err := BuildEvidence(rootDir, []string{"CLAUDE.md"}, nil)
157+
data, err := Build(rootDir, []string{"CLAUDE.md"}, nil)
158158
require.NoError(t, err)
159-
assert.Len(t, evidence.Data.ConfigFiles, 1)
159+
assert.Len(t, data.ConfigFiles, 1)
160160
}

pkg/attestation/crafter/collector_aiagentconfig.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ func (c *AIAgentConfigCollector) Collect(ctx context.Context, cr *Crafter, attes
5959
}
6060
}
6161

62-
evidence, err := aiagentconfig.BuildEvidence(cr.WorkingDir(), files, gitCtx)
62+
evidence, err := aiagentconfig.Build(cr.WorkingDir(), files, gitCtx)
6363
if err != nil {
64-
return fmt.Errorf("building AI agent config evidence: %w", err)
64+
return fmt.Errorf("building AI agent config: %w", err)
6565
}
6666

6767
jsonData, err := json.Marshal(evidence)

pkg/attestation/crafter/materials/chainloop_ai_agent_config.go

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222
"os"
2323

2424
schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
25-
"github.com/chainloop-dev/chainloop/internal/aiagentconfig"
2625
"github.com/chainloop-dev/chainloop/internal/schemavalidators"
2726
api "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1"
2827
"github.com/chainloop-dev/chainloop/pkg/casclient"
@@ -54,22 +53,12 @@ func (c *ChainloopAIAgentConfigCrafter) Craft(ctx context.Context, artifactPath
5453
return nil, fmt.Errorf("can't open the file: %w", err)
5554
}
5655

57-
var v aiagentconfig.Evidence
58-
if err := json.Unmarshal(f, &v); err != nil {
56+
var rawData any
57+
if err := json.Unmarshal(f, &rawData); err != nil {
5958
c.logger.Debug().Err(err).Msg("error decoding file")
6059
return nil, fmt.Errorf("invalid JSON format: %w", err)
6160
}
6261

63-
dataBytes, err := json.Marshal(v.Data)
64-
if err != nil {
65-
return nil, fmt.Errorf("failed to marshal data for validation: %w", err)
66-
}
67-
68-
var rawData any
69-
if err := json.Unmarshal(dataBytes, &rawData); err != nil {
70-
return nil, fmt.Errorf("failed to unmarshal data for validation: %w", err)
71-
}
72-
7362
if err := schemavalidators.ValidateAIAgentConfig(rawData, schemavalidators.AIAgentConfigVersion0_1); err != nil {
7463
c.logger.Debug().Err(err).Msg("schema validation failed")
7564
return nil, fmt.Errorf("AI agent config validation failed: %w", err)

0 commit comments

Comments
 (0)