diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index ea7116b..420c071 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -320,7 +320,7 @@ the fail-closed suspended-child lifecycle. ## Stage 4 — Remove File Writing From The Subprocess Package -**Status: Pending** +**Status: Completed** ### Goal diff --git a/internal/adapters/audita/fake.go b/internal/adapters/audita/fake.go index b4f403a..00376fd 100644 --- a/internal/adapters/audita/fake.go +++ b/internal/adapters/audita/fake.go @@ -6,7 +6,6 @@ import ( "fmt" "path/filepath" - "gitea.maximumdirect.net/eric/narratio/internal/adapters/subprocess" "gitea.maximumdirect.net/eric/narratio/internal/fileops" ) @@ -84,17 +83,17 @@ func materializePlaceholders(req PolishRequest) error { "merged_transcript_path": req.MergedTranscriptPath, "output_path": req.OutputProcessedPath, } - if err := subprocess.WriteYAMLAtomic(req.GeneratedConfigPath, payload, fileops.WorkspaceFileMode); err != nil { + if err := writeGeneratedYAML(req.GeneratedConfigPath, payload); err != nil { return fmt.Errorf("write generated config %q: %w", req.GeneratedConfigPath, err) } } if req.StdoutLogPath != "" { - if err := subprocess.WriteFileAtomic(req.StdoutLogPath, []byte("audita noop/fake stdout placeholder\n"), fileops.WorkspaceFileMode); err != nil { + if err := fileops.WriteFileAtomic(req.StdoutLogPath, []byte("audita noop/fake stdout placeholder\n"), fileops.WorkspaceFileMode); err != nil { return fmt.Errorf("write stdout log %q: %w", req.StdoutLogPath, err) } } if req.StderrLogPath != "" { - if err := subprocess.WriteFileAtomic(req.StderrLogPath, []byte("audita noop/fake stderr placeholder\n"), fileops.WorkspaceFileMode); err != nil { + if err := fileops.WriteFileAtomic(req.StderrLogPath, []byte("audita noop/fake stderr placeholder\n"), fileops.WorkspaceFileMode); err != nil { return fmt.Errorf("write stderr log %q: %w", req.StderrLogPath, err) } } @@ -124,7 +123,7 @@ func writeJSONIfRequested(path string, payload any) error { if err != nil { return fmt.Errorf("marshal placeholder json for %q: %w", path, err) } - if err := subprocess.WriteFileAtomic(path, data, fileops.WorkspaceFileMode); err != nil { + if err := fileops.WriteFileAtomic(path, data, fileops.WorkspaceFileMode); err != nil { return fmt.Errorf("write placeholder json %q: %w", path, err) } return nil diff --git a/internal/adapters/audita/generated_config.go b/internal/adapters/audita/generated_config.go new file mode 100644 index 0000000..833f066 --- /dev/null +++ b/internal/adapters/audita/generated_config.go @@ -0,0 +1,19 @@ +package audita + +import ( + "fmt" + + "gitea.maximumdirect.net/eric/narratio/internal/fileops" + "gopkg.in/yaml.v3" +) + +func writeGeneratedYAML(path string, payload any) error { + data, err := yaml.Marshal(payload) + if err != nil { + return fmt.Errorf("marshal generated YAML for %q: %w", path, err) + } + if err := fileops.WriteFileAtomic(path, data, fileops.WorkspaceFileMode); err != nil { + return fmt.Errorf("write generated YAML %q: %w", path, err) + } + return nil +} diff --git a/internal/adapters/audita/subprocess.go b/internal/adapters/audita/subprocess.go index 8db8b24..96d3c0f 100644 --- a/internal/adapters/audita/subprocess.go +++ b/internal/adapters/audita/subprocess.go @@ -378,7 +378,7 @@ func (r *SubprocessRunner) writeInvocationConfig(req PolishRequest, args []strin "credential_env_var": r.llmAPIKeyEnv, "credential_present": credentialPresent, } - return subprocess.WriteYAMLAtomic(req.GeneratedConfigPath, payload, fileops.WorkspaceFileMode) + return writeGeneratedYAML(req.GeneratedConfigPath, payload) } func validateProcessedOutput(path string) error { diff --git a/internal/adapters/scriptorium/fake.go b/internal/adapters/scriptorium/fake.go index db1e249..a79e06e 100644 --- a/internal/adapters/scriptorium/fake.go +++ b/internal/adapters/scriptorium/fake.go @@ -4,7 +4,6 @@ import ( "context" "fmt" - "gitea.maximumdirect.net/eric/narratio/internal/adapters/subprocess" "gitea.maximumdirect.net/eric/narratio/internal/fileops" ) @@ -143,7 +142,7 @@ func (f *FakeRunner) RenderArtifact(ctx context.Context, req RenderArtifactReque func materializeRunPlaceholders(req RunArtifactRequest) error { if req.OutputPath != "" { - if err := subprocess.WriteFileAtomic(req.OutputPath, []byte("scriptorium noop/fake run artifact\n"), fileops.WorkspaceFileMode); err != nil { + if err := fileops.WriteFileAtomic(req.OutputPath, []byte("scriptorium noop/fake run artifact\n"), fileops.WorkspaceFileMode); err != nil { return fmt.Errorf("write run output %q: %w", req.OutputPath, err) } } @@ -155,17 +154,17 @@ func materializeRunPlaceholders(req RunArtifactRequest) error { "prompt_id": req.PromptID, "output_path": req.OutputPath, } - if err := subprocess.WriteYAMLAtomic(req.GeneratedConfigPath, payload, fileops.WorkspaceFileMode); err != nil { + if err := writeGeneratedYAML(req.GeneratedConfigPath, payload); err != nil { return fmt.Errorf("write generated config %q: %w", req.GeneratedConfigPath, err) } } if req.StdoutLogPath != "" { - if err := subprocess.WriteFileAtomic(req.StdoutLogPath, []byte("scriptorium noop/fake run stdout placeholder\n"), fileops.WorkspaceFileMode); err != nil { + if err := fileops.WriteFileAtomic(req.StdoutLogPath, []byte("scriptorium noop/fake run stdout placeholder\n"), fileops.WorkspaceFileMode); err != nil { return fmt.Errorf("write stdout log %q: %w", req.StdoutLogPath, err) } } if req.StderrLogPath != "" { - if err := subprocess.WriteFileAtomic(req.StderrLogPath, []byte("scriptorium noop/fake run stderr placeholder\n"), fileops.WorkspaceFileMode); err != nil { + if err := fileops.WriteFileAtomic(req.StderrLogPath, []byte("scriptorium noop/fake run stderr placeholder\n"), fileops.WorkspaceFileMode); err != nil { return fmt.Errorf("write stderr log %q: %w", req.StderrLogPath, err) } } @@ -174,7 +173,7 @@ func materializeRunPlaceholders(req RunArtifactRequest) error { func materializeRenderPlaceholders(req RenderArtifactRequest) error { if req.OutputPath != "" { - if err := subprocess.WriteFileAtomic(req.OutputPath, []byte("{\"schema\":\"scriptorium.render.v1\",\"placeholder\":true}\n"), fileops.WorkspaceFileMode); err != nil { + if err := fileops.WriteFileAtomic(req.OutputPath, []byte("{\"schema\":\"scriptorium.render.v1\",\"placeholder\":true}\n"), fileops.WorkspaceFileMode); err != nil { return fmt.Errorf("write render output %q: %w", req.OutputPath, err) } } @@ -186,17 +185,17 @@ func materializeRenderPlaceholders(req RenderArtifactRequest) error { "prompt_id": req.PromptID, "output_path": req.OutputPath, } - if err := subprocess.WriteYAMLAtomic(req.GeneratedConfigPath, payload, fileops.WorkspaceFileMode); err != nil { + if err := writeGeneratedYAML(req.GeneratedConfigPath, payload); err != nil { return fmt.Errorf("write generated config %q: %w", req.GeneratedConfigPath, err) } } if req.StdoutLogPath != "" { - if err := subprocess.WriteFileAtomic(req.StdoutLogPath, []byte("scriptorium noop/fake render stdout placeholder\n"), fileops.WorkspaceFileMode); err != nil { + if err := fileops.WriteFileAtomic(req.StdoutLogPath, []byte("scriptorium noop/fake render stdout placeholder\n"), fileops.WorkspaceFileMode); err != nil { return fmt.Errorf("write stdout log %q: %w", req.StdoutLogPath, err) } } if req.StderrLogPath != "" { - if err := subprocess.WriteFileAtomic(req.StderrLogPath, []byte("scriptorium noop/fake render stderr placeholder\n"), fileops.WorkspaceFileMode); err != nil { + if err := fileops.WriteFileAtomic(req.StderrLogPath, []byte("scriptorium noop/fake render stderr placeholder\n"), fileops.WorkspaceFileMode); err != nil { return fmt.Errorf("write stderr log %q: %w", req.StderrLogPath, err) } } diff --git a/internal/adapters/scriptorium/generated_config.go b/internal/adapters/scriptorium/generated_config.go new file mode 100644 index 0000000..3bd839b --- /dev/null +++ b/internal/adapters/scriptorium/generated_config.go @@ -0,0 +1,19 @@ +package scriptorium + +import ( + "fmt" + + "gitea.maximumdirect.net/eric/narratio/internal/fileops" + "gopkg.in/yaml.v3" +) + +func writeGeneratedYAML(path string, payload any) error { + data, err := yaml.Marshal(payload) + if err != nil { + return fmt.Errorf("marshal generated YAML for %q: %w", path, err) + } + if err := fileops.WriteFileAtomic(path, data, fileops.WorkspaceFileMode); err != nil { + return fmt.Errorf("write generated YAML %q: %w", path, err) + } + return nil +} diff --git a/internal/adapters/scriptorium/subprocess.go b/internal/adapters/scriptorium/subprocess.go index cb50f46..21c0e76 100644 --- a/internal/adapters/scriptorium/subprocess.go +++ b/internal/adapters/scriptorium/subprocess.go @@ -342,7 +342,7 @@ func writeInvocationConfig(path string, payload invocationPayload) error { "render_format": payload.RenderFormat, "render_prompt_logged": payload.RenderPromptStore, } - return subprocess.WriteYAMLAtomic(path, data, fileops.WorkspaceFileMode) + return writeGeneratedYAML(path, data) } func validateNonEmptyOutput(path string) error { diff --git a/internal/adapters/seriatim/fake.go b/internal/adapters/seriatim/fake.go index 267903d..75669c4 100644 --- a/internal/adapters/seriatim/fake.go +++ b/internal/adapters/seriatim/fake.go @@ -4,7 +4,6 @@ import ( "context" "fmt" - "gitea.maximumdirect.net/eric/narratio/internal/adapters/subprocess" "gitea.maximumdirect.net/eric/narratio/internal/fileops" ) @@ -261,7 +260,7 @@ func (f *FakeRunner) Render(ctx context.Context, req RenderRequest) (RenderResul func materializePlaceholders(req MergeRequest) error { if req.OutputMergedTranscriptPath != "" { - if err := subprocess.WriteFileAtomic(req.OutputMergedTranscriptPath, []byte(`{"schema":"seriatim.intermediate.v1","segments":[]}`), fileops.WorkspaceFileMode); err != nil { + if err := fileops.WriteFileAtomic(req.OutputMergedTranscriptPath, []byte(`{"schema":"seriatim.intermediate.v1","segments":[]}`), fileops.WorkspaceFileMode); err != nil { return fmt.Errorf("write merged transcript %q: %w", req.OutputMergedTranscriptPath, err) } } @@ -272,22 +271,22 @@ func materializePlaceholders(req MergeRequest) error { "input_transcript_paths": req.InputTranscriptPaths, "output_path": req.OutputMergedTranscriptPath, } - if err := subprocess.WriteYAMLAtomic(req.GeneratedConfigPath, payload, fileops.WorkspaceFileMode); err != nil { + if err := writeGeneratedYAML(req.GeneratedConfigPath, payload); err != nil { return fmt.Errorf("write generated config %q: %w", req.GeneratedConfigPath, err) } } if req.StdoutLogPath != "" { - if err := subprocess.WriteFileAtomic(req.StdoutLogPath, []byte("seriatim noop/fake stdout placeholder\n"), fileops.WorkspaceFileMode); err != nil { + if err := fileops.WriteFileAtomic(req.StdoutLogPath, []byte("seriatim noop/fake stdout placeholder\n"), fileops.WorkspaceFileMode); err != nil { return fmt.Errorf("write stdout log %q: %w", req.StdoutLogPath, err) } } if req.StderrLogPath != "" { - if err := subprocess.WriteFileAtomic(req.StderrLogPath, []byte("seriatim noop/fake stderr placeholder\n"), fileops.WorkspaceFileMode); err != nil { + if err := fileops.WriteFileAtomic(req.StderrLogPath, []byte("seriatim noop/fake stderr placeholder\n"), fileops.WorkspaceFileMode); err != nil { return fmt.Errorf("write stderr log %q: %w", req.StderrLogPath, err) } } if req.ReportPath != "" { - if err := subprocess.WriteFileAtomic(req.ReportPath, []byte(`{"schema":"seriatim.report.v1","placeholder":true}`), fileops.WorkspaceFileMode); err != nil { + if err := fileops.WriteFileAtomic(req.ReportPath, []byte(`{"schema":"seriatim.report.v1","placeholder":true}`), fileops.WorkspaceFileMode); err != nil { return fmt.Errorf("write report %q: %w", req.ReportPath, err) } } @@ -296,7 +295,7 @@ func materializePlaceholders(req MergeRequest) error { func materializeTrimPlaceholders(req TrimRequest) error { if req.OutputTrimmedPath != "" { - if err := subprocess.WriteFileAtomic(req.OutputTrimmedPath, []byte(`{"schema":"seriatim.intermediate.v1","segments":[]}`), fileops.WorkspaceFileMode); err != nil { + if err := fileops.WriteFileAtomic(req.OutputTrimmedPath, []byte(`{"schema":"seriatim.intermediate.v1","segments":[]}`), fileops.WorkspaceFileMode); err != nil { return fmt.Errorf("write trimmed transcript %q: %w", req.OutputTrimmedPath, err) } } @@ -309,17 +308,17 @@ func materializeTrimPlaceholders(req TrimRequest) error { "output_path": req.OutputTrimmedPath, "keep_selector": req.KeepSelector, } - if err := subprocess.WriteYAMLAtomic(req.GeneratedConfigPath, payload, fileops.WorkspaceFileMode); err != nil { + if err := writeGeneratedYAML(req.GeneratedConfigPath, payload); err != nil { return fmt.Errorf("write generated config %q: %w", req.GeneratedConfigPath, err) } } if req.StdoutLogPath != "" { - if err := subprocess.WriteFileAtomic(req.StdoutLogPath, []byte("seriatim noop/fake trim stdout placeholder\n"), fileops.WorkspaceFileMode); err != nil { + if err := fileops.WriteFileAtomic(req.StdoutLogPath, []byte("seriatim noop/fake trim stdout placeholder\n"), fileops.WorkspaceFileMode); err != nil { return fmt.Errorf("write stdout log %q: %w", req.StdoutLogPath, err) } } if req.StderrLogPath != "" { - if err := subprocess.WriteFileAtomic(req.StderrLogPath, []byte("seriatim noop/fake trim stderr placeholder\n"), fileops.WorkspaceFileMode); err != nil { + if err := fileops.WriteFileAtomic(req.StderrLogPath, []byte("seriatim noop/fake trim stderr placeholder\n"), fileops.WorkspaceFileMode); err != nil { return fmt.Errorf("write stderr log %q: %w", req.StderrLogPath, err) } } @@ -328,7 +327,7 @@ func materializeTrimPlaceholders(req TrimRequest) error { func materializeNormalizePlaceholders(req NormalizeRequest) error { if req.OutputNormalizedPath != "" { - if err := subprocess.WriteFileAtomic(req.OutputNormalizedPath, []byte(`{"schema":"seriatim.intermediate.v1","segments":[]}`), fileops.WorkspaceFileMode); err != nil { + if err := fileops.WriteFileAtomic(req.OutputNormalizedPath, []byte(`{"schema":"seriatim.intermediate.v1","segments":[]}`), fileops.WorkspaceFileMode); err != nil { return fmt.Errorf("write normalized transcript %q: %w", req.OutputNormalizedPath, err) } } @@ -344,22 +343,22 @@ func materializeNormalizePlaceholders(req NormalizeRequest) error { if req.ReportPath != "" { payload["report_path"] = req.ReportPath } - if err := subprocess.WriteYAMLAtomic(req.GeneratedConfigPath, payload, fileops.WorkspaceFileMode); err != nil { + if err := writeGeneratedYAML(req.GeneratedConfigPath, payload); err != nil { return fmt.Errorf("write generated config %q: %w", req.GeneratedConfigPath, err) } } if req.StdoutLogPath != "" { - if err := subprocess.WriteFileAtomic(req.StdoutLogPath, []byte("seriatim noop/fake normalize stdout placeholder\n"), fileops.WorkspaceFileMode); err != nil { + if err := fileops.WriteFileAtomic(req.StdoutLogPath, []byte("seriatim noop/fake normalize stdout placeholder\n"), fileops.WorkspaceFileMode); err != nil { return fmt.Errorf("write stdout log %q: %w", req.StdoutLogPath, err) } } if req.StderrLogPath != "" { - if err := subprocess.WriteFileAtomic(req.StderrLogPath, []byte("seriatim noop/fake normalize stderr placeholder\n"), fileops.WorkspaceFileMode); err != nil { + if err := fileops.WriteFileAtomic(req.StderrLogPath, []byte("seriatim noop/fake normalize stderr placeholder\n"), fileops.WorkspaceFileMode); err != nil { return fmt.Errorf("write stderr log %q: %w", req.StderrLogPath, err) } } if req.ReportPath != "" { - if err := subprocess.WriteFileAtomic(req.ReportPath, []byte(`{"schema":"seriatim.report.v1","placeholder":true}`), fileops.WorkspaceFileMode); err != nil { + if err := fileops.WriteFileAtomic(req.ReportPath, []byte(`{"schema":"seriatim.report.v1","placeholder":true}`), fileops.WorkspaceFileMode); err != nil { return fmt.Errorf("write report %q: %w", req.ReportPath, err) } } @@ -368,7 +367,7 @@ func materializeNormalizePlaceholders(req NormalizeRequest) error { func materializeRenderPlaceholders(req RenderRequest) error { if req.OutputRenderedPath != "" { - if err := subprocess.WriteFileAtomic(req.OutputRenderedPath, []byte("# Transcript\n\nRendered markdown placeholder.\n"), fileops.WorkspaceFileMode); err != nil { + if err := fileops.WriteFileAtomic(req.OutputRenderedPath, []byte("# Transcript\n\nRendered markdown placeholder.\n"), fileops.WorkspaceFileMode); err != nil { return fmt.Errorf("write rendered transcript %q: %w", req.OutputRenderedPath, err) } } @@ -385,17 +384,17 @@ func materializeRenderPlaceholders(req RenderRequest) error { "include_segment_ids": req.IncludeSegmentIDs, "include_metadata": req.IncludeMetadata, } - if err := subprocess.WriteYAMLAtomic(req.GeneratedConfigPath, payload, fileops.WorkspaceFileMode); err != nil { + if err := writeGeneratedYAML(req.GeneratedConfigPath, payload); err != nil { return fmt.Errorf("write generated config %q: %w", req.GeneratedConfigPath, err) } } if req.StdoutLogPath != "" { - if err := subprocess.WriteFileAtomic(req.StdoutLogPath, []byte("seriatim noop/fake render stdout placeholder\n"), fileops.WorkspaceFileMode); err != nil { + if err := fileops.WriteFileAtomic(req.StdoutLogPath, []byte("seriatim noop/fake render stdout placeholder\n"), fileops.WorkspaceFileMode); err != nil { return fmt.Errorf("write stdout log %q: %w", req.StdoutLogPath, err) } } if req.StderrLogPath != "" { - if err := subprocess.WriteFileAtomic(req.StderrLogPath, []byte("seriatim noop/fake render stderr placeholder\n"), fileops.WorkspaceFileMode); err != nil { + if err := fileops.WriteFileAtomic(req.StderrLogPath, []byte("seriatim noop/fake render stderr placeholder\n"), fileops.WorkspaceFileMode); err != nil { return fmt.Errorf("write stderr log %q: %w", req.StderrLogPath, err) } } diff --git a/internal/adapters/seriatim/generated_config.go b/internal/adapters/seriatim/generated_config.go new file mode 100644 index 0000000..560868a --- /dev/null +++ b/internal/adapters/seriatim/generated_config.go @@ -0,0 +1,19 @@ +package seriatim + +import ( + "fmt" + + "gitea.maximumdirect.net/eric/narratio/internal/fileops" + "gopkg.in/yaml.v3" +) + +func writeGeneratedYAML(path string, payload any) error { + data, err := yaml.Marshal(payload) + if err != nil { + return fmt.Errorf("marshal generated YAML for %q: %w", path, err) + } + if err := fileops.WriteFileAtomic(path, data, fileops.WorkspaceFileMode); err != nil { + return fmt.Errorf("write generated YAML %q: %w", path, err) + } + return nil +} diff --git a/internal/adapters/seriatim/subprocess.go b/internal/adapters/seriatim/subprocess.go index dea2998..06b347e 100644 --- a/internal/adapters/seriatim/subprocess.go +++ b/internal/adapters/seriatim/subprocess.go @@ -553,7 +553,7 @@ func (r *SubprocessRunner) writeMergeInvocationConfig(req MergeRequest, args []s payload["coalesce_gap"] = *r.coalesceGap } - return subprocess.WriteYAMLAtomic(req.GeneratedConfigPath, payload, fileops.WorkspaceFileMode) + return writeGeneratedYAML(req.GeneratedConfigPath, payload) } func buildTrimArgs(req TrimRequest) []string { @@ -605,7 +605,7 @@ func writeTrimInvocationConfig(req TrimRequest, args []string, binary string, ti "output_path": req.OutputTrimmedPath, "keep_selector": req.KeepSelector, } - return subprocess.WriteYAMLAtomic(req.GeneratedConfigPath, payload, fileops.WorkspaceFileMode) + return writeGeneratedYAML(req.GeneratedConfigPath, payload) } func writeNormalizeInvocationConfig(req NormalizeRequest, args []string, binary string, timeout time.Duration, outputSchema string) error { @@ -620,7 +620,7 @@ func writeNormalizeInvocationConfig(req NormalizeRequest, args []string, binary "output_schema": outputSchema, "report_path": req.ReportPath, } - return subprocess.WriteYAMLAtomic(req.GeneratedConfigPath, payload, fileops.WorkspaceFileMode) + return writeGeneratedYAML(req.GeneratedConfigPath, payload) } func writeRenderInvocationConfig(req RenderRequest, args []string, binary string, timeout time.Duration, format string) error { @@ -638,7 +638,7 @@ func writeRenderInvocationConfig(req RenderRequest, args []string, binary string "include_segment_ids": req.IncludeSegmentIDs, "include_metadata": req.IncludeMetadata, } - return subprocess.WriteYAMLAtomic(req.GeneratedConfigPath, payload, fileops.WorkspaceFileMode) + return writeGeneratedYAML(req.GeneratedConfigPath, payload) } func validateJSONFile(path string) error { diff --git a/internal/adapters/subprocess/run.go b/internal/adapters/subprocess/run.go index 3dddf0a..2504e27 100644 --- a/internal/adapters/subprocess/run.go +++ b/internal/adapters/subprocess/run.go @@ -7,9 +7,6 @@ import ( "os/exec" "strings" "time" - - "gitea.maximumdirect.net/eric/narratio/internal/fileops" - "gopkg.in/yaml.v3" ) // RunRequest defines a subprocess invocation. @@ -108,29 +105,6 @@ func Run(ctx context.Context, req RunRequest) (RunResult, error) { return result, fmt.Errorf("command failed to run (%s): %w", diagnostics, joinErrors(outcome.waitErr, outcome.contextErr, outcome.abortErr, cleanupErr)) } -// WriteYAMLAtomic marshals value as YAML and atomically writes it to path. -func WriteYAMLAtomic(path string, value any, perm os.FileMode) error { - data, err := yaml.Marshal(value) - if err != nil { - return fmt.Errorf("marshal yaml for %q: %w", path, err) - } - if err := WriteFileAtomic(path, data, perm); err != nil { - return fmt.Errorf("write yaml %q: %w", path, err) - } - return nil -} - -// WriteFileAtomic writes bytes through the shared durable replacement primitive. -func WriteFileAtomic(path string, data []byte, perm os.FileMode) error { - if strings.TrimSpace(path) == "" { - return fmt.Errorf("write file: path is required") - } - if err := fileops.WriteFileAtomic(path, data, perm); err != nil { - return fmt.Errorf("write file %q: %w", path, err) - } - return nil -} - func buildDiagnostics(req RunRequest, result RunResult, stderrTail string) string { details := fmt.Sprintf( "executable=%q args=%v cwd=%q timeout=%s exit_code=%d timed_out=%t canceled=%t stdout_log=%q stderr_log=%q", diff --git a/internal/adapters/subprocess/run_test.go b/internal/adapters/subprocess/run_test.go index 1ab8f33..4fcfb64 100644 --- a/internal/adapters/subprocess/run_test.go +++ b/internal/adapters/subprocess/run_test.go @@ -14,8 +14,6 @@ import ( "syscall" "testing" "time" - - "gopkg.in/yaml.v3" ) func TestRunSuccessCapturesStdoutStderr(t *testing.T) { @@ -592,59 +590,6 @@ func TestRunSharedStdoutStderrLogPath(t *testing.T) { } } -func TestWriteYAMLAtomic(t *testing.T) { - dir := t.TempDir() - path := filepath.Join(dir, "config.generated.yml") - - if err := WriteYAMLAtomic(path, map[string]any{"name": "narratio", "stage": "merge"}, 0o644); err != nil { - t.Fatalf("WriteYAMLAtomic() error = %v", err) - } - - data, err := os.ReadFile(path) - if err != nil { - t.Fatalf("read yaml: %v", err) - } - - var got map[string]any - if err := yaml.Unmarshal(data, &got); err != nil { - t.Fatalf("yaml unmarshal: %v", err) - } - if got["name"] != "narratio" { - t.Fatalf("name = %#v, want narratio", got["name"]) - } -} - -func TestWriteYAMLAtomicOverwriteNoTempResidue(t *testing.T) { - dir := t.TempDir() - path := filepath.Join(dir, "config.generated.yml") - - if err := WriteYAMLAtomic(path, map[string]any{"value": "one"}, 0o644); err != nil { - t.Fatalf("first write: %v", err) - } - if err := WriteYAMLAtomic(path, map[string]any{"value": "two"}, 0o644); err != nil { - t.Fatalf("second write: %v", err) - } - - data, err := os.ReadFile(path) - if err != nil { - t.Fatalf("read yaml: %v", err) - } - if !strings.Contains(string(data), "two") { - t.Fatalf("yaml = %q, want overwritten value", string(data)) - } - - entries, err := os.ReadDir(dir) - if err != nil { - t.Fatalf("ReadDir() error = %v", err) - } - for _, entry := range entries { - name := entry.Name() - if strings.Contains(name, ".tmp-") { - t.Fatalf("temp file residue found: %q", name) - } - } -} - func TestSubprocessHelper(t *testing.T) { if os.Getenv("GO_WANT_SUBPROCESS_HELPER") != "1" { return