Files
yoloyolo/.planning/milestones/v1.2-phases/11-synthesis-and-config-layer/11-RESEARCH.md
T
gurixandClaude Opus 4.6 494385b528 chore: archive v1.2 milestone — Extended Protocol Coverage
35 traffic classes across 9 protocol families shipped. Archives
ROADMAP, REQUIREMENTS, and phase directories to milestones/v1.2-*.
Updates README with new protocol families, sound design table,
and [groups] TOML config documentation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 16:48:53 +01:00

413 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase 11: Synthesis and Config Layer - Research
**Researched:** 2026-03-27
**Domain:** Go — synth config completion, PrintConfig group headers, TOML [groups] table
**Confidence:** HIGH
## Summary
Phase 11 completes the synthesis layer for all 35 traffic classes and adds group-aware output to `PrintConfig`. The work is entirely within `synth/config.go`, `config/config.go`, and `classify/types.go` — no new packages, no external dependencies beyond what is already in go.mod. The frequency allocation table in `synth/config.go` lines 74110 is the authoritative source of truth for all Hz, waveform, group, and pan values for the 18 table-designed classes. LDAP, Kerberos, and Syslog get their Hz values from `autoAssignFreq` (verified by running the FNV hash: LDAP=2950 Hz, Kerberos=3250 Hz, Syslog=3050 Hz) and use Triangle waveform / Infrastructure group, matching existing Infrastructure family members.
The test suite is currently broken in `config` and `synth` packages because `ClassFreqConfigs` only has 14 entries while `AllClasses()` returns 32. After Phase 11, `AllClasses()` returns 35 and `ClassFreqConfigs` must match exactly. Multiple existing test hardcodes (`want 14`, `TestNewBankHas14Layers`, `TestAllClassesCount want 32`) need updating to 35.
The `[groups]` TOML feature requires adding a `Groups map[string]string` field to `rawConfig`, wiring it through `Load()`, storing the reassignments in `LoadResult`, applying them in `PrintConfig`, and adding a warning for unknown class names. No existing config pipeline stages need structural changes — group reassignment is a post-merge overlay on FreqConfig.Group fields.
**Primary recommendation:** Execute in three sequential sub-tasks: (1) add 21 ClassFreqConfigs entries + update AllClasses() + fix count tests, (2) refactor PrintConfig for group headers, (3) add [groups] TOML support + update tests.
---
<user_constraints>
## User Constraints (from CONTEXT.md)
### Locked Decisions
- **D-01:** Add ClassFreqConfigs entries for all 18 new classes currently in AllClasses(). Use Hz values, waveforms, pans, and groups from the Phase 9 frequency allocation table comment in `synth/config.go` lines 74-110. Use `WaveformPresetHarmonics()` for all new entries (not hand-tuned harmonics).
- **D-02:** Add ClassLDAP, ClassKerberos, ClassSyslog to AllClasses() in `classify/types.go`. Create ClassFreqConfigs entries for them using `autoAssignFreq`-derived Hz values (FNV hash in [2500, 4000] Hz range). Their group is "Infrastructure", waveform is Triangle (matching the Infrastructure family pattern).
- **D-03:** After D-01 and D-02, AllClasses() returns 35 entries (32 + 3). TestAllClassesCount updated to 35.
- **D-04:** PrintConfig groups classes by their Group field value. Each group gets a comment header line: `# <Group>` followed by a blank line, then all classes in that group. Groups are ordered: Infrastructure, Web, Mail, Remote Access, File Transfer, Database, Discovery, VoIP, Unknown.
- **D-05:** Within each group, classes are ordered by ascending BaseHz (matching the frequency allocation table order).
- **D-06:** User-defined classes (not in AllClasses but in FreqCfgs) are emitted after all built-in groups under a "# User-defined" section header.
- **D-07:** Users define group reassignments in TOML with a `[groups]` table using simple key-value pairs: `IMAP = "Web"` reassigns IMAP from Mail to Web group. The key is the TrafficClass string value, the value is the target group name.
- **D-08:** Group reassignment only affects `--print-config` output grouping and the Group field in FreqConfig. It does NOT change frequency, waveform, or pan — those stay as designed. PrintConfig reflects the reassignment.
- **D-09:** Unknown group names in `[groups]` config are accepted (user can invent custom group names). Unknown class names produce a warning (same pattern as `[sounds.X]` with unknown class).
### Claude's Discretion
- Exact Hz values for LDAP, Kerberos, Syslog (computed from autoAssignFreq FNV hash)
- Pan positions for LDAP, Kerberos, Syslog
- Test structure for new ClassFreqConfigs entries and PrintConfig group output
- Whether to add `[groups]` to rawConfig struct as `map[string]string` or a custom type
- How to handle group reassignment in the merge/load pipeline
### Deferred Ideas (OUT OF SCOPE)
None — discussion stayed within phase scope.
</user_constraints>
---
<phase_requirements>
## Phase Requirements
| ID | Description | Research Support |
|----|-------------|------------------|
| GRP-02 | `--print-config` output organizes classes by group with section headers | PrintConfig refactor: iterate canonical group order, emit `# <Group>` header + blank line, sort within group by BaseHz ascending; user-defined classes under `# User-defined` |
| GRP-03 | Users can define `[groups]` in TOML config to reassign protocols to different sound families | Add `Groups map[string]string` to rawConfig; apply after merge in Load(); warn on unknown class name; store in LoadResult; PrintConfig reads Group field from FreqConfig |
</phase_requirements>
---
## Standard Stack
Phase 11 uses only packages already in go.mod. No new dependencies.
### Core (already in go.mod)
| Library | Version | Purpose | Why Standard |
|---------|---------|---------|--------------|
| `github.com/BurntSushi/toml` | existing | TOML decode — `map[string]string` for `[groups]` table | Already used; `md.Undecoded()` handles unknown key detection |
| `github.com/netsynth/netsynth/classify` | local | TrafficClass constants, AllClasses() | Local package; phase modifies it |
| `github.com/netsynth/netsynth/synth` | local | FreqConfig, ClassFreqConfigs, WaveformPresetHarmonics | Local package; phase adds 21 entries |
**No installation needed.** `go test ./...` is the only verification command.
---
## Architecture Patterns
### Pattern 1: ClassFreqConfigs Entry Structure
**What:** Each entry in `ClassFreqConfigs` follows a strict pattern — BaseHz from the frequency table, WaveformType constant, Harmonics generated by `WaveformPresetHarmonics`, Pan from the table, Group string.
**When to use:** For all 18 table-designed new entries (D-01) and 3 auto-assigned entries (D-02).
**Example (from existing code):**
```go
classify.ClassIMAP: {
BaseHz: 241.0,
WaveformType: WaveformTriangle,
Harmonics: WaveformPresetHarmonics(WaveformTriangle, 241.0, SampleRate),
Pan: 0.3,
Group: "Mail",
},
```
### Pattern 2: autoAssignFreq for LDAP/Kerberos/Syslog
**What:** FNV-32a hash into [2500, 4000] Hz range. Values are deterministic — computed once and hardcoded in the entry.
**Verified computed values:**
- LDAP: 2950.0 Hz
- Kerberos: 3250.0 Hz
- Syslog: 3050.0 Hz
Pan positions (Claude's discretion): assign spread within [-0.3, 0.3] range — LDAP=-0.2, Kerberos=0.0, Syslog=0.2 keeps them center-ish (Infrastructure family is already spread -0.3 to +0.3 at low frequencies; these are high-frequency so center-cluster is appropriate).
### Pattern 3: AllClasses() Update (classify/types.go)
**What:** Add ClassLDAP, ClassKerberos, ClassSyslog inside the Infrastructure block in AllClasses(). The comment guard `// D-01: no ClassFreqConfigs until Phase 11` is removed.
**Ordering within Infrastructure:** Append after ClassSNMP (highest-Hz member at 118 Hz) since LDAP/Kerberos/Syslog are at 2950/3250/3050 Hz — technically they sort higher but within their family group they follow table order.
### Pattern 4: PrintConfig Group-Ordered Iteration (GRP-02)
**What:** Replace the current flat `classify.AllClasses()` iteration with a canonical-group-ordered iteration. Build a `groupOrder []string` slice with the 9 canonical groups; for each group collect classes from AllClasses() whose `FreqCfgs[cls].Group == group`, sort by BaseHz, emit group header + entries.
**Canonical group order (D-04):**
```
Infrastructure, Web, Mail, Remote Access, File Transfer, Database, Discovery, VoIP, Unknown
```
**Implementation approach:**
```go
// groupOrder defines canonical display order for --print-config
var groupOrder = []string{
"Infrastructure", "Web", "Mail", "Remote Access",
"File Transfer", "Database", "Discovery", "VoIP", "Unknown",
}
// Build group -> []TrafficClass index from AllClasses()
// Sort each group slice by FreqCfgs[cls].BaseHz ascending (D-05)
// For each group: emit "# <group>\n\n" then entries
// After all built-in groups: user-defined classes under "# User-defined\n\n"
```
**Critical detail:** The sort must use `result.FreqCfgs[cls].BaseHz` (the effective config, post-merge) not `synth.ClassFreqConfigs[cls].BaseHz`, so user frequency overrides are reflected in the sort order. This is unlikely to matter in practice but is correct.
### Pattern 5: TOML [groups] Support (GRP-03)
**rawConfig struct extension:**
```go
type rawConfig struct {
Sounds map[string]SoundOverride `toml:"sounds"`
Rules []RawRule `toml:"rules"`
Groups map[string]string `toml:"groups"` // key=TrafficClass string, val=group name
}
```
Using `map[string]string` (Claude's discretion) is the simplest approach — TOML decodes `[groups]` as a string map naturally. No custom type needed.
**LoadResult extension:**
```go
type LoadResult struct {
FreqCfgs map[classify.TrafficClass]synth.FreqConfig
UserRules []classify.Rule
ConfigPath string
AutoClasses map[classify.TrafficClass]bool
GroupOverrides map[classify.TrafficClass]string // NEW: class -> reassigned group name
}
```
**applyGroupOverrides function (new):** After `merge()` in `Load()`, iterate `raw.Groups`; for each key=className, val=groupName: if className is known (exists in freqCfgs), update `cfg.Group = groupName` and store back; if unknown, emit warning to stderr (D-09). The warning uses the same pattern as unknown [sounds.X] classes.
**Alternative approach:** Apply group reassignment directly inside PrintConfig by reading `result.GroupOverrides` map on-the-fly without mutating FreqConfig. This avoids touching LoadResult but makes PrintConfig depend on a new field anyway. Mutating FreqConfig.Group is cleaner because classAnnotation and other consumers see a consistent view.
### Pattern 6: TOML md.Undecoded() and the [groups] map
**Critical:** `BurntSushi/toml` decodes map fields without flagging individual map keys as "undecoded" — unknown class names in `[groups]` will NOT be caught by `md.Undecoded()`. This is the same behavior as `[sounds.*]` (unknown class names are silently accepted at decode time and validated in `merge()`). The warning for unknown class names in `[groups]` must be implemented in the new `applyGroupOverrides` function, not in `parseFile`. This matches D-09 exactly.
### Anti-Patterns to Avoid
- **Hand-computing harmonics for new entries:** Use `WaveformPresetHarmonics()` — don't write `[]HarmonicDef{{1, 1.0}, {2, 0.5}, ...}` manually for table-designed entries.
- **Sorting AllClasses() output by BaseHz globally:** The table order in AllClasses() is the authoritative display order within families. The sort in PrintConfig must use group-then-BaseHz, not a flat sort.
- **Adding [groups] validation to parseFile:** `md.Undecoded()` cannot catch unknown string map keys; validation must be in `applyGroupOverrides`.
- **Mutating synth.ClassFreqConfigs:** copyDefaults() produces a working copy; all mutations go there, never to the package-level map.
---
## Don't Hand-Roll
| Problem | Don't Build | Use Instead | Why |
|---------|-------------|-------------|-----|
| Harmonic series for new waveform entries | Manual `[]HarmonicDef` slices | `WaveformPresetHarmonics(wt, baseHz, SampleRate)` | Already computes bandlimited series; manual values would drift if SampleRate changes |
| FNV hash for LDAP/Kerberos/Syslog Hz | Custom hash | `autoAssignFreq("LDAP")` output hardcoded as 2950.0 | Values are pre-computed; hardcoding eliminates runtime dependency on config package from synth package |
| TOML group reassignment validation | Custom struct with Validate() | `map[string]string` + warning in applyGroupOverrides | Matches existing pattern for [sounds.*] unknown classes |
---
## Runtime State Inventory
Step 2.5: SKIPPED — this is not a rename/refactor/migration phase.
---
## Environment Availability Audit
Step 2.6: SKIPPED — phase is purely code changes within existing Go packages. No external tools, services, databases, or CLI utilities beyond the existing `go` toolchain are required. The Go toolchain is already confirmed working (tests ran above).
---
## Common Pitfalls
### Pitfall 1: Stale hardcoded counts in tests
**What goes wrong:** `TestNewBankHas14Layers` expects 14 layers; `TestLoadNoConfig`/`TestLoadUnknownClass`/`TestLoadAllDefaultsPresent`/`TestLoadNoConfigReturnsLoadResult` all assert `len(cfgs) == 14`; `TestAllClassesCount` asserts 32. After adding 21 ClassFreqConfigs entries and 3 AllClasses entries these fail.
**Why it happens:** Tests were written against the Phase 9 baseline of 14 built-in entries and never updated.
**How to avoid:** Update all hardcoded count assertions to 35 in a single pass after D-01 and D-02 are complete. The specific files and line numbers:
- `synth/bank_test.go:12``want 14``want 35`
- `synth/bank_test.go:13``len(b.layers) != 14``!= 35`
- `config/config_test.go:120``want 14``want 35`
- `config/config_test.go:149``want 14 (BOGUS should not appear)``want 35`
- `config/config_test.go:185``want 14``want 35`
- `config/config_test.go:617``want 14``want 35`
- `classify/classifier_test.go:459``want 32``want 35`
**Warning signs:** Any test that hardcodes a count of 14 or 32.
### Pitfall 2: TestPrintConfigContainsAllClasses class list is stale
**What goes wrong:** `config/config_test.go:407416` lists exactly 14 class name strings. After Phase 11, AllClasses() has 35 entries; the test should either check all 35 or be replaced by a loop over `classify.AllClasses()`.
**How to avoid:** Replace the hardcoded `classNames` slice with `for _, cls := range classify.AllClasses()` during PrintConfig test updates.
### Pitfall 3: PrintConfig group-sort uses default Hz, not effective Hz
**What goes wrong:** If group-sort code reads `synth.ClassFreqConfigs[cls].BaseHz` instead of `result.FreqCfgs[cls].BaseHz`, user frequency overrides don't affect sort position. The output is still correct for default configs but fails when the user overrides a frequency in [sounds.*].
**How to avoid:** Always sort from `result.FreqCfgs[cls].BaseHz`.
### Pitfall 4: [groups] map keys not caught by md.Undecoded()
**What goes wrong:** `parseFile` uses `md.Undecoded()` to catch field typos. TOML map keys are all valid decode targets by definition — `md.Undecoded()` will be empty even if `[groups]` contains `IMAPtypo = "Web"`. The warning must come from `applyGroupOverrides`.
**How to avoid:** Implement unknown-class warning in `applyGroupOverrides`, not in `parseFile` or `validate`.
### Pitfall 5: TestClassFreqConfigsMatchAllClasses fails if counts diverge
**What goes wrong:** `synth/config_test.go:57` checks `len(synth.ClassFreqConfigs) == len(classify.AllClasses())`. If the implementer adds all 21 ClassFreqConfigs entries but only adds 2 of the 3 new AllClasses entries, this test fails in a confusing way.
**How to avoid:** D-01 and D-02 must be completed atomically — add all 21 ClassFreqConfigs entries and all 3 AllClasses entries in a single task.
### Pitfall 6: "File Transfer" group name has a space
**What goes wrong:** If the group sort map uses `"FileTransfer"` instead of `"File Transfer"` (matching the FreqConfig.Group string), Discovery and File Transfer classes end up under a catch-all or dropped entirely.
**How to avoid:** All group strings must exactly match those in `FreqConfig.Group`. Canonical list: `"Infrastructure"`, `"Web"`, `"Mail"`, `"Remote Access"`, `"File Transfer"`, `"Database"`, `"Discovery"`, `"VoIP"`, `"Unknown"`.
---
## Code Examples
### Complete frequency table for all 21 missing entries
From the allocation table in `synth/config.go` lines 74110 plus D-02 computed values:
```
// Infrastructure additions (Triangle, [93-118] Hz)
mDNS: 93 Hz, Triangle, pan=+0.3
SSDP: 105 Hz, Triangle, pan=-0.2
SNMP: 118 Hz, Triangle, pan=+0.2
// Web addition (Sawtooth)
QUIC: 190 Hz, Sawtooth, pan=-0.2
// Mail additions (Triangle)
IMAP: 241 Hz, Triangle, pan=+0.3
POP3: 271 Hz, Triangle, pan=+0.4
SMTP-sub: 305 Hz, Triangle, pan=+0.5
// Remote Access additions (Square)
RDP: 385 Hz, Square, pan=-0.6
Telnet: 432 Hz, Square, pan=-0.5
VNC: 485 Hz, Square, pan=-0.4
// File Transfer additions (Square)
FTP: 545 Hz, Square, pan=+0.5
SMB: 612 Hz, Square, pan=+0.6
TFTP: 687 Hz, Square, pan=+0.7
// Database additions (Sawtooth)
MySQL: 1543 Hz, Sawtooth, pan=-0.4
PostgreSQL: 1732 Hz, Sawtooth, pan=-0.2
Redis: 1944 Hz, Sawtooth, pan=+0.2
MongoDB: 2182 Hz, Sawtooth, pan=+0.4
// VoIP (Sine)
SIP: 2449 Hz, Sine, pan=0.0
// Infrastructure auto-assigned (Triangle) — D-02
LDAP: 2950 Hz, Triangle, pan=-0.2
Kerberos: 3250 Hz, Triangle, pan=0.0
Syslog: 3050 Hz, Triangle, pan=+0.2
```
### PrintConfig group header pattern (D-04)
```go
// groupOrder is the canonical display order for --print-config section headers.
var groupOrder = []string{
"Infrastructure", "Web", "Mail", "Remote Access",
"File Transfer", "Database", "Discovery", "VoIP", "Unknown",
}
// In PrintConfig, replace the flat AllClasses() loop with:
builtinByGroup := map[string][]classify.TrafficClass{}
for _, cls := range classify.AllClasses() {
cfg := result.FreqCfgs[cls]
grp := cfg.Group
builtinByGroup[grp] = append(builtinByGroup[grp], cls)
}
// Sort each group slice by BaseHz ascending (D-05)
for grp := range builtinByGroup {
sort.Slice(builtinByGroup[grp], func(i, j int) bool {
return result.FreqCfgs[builtinByGroup[grp][i]].BaseHz <
result.FreqCfgs[builtinByGroup[grp][j]].BaseHz
})
}
// Emit in canonical order
for _, grp := range groupOrder {
classes, ok := builtinByGroup[grp]
if !ok || len(classes) == 0 {
continue
}
fmt.Fprintf(&sb, "# %s\n\n", grp)
for _, cls := range classes {
// ... existing per-class emit logic ...
}
}
```
### [groups] TOML apply pattern (D-07/D-08/D-09)
```go
// applyGroupOverrides overlays [groups] reassignments onto freqCfgs.Group in-place.
// Unknown class names produce a warning; unknown group names are silently accepted (D-09).
func applyGroupOverrides(cfgs map[classify.TrafficClass]synth.FreqConfig, groups map[string]string) {
for className, groupName := range groups {
cls := classify.TrafficClass(className)
cfg, known := cfgs[cls]
if !known {
fmt.Fprintf(os.Stderr, "Warning: config: [groups]: unknown class %q (ignored)\n", className)
continue
}
cfg.Group = groupName
cfgs[cls] = cfg
}
}
```
Call site in `Load()` — after `merge()`:
```go
addAutoFreqEntries(freqCfgs, userRules, autoClasses)
merge(freqCfgs, raw.Sounds)
applyGroupOverrides(freqCfgs, raw.Groups) // NEW
```
---
## Validation Architecture
nyquist_validation is enabled (not false in config.json).
### Test Framework
| Property | Value |
|----------|-------|
| Framework | Go testing (stdlib) |
| Config file | none — `go test ./...` |
| Quick run command | `go test ./classify/... ./synth/... ./config/...` |
| Full suite command | `go test ./...` |
### Current Test Failures (baseline — must fix)
The following tests are currently failing and Phase 11 must make them green:
| Test | Package | Failure Cause | Fix Required |
|------|---------|---------------|--------------|
| `TestAllClassesHaveConfig` | `synth` | 18 new classes in AllClasses() missing from ClassFreqConfigs | Add 18+3 entries |
| `TestClassFreqConfigsMatchAllClasses` | `synth` | 14 != 32 | Add entries, update AllClasses |
| `TestNewBankHas14Layers` | `synth` | 14 != 32 + missing layers | Add entries + update assert |
| `TestLoadAllDefaultsPresent` | `config` | 18 classes missing from loaded map | Add ClassFreqConfigs entries |
| `TestLoadNoConfig` | `config` | len==14 assert, now 35 | Update count |
| `TestLoadUnknownClass` | `config` | len==14 assert | Update count |
| `TestLoadNoConfigReturnsLoadResult` | `config` | len==14 assert | Update count |
| `TestPrintConfigContainsAllClasses` | `config` | Hardcoded 14 class names | Expand to all 35 |
| `TestAllClassesCount` | `classify` | want 32, still passes (no new classes yet) | Update to 35 after D-02 |
### Phase Requirements to Test Map
| Req ID | Behavior | Test Type | Automated Command | Test Exists? |
|--------|----------|-----------|-------------------|-------------|
| GRP-02 | PrintConfig shows group headers | unit | `go test ./config/... -run TestPrintConfig` | Partial — `TestPrintConfigContainsAllClasses` exists; new `TestPrintConfigGroupHeaders` needed |
| GRP-02 | Each built-in class appears in correct group | unit | `go test ./config/... -run TestPrintConfigGroupHeaders` | No — Wave 0 |
| GRP-03 | [groups] TOML reassigns class to different group | unit | `go test ./config/... -run TestLoadGroupOverride` | No — Wave 0 |
| GRP-03 | Unknown class in [groups] produces warning | unit | `go test ./config/... -run TestLoadGroupUnknownClass` | No — Wave 0 |
| GRP-03 | PrintConfig reflects group reassignment | unit | `go test ./config/... -run TestPrintConfigGroupReassignment` | No — Wave 0 |
### Sampling Rate
- **Per task commit:** `go test ./classify/... ./synth/... ./config/...`
- **Per wave merge:** `go test ./...`
- **Phase gate:** `go test ./...` fully green before `/gsd:verify-work`
### Wave 0 Gaps
- [ ] `config/config_test.go` — add `TestPrintConfigGroupHeaders` (asserts `# Infrastructure`, `# Mail`, etc. appear in output in correct order)
- [ ] `config/config_test.go` — add `TestLoadGroupOverride` (TOML `[groups]\nIMAP = "Web"` → FreqCfgs[IMAP].Group == "Web")
- [ ] `config/config_test.go` — add `TestLoadGroupUnknownClass` (TOML `[groups]\nBOGUS = "Web"` → no error, warning to stderr)
- [ ] `config/config_test.go` — add `TestPrintConfigGroupReassignment` (LoadResult with group-reassigned IMAP shows IMAP under `# Web` not `# Mail`)
- [ ] `config/config_test.go` — update `TestPrintConfigContainsAllClasses` classNames slice to cover all 35 (or replace with AllClasses() loop)
---
## Open Questions
1. **Pan positions for LDAP, Kerberos, Syslog (Claude's discretion)**
- What we know: Infrastructure family uses [-0.3, +0.3] pan range for existing members (ICMP=-0.3, NTP=-0.1, DHCP=+0.1, DNS=0.0, mDNS=+0.3, SSDP=-0.2, SNMP=+0.2)
- Recommendation: LDAP=-0.2, Kerberos=0.0, Syslog=+0.2 — fills the same spread pattern without exact collision with existing values
2. **TestNewBankHas14Layers function name**
- What we know: The CONTEXT.md calls out that the name is stale (should reflect 35 classes)
- Recommendation: Rename to `TestNewBankHasAllLayers` and update the assertion to `len(b.layers) != len(classify.AllClasses())` — this future-proofs it against further class additions
3. **Discovery group — no existing built-in entries in AllClasses() have Group="Discovery"**
- What we know: mDNS, SSDP, SNMP are currently in AllClasses() but not in ClassFreqConfigs (they are 3 of the 18 missing entries); they belong to "Infrastructure" per the frequency allocation table (slots 3-5, Group=Infrastructure)
- Clarification needed: the frequency table marks them as Infrastructure, but the canonical group order in D-04 lists "Discovery" as a separate group. The table must take precedence — mDNS/SSDP/SNMP are Group="Infrastructure", not "Discovery". The "Discovery" slot in groupOrder may remain empty or be omitted from PrintConfig output (the `if !ok || len(classes) == 0 { continue }` guard handles this).
---
## Sources
### Primary (HIGH confidence)
- Direct code inspection of `/home/dev/workspace/yoloyolo/synth/config.go` — frequency allocation table lines 74-110 (authoritative Hz, waveform, pan, group values)
- Direct code inspection of `/home/dev/workspace/yoloyolo/config/config.go` — PrintConfig, rawConfig struct, merge(), autoAssignFreq(), addAutoFreqEntries()
- Direct code inspection of `/home/dev/workspace/yoloyolo/classify/types.go` — AllClasses(), TrafficClass constants
- Computed autoAssignFreq values by running the FNV hash Go code — LDAP=2950, Kerberos=3250, Syslog=3050
- `go test ./...` output — confirmed exact failing tests and error messages
### Secondary (MEDIUM confidence)
- CONTEXT.md decisions D-01 through D-09 — user decisions locked in prior /gsd:discuss-phase session
- STATE.md accumulated decisions — confirms Phase 10 left AllClasses() at 32 intentionally pending Phase 11
---
## Metadata
**Confidence breakdown:**
- Standard stack: HIGH — no new dependencies; entire phase is code within existing packages
- Architecture: HIGH — patterns derived directly from existing code and locked CONTEXT.md decisions
- Pitfalls: HIGH — derived from actual failing test output and code inspection
- autoAssignFreq Hz values: HIGH — computed by executing the actual FNV hash code
**Research date:** 2026-03-27
**Valid until:** 2026-04-27 (stable — no external packages change)