diff --git a/PLAN.md b/PLAN.md index 5398a42..bf61563 100644 --- a/PLAN.md +++ b/PLAN.md @@ -2659,7 +2659,7 @@ The exact direct prerequisites below, not the ranges above, determine readiness. **Owns:** `internal/diff/safe.go`, `internal/diff/safe_test.go`. -**Deliverable:** Build the exact tagged `SafeRecord` variants for printable text, binary/large ordinary files, metadata-only changes, and secrets. +**Deliverable:** Build the exact tagged `SafeRecord` variants for printable text, binary/large ordinary files, metadata-only changes, and secrets; export `NewSafeRecord` so renderers can freeze records across the boundary. **Tests:** `TestSafeDiffRecord` covers controls, bidi, invalid UTF-8, size limits, escaped labels, hashes, and secret zero fields. @@ -2787,7 +2787,7 @@ The exact direct prerequisites below, not the ranges above, determine readiness. **Owns:** `internal/application/inspect/diff.go`, `internal/application/inspect/diff_test.go`. -**Deliverable:** Translate the same evaluation into sorted safe diff/status records, counts, convergence, and `Difference` error. +**Deliverable:** Translate the same evaluation into sorted safe diff/status records, counts, convergence, and `Difference` error; export `NewDiffRecord`, `NewDiffResult`, and `DiffTagName` so the CLI renderer tests can build frozen records. **Tests:** `TestDiffService` proves status parity, text/binary/secret behavior, alias-only drift, and no secret fields. diff --git a/internal/application/inspect/diff.go b/internal/application/inspect/diff.go index cfbec36..0012a22 100644 --- a/internal/application/inspect/diff.go +++ b/internal/application/inspect/diff.go @@ -20,6 +20,62 @@ type DiffRecord struct { safe diff.SafeRecord } +// DiffRecordInput carries the renderable fields of one diff record. +type DiffRecordInput struct { + TargetPath string + Kind StatusKind + Action string + Tag string + SourceLabel string + TargetLabel string + Lines string + SourceSize int64 + TargetSize int64 +} + +// NewDiffRecord freezes one diff record over the given renderable fields, +// so the CLI renderer tests can build records across the boundary. +func NewDiffRecord(input DiffRecordInput) DiffRecord { + return DiffRecord{ + status: StatusRecord{targetPath: input.TargetPath, kind: input.Kind, action: input.Action}, + safe: diff.NewSafeRecord(diff.SafeRecordInput{ + TargetPath: input.TargetPath, + Tag: parseDiffTag(input.Tag), + SourceLabel: input.SourceLabel, + TargetLabel: input.TargetLabel, + Lines: input.Lines, + SourceSize: input.SourceSize, + TargetSize: input.TargetSize, + }), + } +} + +// parseDiffTag maps one stable tag name to its diff value. +func parseDiffTag(name string) diff.Tag { + switch name { + case "text": + return diff.TagText + case "binary": + return diff.TagBinary + case "secret": + return diff.TagSecret + } + return diff.TagNone +} + +// DiffTagName returns the stable lowercase name of one record's safe tag. +func DiffTagName(record DiffRecord) string { + switch record.safe.Tag() { + case diff.TagText: + return "text" + case diff.TagBinary: + return "binary" + case diff.TagSecret: + return "secret" + } + return "none" +} + func (record DiffRecord) TargetPath() string { return record.status.TargetPath() } func (record DiffRecord) Kind() StatusKind { return record.status.Kind() } func (record DiffRecord) Action() string { return record.status.Action() } @@ -49,6 +105,23 @@ type DiffResult struct { converged bool } +// NewDiffResult freezes one diff result over the given records and the +// convergence flag, keeping the record slice defensive. +func NewDiffResult(records []DiffRecord, converged bool) DiffResult { + result := DiffResult{records: append([]DiffRecord(nil), records...), converged: converged} + for _, record := range result.records { + switch record.status.kind { + case StatusKindAlias: + result.aliases++ + case StatusKindRetired: + result.retired++ + default: + result.files++ + } + } + return result +} + // Records returns a defensive copy of the path-sorted diff records. func (result DiffResult) Records() []DiffRecord { return append([]DiffRecord(nil), result.records...) diff --git a/internal/diff/safe.go b/internal/diff/safe.go index a320234..21faab9 100644 --- a/internal/diff/safe.go +++ b/internal/diff/safe.go @@ -41,6 +41,35 @@ func (t Tag) Valid() bool { return t >= TagNone && t <= TagSecret } // Text records carry precomputed printable unified-diff lines, binary records // carry ordinary-file sizes and hashes, and secret records carry no payload // at all (PLAN.md Sections 9.6 and 12.4). +// SafeRecordInput carries the renderable fields of one safe record. +type SafeRecordInput struct { + TargetPath string + Tag Tag + SourceLabel string + TargetLabel string + Lines string + SourceSize int64 + TargetSize int64 + SourceHash deployment.Digest + TargetHash deployment.Digest +} + +// NewSafeRecord freezes one safe record over the given fields, so the +// inspection and CLI renderers can build records across the boundary. +func NewSafeRecord(input SafeRecordInput) SafeRecord { + return SafeRecord{ + targetPath: input.TargetPath, + tag: input.Tag, + sourceLabel: input.SourceLabel, + targetLabel: input.TargetLabel, + lines: input.Lines, + sourceSize: input.SourceSize, + targetSize: input.TargetSize, + sourceHash: input.SourceHash, + targetHash: input.TargetHash, + } +} + type SafeRecord struct { targetPath string tag Tag