257 lines
10 KiB
Markdown
257 lines
10 KiB
Markdown
---
|
|||
|
|
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 <iface> -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 <path>...' status message during encoding"
|
||
|
|
- "User sees 'Saved <path> (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"
|
||
|
|
---
|
||
|
|
|
||
|
|
<objective>
|
||
|
|
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.
|
||
|
|
</objective>
|
||
|
|
|
||
|
|
<execution_context>
|
||
|
|
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
||
|
|
@$HOME/.claude/get-shit-done/templates/summary.md
|
||
|
|
</execution_context>
|
||
|
|
|
||
|
|
<context>
|
||
|
|
@.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
|
||
|
|
|
||
|
|
<interfaces>
|
||
|
|
<!-- Key types and contracts the executor needs. -->
|
||
|
|
|
||
|
|
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
|
||
|
|
```
|
||
|
|
</interfaces>
|
||
|
|
</context>
|
||
|
|
|
||
|
|
<tasks>
|
||
|
|
|
||
|
|
<task type="auto">
|
||
|
|
<name>Task 1: Wire RunSynthesis and add encoding feedback messages</name>
|
||
|
|
<files>cmd/netsynth/main.go</files>
|
||
|
|
<read_first>cmd/netsynth/main.go, encode/mp3.go, aggregate/summary.go</read_first>
|
||
|
|
<action>
|
||
|
|
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
|
||
|
|
</action>
|
||
|
|
<verify>
|
||
|
|
<automated>cd /home/dev/workspace/yoloyolo && go build -o /tmp/netsynth-test ./cmd/netsynth && echo "BUILD OK" && go test ./... -count=1 2>&1 | tail -20</automated>
|
||
|
|
</verify>
|
||
|
|
<acceptance_criteria>
|
||
|
|
- 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
|
||
|
|
</acceptance_criteria>
|
||
|
|
<done>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.</done>
|
||
|
|
</task>
|
||
|
|
|
||
|
|
<task type="checkpoint:human-verify" gate="blocking">
|
||
|
|
<name>Task 2: Verify end-to-end MVP flow</name>
|
||
|
|
<files>cmd/netsynth/main.go</files>
|
||
|
|
<read_first>cmd/netsynth/main.go</read_first>
|
||
|
|
<action>
|
||
|
|
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.
|
||
|
|
</action>
|
||
|
|
<what-built>Complete NetSynth v1 MVP: live capture -> classification -> audio synthesis -> MP3 output with encoding feedback</what-built>
|
||
|
|
<how-to-verify>
|
||
|
|
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
|
||
|
|
</how-to-verify>
|
||
|
|
<verify>
|
||
|
|
<automated>cd /home/dev/workspace/yoloyolo && go build -o /tmp/netsynth-verify ./cmd/netsynth && echo "Binary builds OK"</automated>
|
||
|
|
</verify>
|
||
|
|
<acceptance_criteria>
|
||
|
|
- 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
|
||
|
|
</acceptance_criteria>
|
||
|
|
<done>User has verified the end-to-end MVP flow works with live traffic on loopback interface.</done>
|
||
|
|
<resume-signal>Type "approved" or describe issues</resume-signal>
|
||
|
|
</task>
|
||
|
|
|
||
|
|
</tasks>
|
||
|
|
|
||
|
|
<verification>
|
||
|
|
```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 '//'
|
||
|
|
```
|
||
|
|
</verification>
|
||
|
|
|
||
|
|
<success_criteria>
|
||
|
|
- 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
|
||
|
|
</success_criteria>
|
||
|
|
|
||
|
|
<output>
|
||
|
|
After completion, create `.planning/phases/03-pipeline-integration-and-mvp/03-02-SUMMARY.md`
|
||
|
|
</output>
|