| 97 | } |
| 98 | |
| 99 | func BuildDiffFile(db *sql.DB, runID, filePath string) (FileDiffManifest, error) { |
| 100 | filePath, err := cleanRelativeFile(filePath) |
| 101 | if err != nil { |
| 102 | return FileDiffManifest{}, err |
| 103 | } |
| 104 | baseSnapshotID, baseRoot, err := baseSnapshotForRun(db, runID) |
| 105 | if err != nil { |
| 106 | return FileDiffManifest{}, err |
| 107 | } |
| 108 | baseContent, baseOK, err := readRelativeFile(baseRoot, filePath) |
| 109 | if err != nil { |
| 110 | return FileDiffManifest{}, err |
| 111 | } |
| 112 | attempts, err := attemptsForRun(db, runID) |
| 113 | if err != nil { |
| 114 | return FileDiffManifest{}, err |
| 115 | } |
| 116 | manifest := FileDiffManifest{ |
| 117 | SchemaVersion: "agentprovenance.diff/v1", |
| 118 | RunID: runID, |
| 119 | File: filePath, |
| 120 | BaseSnapshotID: baseSnapshotID, |
| 121 | BasePath: filepath.Join(baseRoot, filePath), |
| 122 | BaseExists: baseOK, |
| 123 | BaseSHA256: hashOrMissing(baseContent, baseOK), |
| 124 | } |
| 125 | for _, attempt := range attempts { |
| 126 | content, ok, err := readRelativeFile(attempt.WorkspacePath, filePath) |
| 127 | if err != nil { |
| 128 | return FileDiffManifest{}, err |
| 129 | } |
| 130 | changed := fileChanged(baseContent, baseOK, content, ok) |
| 131 | attemptHash := "missing" |
| 132 | if ok { |
| 133 | attemptHash = sha256Hex(content) |
| 134 | } |
| 135 | entry := FileDiffAttempt{ |
| 136 | AttemptID: attempt.AttemptID, |
| 137 | RolloutID: attempt.RolloutID, |
| 138 | ToolCallID: attempt.ToolCallID, |
| 139 | SnapshotID: attempt.SnapshotID, |
| 140 | WorkspacePath: attempt.WorkspacePath, |
| 141 | Strategy: attempt.Strategy, |
| 142 | Command: attempt.Command, |
| 143 | Status: attempt.Status, |
| 144 | IsWinner: attempt.IsWinner, |
| 145 | ArtifactRef: attempt.ArtifactRef, |
| 146 | Changed: changed, |
| 147 | FileExists: ok, |
| 148 | FileSHA256: attemptHash, |
| 149 | } |
| 150 | if changed { |
| 151 | entry.UnifiedDiff = unifiedLines(baseContent, baseOK, content, ok) |
| 152 | } |
| 153 | manifest.Attempts = append(manifest.Attempts, entry) |
| 154 | } |
| 155 | return manifest, nil |
| 156 | } |