--- phase: 03-pipeline-integration-and-mvp plan: 02 type: execute wave: 2 depends_on: ["03-01"] files_modified: - cmd/netsynth/main.go autonomous: false requirements: [CAPT-03] must_haves: truths: - "User runs netsynth -i -o out.mp3, generates traffic, presses Ctrl+C, and receives a valid playable MP3" - "Protocol summary prints to stderr BEFORE encoding begins" - "User sees 'Encoding N windows to ...' status message during encoding" - "User sees 'Saved (Xs, N KB, encoded in Xs)' confirmation after encoding" - "Zero-packet captures produce an error message, not a corrupt file" artifacts: - path: "cmd/netsynth/main.go" provides: "End-to-end pipeline wiring and encoding feedback" contains: "encode.RunSynthesis" key_links: - from: "cmd/netsynth/main.go" to: "encode/mp3.go" via: "encode.RunSynthesis(collectedSnapshots, outputPath)" pattern: "encode\\.RunSynthesis" - from: "cmd/netsynth/main.go" to: "aggregate/summary.go" via: "PrintSummary called before RunSynthesis (D-08)" pattern: "PrintSummary.*\n.*Encoding" --- Wire the capture-classify-aggregate pipeline into the audio synthesis engine, completing the v1 MVP end-to-end flow. Add encoding progress feedback messages per D-07/D-08/D-09. Purpose: Implements CAPT-03 — the final integration that makes `netsynth -i eth0 -o out.mp3` produce a real audio file from live network traffic. Output: Updated main.go with RunSynthesis call, encoding status/saved messages, and correct summary ordering. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/03-pipeline-integration-and-mvp/03-CONTEXT.md @.planning/phases/03-pipeline-integration-and-mvp/03-01-SUMMARY.md From encode/mp3.go: ```go // RunSynthesis consumes a slice of WindowSnapshots, renders audio via OscillatorBank, // and encodes to MP3 at outputPath. // Returns an error if zero packets were captured (OUT-03). func RunSynthesis(snapshots []classify.WindowSnapshot, outputPath string) error ``` From aggregate/accumulator.go: ```go const DefaultWindowMs = 500 ``` From aggregate/summary.go: ```go func PrintSummary(w io.Writer, totals map[classify.TrafficClass]int64) func AccumulateTotals(totals map[classify.TrafficClass]int64, snap classify.WindowSnapshot) ``` From cmd/netsynth/main.go (CURRENT — lines 91-109 are the integration zone): ```go // Accumulate snapshots for synthesis (Phase 3 will pass to encode.RunSynthesis) totals := make(map[classify.TrafficClass]int64) var collectedSnapshots []classify.WindowSnapshot for snap := range snapshots { collectedSnapshots = append(collectedSnapshots, snap) aggregate.AccumulateTotals(totals, snap) } // TODO(phase-3): Pass collectedSnapshots to encode.RunSynthesis(collectedSnapshots, outputPath) _ = collectedSnapshots // Print exit summary (CLAS-03) dropped := atomic.LoadInt64(droppedPtr) if dropped > 0 { fmt.Fprintf(os.Stderr, "\nWarning: %d packets dropped (channel buffer full)\n", dropped) } aggregate.PrintSummary(os.Stderr, totals) return nil ``` Task 1: Wire RunSynthesis and add encoding feedback messages cmd/netsynth/main.go cmd/netsynth/main.go, encode/mp3.go, aggregate/summary.go Modify `cmd/netsynth/main.go` to replace the TODO stub (lines 99-109) with the complete pipeline wiring and encoding feedback. The changes are: **Step 1: Add import** Add `"github.com/netsynth/netsynth/encode"` to the import block. The `time` import already exists. **Step 2: Replace lines 99-109 with the following sequence** Remove: ```go // TODO(phase-3): Pass collectedSnapshots to encode.RunSynthesis(collectedSnapshots, outputPath) _ = collectedSnapshots // Print exit summary (CLAS-03) dropped := atomic.LoadInt64(droppedPtr) if dropped > 0 { fmt.Fprintf(os.Stderr, "\nWarning: %d packets dropped (channel buffer full)\n", dropped) } aggregate.PrintSummary(os.Stderr, totals) return nil ``` Replace with: ```go // D-08: Print protocol summary BEFORE encoding — user sees stats immediately dropped := atomic.LoadInt64(droppedPtr) if dropped > 0 { fmt.Fprintf(os.Stderr, "\nWarning: %d packets dropped (channel buffer full)\n", dropped) } aggregate.PrintSummary(os.Stderr, totals) // D-07: Encoding status line fmt.Fprintf(os.Stderr, "Encoding %d windows to %s...\n", len(collectedSnapshots), outputPath) encodeStart := time.Now() if err := encode.RunSynthesis(collectedSnapshots, outputPath); err != nil { return fmt.Errorf("synthesis failed: %w", err) } encodeElapsed := time.Since(encodeStart) // D-09: Saved confirmation with path, audio duration, file size, encoding time audioDuration := float64(len(collectedSnapshots)) * float64(aggregate.DefaultWindowMs) / 1000.0 info, statErr := os.Stat(outputPath) if statErr != nil { return fmt.Errorf("stat output file: %w", statErr) } fmt.Fprintf(os.Stderr, "Saved %s (%.1fs, %d KB, encoded in %.1fs)\n", outputPath, audioDuration, info.Size()/1024, encodeElapsed.Seconds()) return nil ``` **Step 3: Update the Long description** Change line 32 from: ```go Long: "NetSynth captures network traffic, classifies it by protocol, and (in future phases) synthesizes an ambient MP3 soundscape.", ``` to: ```go Long: "NetSynth captures network traffic, classifies it by protocol, and synthesizes an ambient MP3 soundscape.", ``` **Key details:** - `encode.RunSynthesis` already handles the zero-packet guard (returns error, no corrupt file created) — we just propagate the error via `fmt.Errorf("synthesis failed: %w", err)` - Audio duration is computed from snapshot count, NOT wall-clock time: `float64(len(collectedSnapshots)) * float64(aggregate.DefaultWindowMs) / 1000.0` — uses float64 to avoid integer truncation for short captures (Pitfall 4) - `os.Stat` is only called AFTER confirming `RunSynthesis` returned nil (Pitfall 5) - The `_ = collectedSnapshots` line and the TODO comment are completely removed cd /home/dev/workspace/yoloyolo && go build -o /tmp/netsynth-test ./cmd/netsynth && echo "BUILD OK" && go test ./... -count=1 2>&1 | tail -20 - cmd/netsynth/main.go contains `"github.com/netsynth/netsynth/encode"` in imports - cmd/netsynth/main.go contains `encode.RunSynthesis(collectedSnapshots, outputPath)` - cmd/netsynth/main.go contains `fmt.Fprintf(os.Stderr, "Encoding %d windows to %s...\n"` - cmd/netsynth/main.go contains `fmt.Fprintf(os.Stderr, "Saved %s (%.1fs, %d KB, encoded in %.1fs)\n"` - cmd/netsynth/main.go does NOT contain `TODO(phase-3)` - cmd/netsynth/main.go does NOT contain `_ = collectedSnapshots` - cmd/netsynth/main.go `PrintSummary` call appears BEFORE `encode.RunSynthesis` call (D-08) - cmd/netsynth/main.go audioDuration uses `float64(len(collectedSnapshots)) * float64(aggregate.DefaultWindowMs) / 1000.0` (not integer division) - `go build ./cmd/netsynth` exits 0 - `go test ./... -count=1` all packages pass The complete v1 MVP pipeline is wired: capture -> classify -> aggregate -> synthesize -> encode MP3. Protocol summary prints before encoding. Encoding status and saved confirmation messages display path, audio duration, file size, and encoding time. Binary builds successfully. All tests pass. Task 2: Verify end-to-end MVP flow cmd/netsynth/main.go cmd/netsynth/main.go Human verification of the complete end-to-end MVP flow. The binary has been built and all automated tests pass. This checkpoint verifies the live capture-to-MP3 pipeline works with real network traffic and produces the expected UX output. Complete NetSynth v1 MVP: live capture -> classification -> audio synthesis -> MP3 output with encoding feedback 1. Build the binary: `go build -o ./netsynth ./cmd/netsynth` 2. Run with loopback interface: `sudo ./netsynth -i lo -o /tmp/test-mvp.mp3` 3. In another terminal, generate some traffic: `ping -c 5 127.0.0.1` and `curl http://127.0.0.1:80 2>/dev/null || true` 4. Press Ctrl+C in the netsynth terminal 5. Verify stderr output shows: - Protocol summary (packet counts per class) printed FIRST - "Encoding N windows to /tmp/test-mvp.mp3..." message - "Saved /tmp/test-mvp.mp3 (Xs, N KB, encoded in Xs)" confirmation 6. Verify the MP3 file exists and has non-zero size: `ls -la /tmp/test-mvp.mp3` 7. Optionally validate with ffprobe: `ffprobe /tmp/test-mvp.mp3 2>&1 | head -20` 8. Test zero-packet case: run and immediately Ctrl+C (no traffic): should show error, no file created cd /home/dev/workspace/yoloyolo && go build -o /tmp/netsynth-verify ./cmd/netsynth && echo "Binary builds OK" - Binary builds without errors - Running with loopback + traffic + Ctrl+C produces a non-zero MP3 file - stderr shows protocol summary, then "Encoding...", then "Saved..." in that order - Zero-packet run shows error message, no file created User has verified the end-to-end MVP flow works with live traffic on loopback interface. Type "approved" or describe issues ```bash # Full test suite go test ./... -count=1 # Binary builds go build -o /tmp/netsynth-verify ./cmd/netsynth # No TODO(phase-3) remaining grep -rn 'TODO(phase-3)' cmd/ classify/ synth/ encode/ aggregate/ # No bare ClassUnknown references (from Plan 01, verify still clean) grep -rn 'ClassUnknown[^1234]' classify/ synth/ encode/ cmd/ aggregate/ | grep -v test | grep -v '//' ``` - Binary builds and runs: `netsynth -i lo -o out.mp3` produces a valid MP3 - Protocol summary appears before encoding messages on stderr - Saved line shows path, audio duration, file size, encoding time - Zero-packet capture returns error without creating a file - All tests pass (`go test ./... -count=1`) - No TODO(phase-3) stubs remain After completion, create `.planning/phases/03-pipeline-integration-and-mvp/03-02-SUMMARY.md`