Files
yoloyolo/.planning/phases/11-synthesis-and-config-layer/11-01-PLAN.md
T
2026-03-27 16:25:14 +01:00

225 lines
12 KiB
Markdown

---
phase: 11-synthesis-and-config-layer
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- synth/config.go
- classify/types.go
- synth/bank_test.go
- config/config_test.go
- classify/classifier_test.go
autonomous: true
requirements:
- GRP-02
- GRP-03
must_haves:
truths:
- "ClassFreqConfigs has exactly 35 entries matching AllClasses()"
- "Every new class has correct Hz, waveform, pan, and group from frequency allocation table"
- "LDAP, Kerberos, Syslog appear in AllClasses() and have Infrastructure group with Triangle waveform"
- "go test ./synth/... ./classify/... ./config/... all pass"
artifacts:
- path: "synth/config.go"
provides: "21 new ClassFreqConfigs entries"
contains: "classify.ClassIMAP"
- path: "classify/types.go"
provides: "AllClasses() returns 35 entries including LDAP/Kerberos/Syslog"
contains: "ClassLDAP"
key_links:
- from: "synth/config.go"
to: "classify/types.go"
via: "ClassFreqConfigs references TrafficClass constants"
pattern: "classify\\.Class(IMAP|POP3|SMTPSub|RDP|Telnet|VNC|FTP|SMB|TFTP|MySQL|PostgreSQL|Redis|MongoDB|SIP|QUIC|MDNS|SSDP|SNMP|LDAP|Kerberos|Syslog)"
---
<objective>
Add all 21 missing ClassFreqConfigs entries and update AllClasses() to include LDAP/Kerberos/Syslog, then fix every hardcoded count assertion across synth, config, and classify test files.
Purpose: This is the foundational data layer for Phase 11 -- all subsequent work (PrintConfig group headers, TOML [groups]) depends on all 35 classes having complete synthesis configs.
Output: synth/config.go with 35 ClassFreqConfigs entries, classify/types.go with 35-entry AllClasses(), all count-based tests green.
</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
@synth/config.go
@classify/types.go
@synth/bank_test.go
@synth/config_test.go
@config/config_test.go
@classify/classifier_test.go
<interfaces>
<!-- Key types and contracts the executor needs -->
From classify/types.go:
```go
type TrafficClass string
// Constants: ClassIMAP, ClassPOP3, ClassSMTPSub, ClassFTP, ClassSMB, ClassTFTP,
// ClassRDP, ClassTelnet, ClassVNC, ClassMySQL, ClassPostgreSQL, ClassRedis,
// ClassMongoDB, ClassMDNS, ClassSSDP, ClassSNMP, ClassSIP, ClassQUIC,
// ClassLDAP, ClassKerberos, ClassSyslog
func AllClasses() []TrafficClass
```
From synth/config.go:
```go
type FreqConfig struct {
BaseHz float64
Harmonics []HarmonicDef
Pan float64
WaveformType WaveformType
Group string
}
var ClassFreqConfigs = map[classify.TrafficClass]FreqConfig{...}
func WaveformPresetHarmonics(wt WaveformType, baseHz float64, sampleRate int) []HarmonicDef
```
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Add 21 ClassFreqConfigs entries and update AllClasses()</name>
<files>synth/config.go, classify/types.go</files>
<read_first>synth/config.go, classify/types.go</read_first>
<action>
**synth/config.go** -- Add 21 new entries to the ClassFreqConfigs map, after the existing entries and before the closing brace. Use WaveformPresetHarmonics() for all entries (per D-01). The exact values from the frequency allocation table (lines 74-110 of synth/config.go):
```
// --- Infrastructure additions (Triangle, 93-118 Hz) ---
classify.ClassMDNS: {BaseHz: 93.0, WaveformType: WaveformTriangle, Harmonics: WaveformPresetHarmonics(WaveformTriangle, 93.0, SampleRate), Pan: 0.3, Group: "Infrastructure"}
classify.ClassSSDP: {BaseHz: 105.0, WaveformType: WaveformTriangle, Harmonics: WaveformPresetHarmonics(WaveformTriangle, 105.0, SampleRate), Pan: -0.2, Group: "Infrastructure"}
classify.ClassSNMP: {BaseHz: 118.0, WaveformType: WaveformTriangle, Harmonics: WaveformPresetHarmonics(WaveformTriangle, 118.0, SampleRate), Pan: 0.2, Group: "Infrastructure"}
// --- Web addition (Sawtooth, 190 Hz) ---
classify.ClassQUIC: {BaseHz: 190.0, WaveformType: WaveformSawtooth, Harmonics: WaveformPresetHarmonics(WaveformSawtooth, 190.0, SampleRate), Pan: -0.2, Group: "Web"}
// --- Mail additions (Triangle, 241-305 Hz) ---
classify.ClassIMAP: {BaseHz: 241.0, WaveformType: WaveformTriangle, Harmonics: WaveformPresetHarmonics(WaveformTriangle, 241.0, SampleRate), Pan: 0.3, Group: "Mail"}
classify.ClassPOP3: {BaseHz: 271.0, WaveformType: WaveformTriangle, Harmonics: WaveformPresetHarmonics(WaveformTriangle, 271.0, SampleRate), Pan: 0.4, Group: "Mail"}
classify.ClassSMTPSub: {BaseHz: 305.0, WaveformType: WaveformTriangle, Harmonics: WaveformPresetHarmonics(WaveformTriangle, 305.0, SampleRate), Pan: 0.5, Group: "Mail"}
// --- Remote Access additions (Square, 385-485 Hz) ---
classify.ClassRDP: {BaseHz: 385.0, WaveformType: WaveformSquare, Harmonics: WaveformPresetHarmonics(WaveformSquare, 385.0, SampleRate), Pan: -0.6, Group: "Remote Access"}
classify.ClassTelnet: {BaseHz: 432.0, WaveformType: WaveformSquare, Harmonics: WaveformPresetHarmonics(WaveformSquare, 432.0, SampleRate), Pan: -0.5, Group: "Remote Access"}
classify.ClassVNC: {BaseHz: 485.0, WaveformType: WaveformSquare, Harmonics: WaveformPresetHarmonics(WaveformSquare, 485.0, SampleRate), Pan: -0.4, Group: "Remote Access"}
// --- File Transfer additions (Square, 545-687 Hz) ---
classify.ClassFTP: {BaseHz: 545.0, WaveformType: WaveformSquare, Harmonics: WaveformPresetHarmonics(WaveformSquare, 545.0, SampleRate), Pan: 0.5, Group: "File Transfer"}
classify.ClassSMB: {BaseHz: 612.0, WaveformType: WaveformSquare, Harmonics: WaveformPresetHarmonics(WaveformSquare, 612.0, SampleRate), Pan: 0.6, Group: "File Transfer"}
classify.ClassTFTP: {BaseHz: 687.0, WaveformType: WaveformSquare, Harmonics: WaveformPresetHarmonics(WaveformSquare, 687.0, SampleRate), Pan: 0.7, Group: "File Transfer"}
// --- Database additions (Sawtooth, 1543-2182 Hz) ---
classify.ClassMySQL: {BaseHz: 1543.0, WaveformType: WaveformSawtooth, Harmonics: WaveformPresetHarmonics(WaveformSawtooth, 1543.0, SampleRate), Pan: -0.4, Group: "Database"}
classify.ClassPostgreSQL: {BaseHz: 1732.0, WaveformType: WaveformSawtooth, Harmonics: WaveformPresetHarmonics(WaveformSawtooth, 1732.0, SampleRate), Pan: -0.2, Group: "Database"}
classify.ClassRedis: {BaseHz: 1944.0, WaveformType: WaveformSawtooth, Harmonics: WaveformPresetHarmonics(WaveformSawtooth, 1944.0, SampleRate), Pan: 0.2, Group: "Database"}
classify.ClassMongoDB: {BaseHz: 2182.0, WaveformType: WaveformSawtooth, Harmonics: WaveformPresetHarmonics(WaveformSawtooth, 2182.0, SampleRate), Pan: 0.4, Group: "Database"}
// --- VoIP (Sine, 2449 Hz) ---
classify.ClassSIP: {BaseHz: 2449.0, WaveformType: WaveformSine, Harmonics: WaveformPresetHarmonics(WaveformSine, 2449.0, SampleRate), Pan: 0.0, Group: "VoIP"}
// --- Infrastructure auto-assigned (Triangle, 2950-3250 Hz) per D-02 ---
classify.ClassLDAP: {BaseHz: 2950.0, WaveformType: WaveformTriangle, Harmonics: WaveformPresetHarmonics(WaveformTriangle, 2950.0, SampleRate), Pan: -0.2, Group: "Infrastructure"}
classify.ClassKerberos: {BaseHz: 3250.0, WaveformType: WaveformTriangle, Harmonics: WaveformPresetHarmonics(WaveformTriangle, 3250.0, SampleRate), Pan: 0.0, Group: "Infrastructure"}
classify.ClassSyslog: {BaseHz: 3050.0, WaveformType: WaveformTriangle, Harmonics: WaveformPresetHarmonics(WaveformTriangle, 3050.0, SampleRate), Pan: 0.2, Group: "Infrastructure"}
```
Place new entries in the map grouped by family with section comments matching the existing pattern (e.g., `// --- Infrastructure additions ...`). Insert them logically:
- Infrastructure additions (mDNS, SSDP, SNMP) after ClassDHCP and before ClassDNS (since 93/105/118 Hz come between DHCP=82 and DNS=133)
- Web addition (QUIC) after ClassHTTP
- Mail additions after ClassSMTP
- Remote Access additions after ClassSSH
- File Transfer after Unknown entries
- Database after Unknown entries
- VoIP after Database
- LDAP/Kerberos/Syslog at end (auto-assigned range)
**classify/types.go** -- Per D-02 and D-03:
1. Add ClassLDAP, ClassKerberos, ClassSyslog to AllClasses() in the Infrastructure section, after ClassSNMP.
2. Remove the comment "Excludes ClassLDAP, ClassKerberos, and ClassSyslog" from the AllClasses() doc comment.
3. Update the doc comment to say "AllClasses returns all known traffic classes in display order."
4. The AllClasses() function should now return 35 entries total.
</action>
<verify>
<automated>cd /home/dev/workspace/yoloyolo && go build ./synth/... ./classify/...</automated>
</verify>
<acceptance_criteria>
- `grep -c "classify\.Class" synth/config.go` shows at least 35 occurrences in ClassFreqConfigs
- `grep "ClassLDAP" classify/types.go` appears in AllClasses() return slice
- `grep "ClassKerberos" classify/types.go` appears in AllClasses() return slice
- `grep "ClassSyslog" classify/types.go` appears in AllClasses() return slice
- `go build ./synth/... ./classify/...` succeeds
</acceptance_criteria>
<done>ClassFreqConfigs has 35 entries with correct Hz/waveform/pan/group values. AllClasses() returns 35 entries including LDAP/Kerberos/Syslog. Both packages compile.</done>
</task>
<task type="auto">
<name>Task 2: Fix all hardcoded count assertions in tests</name>
<files>synth/bank_test.go, config/config_test.go, classify/classifier_test.go</files>
<read_first>synth/bank_test.go, config/config_test.go, classify/classifier_test.go</read_first>
<action>
Update all hardcoded count assertions to reflect the new 35-entry state. Per research Pitfall 1 and Pitfall 2:
**synth/bank_test.go:**
- Line 10: Rename `TestNewBankHas14Layers` to `TestNewBankHasAllLayers`
- Line 12: Change `len(b.layers) != 14` to `len(b.layers) != len(classify.AllClasses())`
- Line 13: Change `want 14` to a dynamic message using `len(classify.AllClasses())`
**config/config_test.go:**
- Line 120: Change `len(cfgs) != 14` to `len(cfgs) != len(classify.AllClasses())` in TestLoadNoConfig
- Line 149: Change `len(cfgs) != 14` to `len(cfgs) != len(classify.AllClasses())` in TestLoadUnknownClass
- Line 180: Change `len(cfgs) != 14` to `len(cfgs) != len(classify.AllClasses())` in TestLoadAllDefaultsPresent
- Line 617: Change `len(result.FreqCfgs) != 14` to `len(result.FreqCfgs) != len(classify.AllClasses())` in TestLoadNoConfigReturnsLoadResult
- Lines 407-416 in TestPrintConfigContainsAllClasses: Replace the hardcoded `classNames` slice with a loop over `classify.AllClasses()`. Change to:
```go
for _, cls := range classify.AllClasses() {
if !strings.Contains(output, string(cls)) {
t.Errorf("PrintConfig output missing class %q", cls)
}
}
```
**classify/classifier_test.go:**
- Line 459 (approximately): Change `want 32` to `want 35` in TestAllClassesCount. Update the assertion value from 32 to 35.
Use `len(classify.AllClasses())` for dynamic counts wherever possible (synth and config tests). For classifier_test.go, use the literal 35 since the test is specifically verifying the count is a known value per D-03.
</action>
<verify>
<automated>cd /home/dev/workspace/yoloyolo && go test ./synth/... ./classify/... ./config/...</automated>
</verify>
<acceptance_criteria>
- `go test ./synth/...` passes (0 failures)
- `go test ./classify/...` passes (0 failures)
- `go test ./config/...` passes (0 failures)
- `grep "14" synth/bank_test.go` returns no lines with hardcoded layer counts
- `grep 'want 14' config/config_test.go` returns no matches
- `grep 'want 32' classify/classifier_test.go` returns no matches
</acceptance_criteria>
<done>All test suites pass with 35 classes. No hardcoded counts of 14 or 32 remain in test assertions. TestNewBankHas14Layers renamed to TestNewBankHasAllLayers.</done>
</task>
</tasks>
<verification>
```bash
cd /home/dev/workspace/yoloyolo && go test ./...
```
All tests pass. ClassFreqConfigs has 35 entries matching AllClasses().
</verification>
<success_criteria>
- `go test ./...` passes fully
- ClassFreqConfigs map has exactly 35 entries
- AllClasses() returns exactly 35 entries
- No hardcoded counts of 14 or 32 remain in test files
- Every new entry uses WaveformPresetHarmonics() (not hand-tuned harmonics)
- LDAP/Kerberos/Syslog have Group="Infrastructure" and WaveformTriangle
</success_criteria>
<output>
After completion, create `.planning/phases/11-synthesis-and-config-layer/11-01-SUMMARY.md`
</output>