docs(11): research synthesis-and-config-layer phase

This commit is contained in:
2026-03-27 16:20:37 +01:00
parent e8072510f4
commit aa1aca29a8
@@ -0,0 +1,412 @@
# 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)