diff --git a/pkg/workflow/lock_schema.go b/pkg/workflow/lock_schema.go index 9de2560e936..ca79cb77eda 100644 --- a/pkg/workflow/lock_schema.go +++ b/pkg/workflow/lock_schema.go @@ -33,12 +33,19 @@ const ( // LockMetadata represents the structured metadata embedded in lock files type LockMetadata struct { - SchemaVersion LockSchemaVersion `json:"schema_version"` - FrontmatterHash string `json:"frontmatter_hash,omitempty"` - BodyHash string `json:"body_hash,omitempty"` - StopTime string `json:"stop_time,omitempty"` - CompilerVersion string `json:"compiler_version,omitempty"` - Strict bool `json:"strict,omitempty"` + SchemaVersion LockSchemaVersion `json:"schema_version"` + FrontmatterHash string `json:"frontmatter_hash,omitempty"` + BodyHash string `json:"body_hash,omitempty"` + StopTime string `json:"stop_time,omitempty"` + CompilerVersion string `json:"compiler_version,omitempty"` + Strict bool `json:"strict,omitempty"` + // AgentMetadataInfo is embedded so agent fields are declared once and + // serialized inline in the lock metadata JSON. + AgentMetadataInfo +} + +// AgentMetadataInfo holds agent and detection agent information for embedding in lock file metadata +type AgentMetadataInfo struct { EngineBaseURLCustomized bool `json:"engine_base_url_customized,omitempty"` AgentID string `json:"agent_id,omitempty"` AgentModel string `json:"agent_model,omitempty"` @@ -48,17 +55,6 @@ type LockMetadata struct { AgentImageRunner string `json:"agent_image_runner,omitempty"` } -// AgentMetadataInfo holds agent and detection agent information for embedding in lock file metadata -type AgentMetadataInfo struct { - AgentID string - AgentModel string - DetectionAgentID string - DetectionAgentModel string - EngineBaseURLCustomized bool - EngineVersions map[string]string - AgentImageRunner string -} - // SupportedSchemaVersions lists all schema versions this build can consume var SupportedSchemaVersions = []LockSchemaVersion{ LockSchemaV1, @@ -83,7 +79,7 @@ func ExtractMetadataFromLockFile(content string) (*LockMetadata, bool, error) { jsonStr := matches[1] var metadata LockMetadata if err := json.Unmarshal([]byte(jsonStr), &metadata); err != nil { - return nil, false, fmt.Errorf("failed to parse lock metadata JSON: %w", err) + return nil, false, fmt.Errorf("lock metadata JSON should be a single valid JSON object; recompile the workflow with gh aw compile to regenerate the lock file: %w", err) } lockSchemaLog.Printf("Extracted metadata from lock file: schema=%s", metadata.SchemaVersion) return &metadata, false, nil @@ -123,18 +119,12 @@ func GenerateLockMetadata(hashInfo LockHashInfo, stopTime string, strict bool, a lockSchemaLog.Printf("Generating lock metadata: schema=%s, strict=%t, hasStopTime=%t, hasBodyHash=%t", LockSchemaV4, strict, stopTime != "", hashInfo.BodyHash != "") metadata := &LockMetadata{ - SchemaVersion: LockSchemaV4, - FrontmatterHash: hashInfo.FrontmatterHash, - BodyHash: hashInfo.BodyHash, - StopTime: stopTime, - Strict: strict, - EngineBaseURLCustomized: agentInfo.EngineBaseURLCustomized, - AgentID: agentInfo.AgentID, - AgentModel: agentInfo.AgentModel, - DetectionAgentID: agentInfo.DetectionAgentID, - DetectionAgentModel: agentInfo.DetectionAgentModel, - EngineVersions: agentInfo.EngineVersions, - AgentImageRunner: agentInfo.AgentImageRunner, + SchemaVersion: LockSchemaV4, + FrontmatterHash: hashInfo.FrontmatterHash, + BodyHash: hashInfo.BodyHash, + StopTime: stopTime, + Strict: strict, + AgentMetadataInfo: agentInfo, } // Include compiler version only for release builds @@ -150,7 +140,7 @@ func GenerateLockMetadata(hashInfo LockHashInfo, stopTime string, strict bool, a func (m *LockMetadata) ToJSON() (string, error) { bytes, err := json.Marshal(m) if err != nil { - return "", fmt.Errorf("failed to serialize lock metadata: %w", err) + return "", fmt.Errorf("lock metadata should contain JSON-serializable values; check metadata field values before writing the lock file: %w", err) } return string(bytes), nil } diff --git a/pkg/workflow/lock_schema_test.go b/pkg/workflow/lock_schema_test.go index 525b84369a6..79f340b408e 100644 --- a/pkg/workflow/lock_schema_test.go +++ b/pkg/workflow/lock_schema_test.go @@ -208,7 +208,7 @@ name: test `, lockPath: "malformed.lock.yml", expectError: true, - errorText: "failed to parse lock metadata JSON", + errorText: "lock metadata JSON should be a single valid JSON object", }, } @@ -664,20 +664,22 @@ func TestGenerateLockMetadataAgentFieldsOmittedWhenEmpty(t *testing.T) { func TestLockMetadataToJSONWithAgentFields(t *testing.T) { metadata := &LockMetadata{ - SchemaVersion: LockSchemaV3, - FrontmatterHash: "test123", - Strict: true, - EngineBaseURLCustomized: true, - AgentID: "claude", - AgentModel: "claude-sonnet-4.5", - DetectionAgentID: "copilot", - DetectionAgentModel: "gpt-5.1-codex-mini", - EngineVersions: map[string]string{ - "claude": "2.1.160", - "copilot": "1.0.57", - "copilot-sdk": "1.0.0", + SchemaVersion: LockSchemaV3, + FrontmatterHash: "test123", + Strict: true, + AgentMetadataInfo: AgentMetadataInfo{ + EngineBaseURLCustomized: true, + AgentID: "claude", + AgentModel: "claude-sonnet-4.5", + DetectionAgentID: "copilot", + DetectionAgentModel: "gpt-5.1-codex-mini", + EngineVersions: map[string]string{ + "claude": "2.1.160", + "copilot": "1.0.57", + "copilot-sdk": "1.0.0", + }, + AgentImageRunner: `["self-hosted","linux"]`, }, - AgentImageRunner: `["self-hosted","linux"]`, } json, err := metadata.ToJSON()