6.1 KiB
6.1 KiB
Phase 3: Pipeline Integration and MVP - Context
Gathered: 2026-03-26 Status: Ready for planning
## Phase BoundaryWire the capture-classify-aggregate pipeline into the audio synthesis and MP3 encoding engine. Deliver the complete v1 MVP: netsynth -i eth0 -o out.mp3 → capture → Ctrl+C → valid MP3. Replace the single "unknown" traffic class with 4 hash-bucketed sub-clusters, each producing a distinct dissonant drone tone.
Auto-clustering
- D-01: Hash-bucketing approach — deterministic hash of (dst_port, protocol) into 4 fixed buckets. No k-means, no warmup period, no external dependency.
- D-02: 4 buckets for unknown traffic. Total layer count becomes 14 (10 known + 4 unknown buckets).
- D-03: Hash-bucketing happens inside the existing classifier — classifier returns
unknown-1throughunknown-4instead of plainunknown. New TrafficClass constants added. - D-04: Replace
ClassUnknownentirely withClassUnknown1-ClassUnknown4.AllClasses()returns 14 classes. Clean break, no fallback.
Cluster tone assignment
- D-05: All 4 unknown buckets live in a dedicated 850-1100 Hz dissonant range, above the known protocol range (60-800 Hz). Slightly detuned intervals between them.
- D-06: Same dissonant harmonic character for all 4 buckets (matching Phase 2's D-04/D-06 for the original "unknown" tone). Differentiated by pitch only, preserving the "something foreign" sonic identity.
Shutdown & encoding feedback
- D-07: Status line during encoding: print
Encoding N windows to <path>...thenSaved <path> (Xs, N KB, encoded in Xs)on completion. Minimal but confirms encoding is happening. - D-08: Protocol summary prints BEFORE encoding. Ctrl+C → protocol summary → "Encoding..." → "Saved". User sees capture stats immediately.
Post-run output
- D-09: Saved line includes path, audio duration, file size, and encoding time. e.g.,
Saved out.mp3 (12.5s, 198 KB, encoded in 0.3s). - D-10: Unknown buckets appear in the protocol summary naturally as
unknown-1: 42, unknown-2: 17, etc. Falls out of existing summary logic since they're full TrafficClass values.
Claude's Discretion
- Exact hash function for port/protocol → bucket mapping (within the 4-bucket constraint)
- Exact Hz values for the 4 unknown bucket tones (within 850-1100 Hz range, detuned intervals)
- GainPerLayer recalculation for 14 layers (was 1/11 for 11 layers)
- Stereo panning positions for the 4 unknown buckets
- Encoding time measurement implementation
<canonical_refs>
Canonical References
Downstream agents MUST read these before planning or implementing.
Project context
.planning/PROJECT.md— Core value, constraints (Go, MP3 output, non-interactive).planning/REQUIREMENTS.md— CAPT-03 (graceful flush), CLAS-02 (auto-clustering)
Prior phase context
.planning/phases/01-capture-and-classification/01-CONTEXT.md— Phase 1 decisions; classifier design, pipeline architecture.planning/phases/02-audio-synthesis-engine/02-CONTEXT.md— Phase 2 decisions; tone mapping, mixing, encoding
Key source files (integration points)
cmd/netsynth/main.go— CLI wiring with TODO(phase-3) at line 99; accumulator loop at lines 92-97encode/mp3.go—RunSynthesis()andEncodeMP3()— the synthesis entry pointsynth/bank.go—OscillatorBankandRenderWindow()— renders audio from WindowSnapshotssynth/config.go—ClassFreqConfigsmap,NumLayers,GainPerLayerconstantsclassify/types.go—TrafficClassconstants,AllClasses(),WindowSnapshotstructclassify/rules.go— Classification rules (first-match-wins ordered slice)classify/classifier.go—Classify()method where hash-bucketing will be addedaggregate/summary.go—PrintSummary()andPrintWindowLine()for exit output
Stack decisions
CLAUDE.md— Technology stack section;muesli/kmeanslisted but NOT needed (hash-bucketing chosen instead)
</canonical_refs>
<code_context>
Existing Code Insights
Reusable Assets
encode.RunSynthesis([]WindowSnapshot, string) error— already takes snapshots and produces MP3; main.go just needs to call itsynth.NewBank(tau)/bank.RenderWindow(snap)— synthesis engine already works with WindowSnapshotclassify.NewClassifier(rules)/classifier.Classify(pkt)— classifier where hash-bucketing logic will be addedaggregate.PrintSummary(io.Writer, map[TrafficClass]int64)— exit summary; will naturally show unknown-1 through unknown-4
Established Patterns
- Channel-based pipeline: capture → classify → aggregate → (synthesis goes here)
done <-chan struct{}for shutdown signaling viasignal.NotifyContext- Buffered channels for stage decoupling (512-buffer capture, 1024-buffer classified, 8-buffer aggregate)
io.Writerinjection for testable output (aggregate/summary.go)- Config-driven classifier with ordered
[]Ruleslice, first-match-wins
Integration Points
main.go:99— Replace_ = collectedSnapshotswithencode.RunSynthesis(collectedSnapshots, outputPath)callmain.go:92-97— Snapshot accumulator loop already collects snapshots; wiring is mechanicalsynth/config.go—ClassFreqConfigsmap needs 4 new entries for unknown buckets;NumLayersandGainPerLayerneed updating from 11 to 14classify/types.go— RemoveClassUnknown, addClassUnknown1-ClassUnknown4, updateAllClasses()
</code_context>
## Specific Ideas- The end-to-end flow should feel like tcpdump: start capturing, see activity, Ctrl+C, get your results
- Unknown bucket tones should be immediately recognizable as "something foreign" — the dissonant character is the sonic signal, pitch differentiates which bucket
- The protocol summary + saved line should give the user everything they need at a glance without running ffprobe
None — discussion stayed within phase scope
Phase: 03-pipeline-integration-and-mvp Context gathered: 2026-03-26