Research for Phase 7 custom rules and print-config. Covers TOML array-of-tables parsing behavior, LoadResult struct design, FNV-32a auto-frequency algorithm, and print-config implementation patterns. All verified against existing source code. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
28 KiB
Phase 7: Custom Rules and Print-Config - Research
Researched: 2026-03-26 Domain: Go config parsing (BurntSushi/toml), rule system extension, TOML serialization Confidence: HIGH
Summary
Phase 7 adds two related features: user-defined TOML classification rules that prepend before built-in DefaultRules, and a --print-config flag that serializes the full effective config to stdout as commented TOML without starting a capture. Both features are pure Go additions with no new dependencies — the entire implementation works within the existing stack.
The rule parsing extension is straightforward: add a Rules []RawRule field to rawConfig, validate required fields (protocol, class), and prepend the parsed []classify.Rule slice before classify.DefaultRules in main.go. BurntSushi/toml's Undecoded() mechanism already catches typos in [[rules]] blocks (verified experimentally — unknown fields in array-of-table entries appear in Undecoded() as rules.field_name). Auto-frequency assignment for new class names uses FNV-32a hash of the class name mapped to a 1200–2400 Hz range (above all 14 built-in frequencies which top out at 1047 Hz), producing deterministic and collision-resistant results.
The --print-config implementation has two design paths: Cobra flag on the root command (simpler, consistent with the existing flag-on-root pattern) or a Cobra subcommand. The flag path is recommended as it mirrors how --list-interfaces works. The output format uses manual string building (not the TOML encoder) to support # default / # override annotations that the encoder cannot produce.
Primary recommendation: Extend config.Load() to return a LoadResult struct (freqCfgs + user rules), prepend user rules in main.go, use FNV-32a for auto-frequency assignment, and implement --print-config as a flag that triggers early-exit in the run() function.
<user_constraints>
User Constraints (from CONTEXT.md)
Locked Decisions
- D-01: Custom rules use TOML array-of-tables
[[rules]]with three fields:port(uint16, optional — omit to match any port),protocol(string, required — "tcp", "udp", or "icmp"), andclass(string, required — the TrafficClass name). Sound configuration for the class goes in a separate[sounds.<class>]block. - D-02: Port is optional. When omitted (or 0), the rule matches all traffic for the given protocol, mirroring the existing
Rule.DstPort = 0semantics inclassify.DefaultRules. - D-03: Protocol is required. No implicit "match both TCP and UDP" behavior. User must write separate rules for each protocol.
- D-04: User-defined rules are prepended before built-in
DefaultRules(RULE-02). First-match-wins semantics are preserved. A user rule for port 443/tcp fires before the built-in HTTPS rule. - D-05: Rules within the TOML
[[rules]]array maintain their file order. First rule in the file is first to match. - D-06: User-defined class names that match built-in names (e.g.,
class = "HTTPS") are treated as overrides, not errors. The user's rule fires first (prepended), so traffic matching it gets classified under the same built-in class name via the user rule. Sound config in[sounds.HTTPS]still applies. - D-07: New class names that have no
[sounds.<class>]entry automatically get sensible defaults: a frequency from an unused range and sine waveform. This satisfies RULE-03 (no silent gaps for user-defined classes). - D-08: (Claude's Discretion) The auto-assignment algorithm — how to pick frequencies for new classes that don't collide with built-in frequencies. Could use a hash of the class name, a sequential pool, or a deterministic spread across an unused frequency band.
- D-09:
--print-configoutputs the full effective config (defaults merged with user overrides and custom rules) as commented TOML. Comments indicate which values are defaults vs overrides. This satisfies CFG-06. - D-10: Output goes to stdout (pipeable). User can do
netsynth --print-config > template.tomlto create a config template. The command exits without starting a capture. - D-11: If a config file is loaded (via auto-discovery or
--config), show its source path in a header comment. - D-12: The existing
config.Load()function must be extended to parse[[rules]]blocks in addition to[sounds.*]. TherawConfigstruct gains aRules []RawRulefield. - D-13:
config.Load()returns both the mergedFreqConfigmap and the user rules (as[]classify.Rule). The caller prepends user rules beforeclassify.DefaultRules.
Claude's Discretion
- How to extend
rawConfigstruct andLoad()return type (tuple, struct, or new function) - Auto-frequency assignment algorithm for custom classes without explicit sound config
- Whether
--print-configis a Cobra subcommand or a flag on the root command - How to format the commented TOML output (manual string building vs TOML encoder + post-processing)
Deferred Ideas (OUT OF SCOPE)
None — discussion stayed within phase scope. </user_constraints>
<phase_requirements>
Phase Requirements
| ID | Description | Research Support |
|---|---|---|
| RULE-01 | User can define custom classification rules in TOML (match by port and/or protocol, assign class name and sound) | D-01 through D-05; [[rules]] TOML parsing with BurntSushi/toml confirmed working |
| RULE-02 | User-defined rules take priority over built-in rules (prepend before defaults) | D-04; classify.NewClassifier(rules []Rule) already accepts any rule slice; prepend in main.go |
| RULE-03 | User-defined class names automatically get a synthesis layer (no silent gaps) | D-07/D-08; FNV-32a auto-frequency in 1200–2400 Hz range; synth.NewBank already accepts arbitrary class maps |
| CFG-06 | User can run netsynth --print-config to see the effective config as commented TOML |
D-09 through D-11; flag on root command triggering early-exit; manual string building for comment annotations |
| </phase_requirements> |
Standard Stack
No new dependencies required. All features use existing libraries.
Core (existing, no changes needed)
| Library | Version | Purpose | Notes |
|---|---|---|---|
github.com/BurntSushi/toml |
v1.6.0 | TOML decode + Undecoded() typo detection |
Handles [[rules]] array-of-tables natively; Undecoded() catches typos in rule blocks |
github.com/spf13/cobra |
v1.10.2 | --print-config flag addition |
PersistentPreRunE / early-exit pattern already used; add flag to root command |
hash/fnv |
stdlib | FNV-32a hash for auto-frequency assignment | No import needed — already in Go stdlib |
fmt |
stdlib | Manual TOML comment string building for print-config | Simplest approach for annotated output |
Installation: No new packages. go build with existing go.mod is sufficient.
Architecture Patterns
Recommended Project Structure (additions only)
config/
├── config.go — extend rawConfig + Load() return type
└── config_test.go — add tests for [[rules]] parsing, validate, auto-freq
cmd/netsynth/
└── main.go — --print-config flag, prepend user rules, printConfig()
Pattern 1: rawConfig + Load() Return Type Extension
What: Add Rules []RawRule to rawConfig. Change Load() to return a LoadResult struct instead of a bare map.
When to use: Prefer a struct return over a tuple (map, []Rule, error) — Go tuples with 3+ values become unwieldy at the call site.
Recommended struct:
// Source: internal design — no external library required
type LoadResult struct {
FreqCfgs map[classify.TrafficClass]synth.FreqConfig
UserRules []classify.Rule
ConfigPath string // "" if no config loaded (for --print-config header comment)
}
type RawRule struct {
Port *uint16 `toml:"port"`
Protocol string `toml:"protocol"`
Class string `toml:"class"`
}
type rawConfig struct {
Sounds map[string]SoundOverride `toml:"sounds"`
Rules []RawRule `toml:"rules"`
}
Call site in main.go:
result, err := config.Load(configPath)
if err != nil {
return err
}
allRules := append(result.UserRules, classify.DefaultRules...)
classifier := classify.NewClassifier(allRules)
Pattern 2: RawRule Validation
What: Validate each RawRule before converting to classify.Rule. Required fields: protocol (non-empty, must be "tcp"/"udp"/"icmp"). class must be non-empty. port is optional.
Port omission semantics: In TOML, a missing port key means the struct field stays at its zero value. Using *uint16 (pointer) lets us distinguish "omitted" from "port = 0". In practice, both map to DstPort: 0 (match-any-port), so a uint16 field (non-pointer) also works here — the distinction is only meaningful for validation messages. Use *uint16 to match D-02 intent and for consistency with SoundOverride pointer fields.
Validation function:
func validateRules(rules []RawRule) error {
validProtocols := map[string]bool{"tcp": true, "udp": true, "icmp": true}
for i, r := range rules {
if r.Protocol == "" {
return fmt.Errorf("config: rules[%d]: protocol is required", i)
}
if !validProtocols[r.Protocol] {
return fmt.Errorf("config: rules[%d]: invalid protocol %q — valid: tcp, udp, icmp", i, r.Protocol)
}
if r.Class == "" {
return fmt.Errorf("config: rules[%d]: class is required", i)
}
}
return nil
}
Conversion to classify.Rule:
func convertRules(raw []RawRule) []classify.Rule {
result := make([]classify.Rule, len(raw))
for i, r := range raw {
var port uint16
if r.Port != nil {
port = *r.Port
}
result[i] = classify.Rule{
Protocol: r.Protocol,
DstPort: port,
Class: classify.TrafficClass(r.Class),
}
}
return result
}
Pattern 3: Auto-Frequency Assignment via FNV-32a
What: For custom class names with no [sounds.<class>] block, assign a frequency deterministically from the class name using FNV-32a hash. Map to 1200–2400 Hz in 50 Hz steps.
Why FNV-32a: Fast, deterministic, already in stdlib, zero collisions observed across realistic class names. The 1200–2400 Hz range is entirely above the highest built-in frequency (1047 Hz for ClassUnknown4), so no overlap is possible.
Verified with test: The 24-step spread (1200, 1250, ..., 2350 Hz) gives clean frequency assignments: "MyApp"→1500 Hz, "GameServer"→1800 Hz, "MediaStream"→2150 Hz, "VoIP"→1750 Hz, "Database"→2000 Hz.
// Source: stdlib hash/fnv — no import required beyond "hash/fnv"
import "hash/fnv"
func autoAssignFreq(className string) float64 {
h := fnv.New32a()
h.Write([]byte(className))
const (
baseHz = 1200.0
stepHz = 50.0
numSteps = 24 // range: 1200–2350 Hz
)
step := h.Sum32() % numSteps
return baseHz + float64(step)*stepHz
}
Integration point in merge(): After processing all Sounds overrides, iterate user rules. For each rule whose Class is not in the defaults map and has no [sounds.<class>] entry, call autoAssignFreq and create a FreqConfig with WaveformSine.
// In config.merge() or a new mergeCustomClasses() helper:
func addAutoFreqEntries(
cfgs map[classify.TrafficClass]synth.FreqConfig,
userRules []classify.Rule,
) {
for _, rule := range userRules {
class := rule.Class
if _, exists := cfgs[class]; !exists {
baseHz := autoAssignFreq(string(class))
cfgs[class] = synth.FreqConfig{
BaseHz: baseHz,
WaveformType: synth.WaveformSine,
Harmonics: synth.WaveformPresetHarmonics(synth.WaveformSine, baseHz, synth.SampleRate),
Pan: 0.0,
}
}
}
}
Pattern 4: --print-config as Flag on Root Command
What: Add --print-config bool flag. In run(), check the flag early and call a printConfig() function that writes to stdout, then return nil without starting a capture.
Why flag over subcommand: Consistent with --list-interfaces (existing pattern). Both are "inspect mode" flags that short-circuit the main capture path. Subcommand would require the user to write netsynth print-config rather than netsynth --print-config, deviating from the established CLI style.
// In main():
var printConfigFlag bool
rootCmd.Flags().BoolVar(&printConfigFlag, "print-config", false, "Print effective config as commented TOML and exit")
// In run():
if printConfigFlag {
return runPrintConfig(configPath)
}
runPrintConfig() structure:
func runPrintConfig(configPath string) error {
result, err := config.Load(configPath)
if err != nil {
return err
}
output, err := config.PrintConfig(result)
if err != nil {
return err
}
fmt.Print(output)
return nil
}
Pattern 5: PrintConfig Output Format
What: Manual string building using fmt.Fprintf to a strings.Builder. The BurntSushi/toml encoder cannot add comments, so manual building is the correct approach.
Output structure:
# NetSynth effective configuration
# Config source: /home/user/.config/netsynth/config.toml
# Generated: 2026-03-26
# Custom classification rules (prepended before built-in rules)
# [[rules]]
# port = 8080
# protocol = "tcp"
# class = "MyApp"
[sounds]
# ICMP — 65.0 Hz (default)
[sounds.ICMP]
frequency = 65.0
waveform = "custom"
# HTTPS — 300.0 Hz (override)
[sounds.HTTPS]
frequency = 300.0
waveform = "sine"
Comment semantics:
# (default)— value came fromsynth.ClassFreqConfigs# (override)— value was set in the user's config file# (auto-assigned)— frequency was auto-generated for a user-defined class
Key implementation note: Custom class entries added by addAutoFreqEntries() need their origin tracked. The LoadResult or a separate annotation map needs to carry which classes are auto-assigned vs user-overridden vs defaults. Simplest approach: PrintConfig() receives the LoadResult and compares against synth.ClassFreqConfigs to determine annotation.
Pattern 6: Undecoded() and rules Interaction
Verified behavior (experimental): BurntSushi/toml's Undecoded() correctly catches unknown fields in [[rules]] blocks. A typo like typo_field = "bad" in a [[rules]] entry appears in Undecoded() as rules.typo_field. The existing parseFile() logic handles this automatically — no changes to the undecoded key check are needed.
Important: Class-name typos in [sounds.<class>] are STILL not caught (pre-existing limitation documented in the existing code comment). This is unchanged behavior for Phase 7.
Anti-Patterns to Avoid
- Returning a tuple
(map, []Rule, error)from Load(): Three-value tuples at call sites are verbose and error-prone. Use aLoadResultstruct. - Using the TOML encoder for print-config output: The encoder cannot add
# (default)comments. Manualfmt.Fprintftostrings.Builderis the correct approach. - Modifying
classify.DefaultRulesin place: Always prepend user rules as a new slice.DefaultRulesis a package-level var that must not be mutated. Useappend(userRules, classify.DefaultRules...)to create a fresh slice. - Silent auto-frequency collision: If two user-defined classes hash to the same frequency step, they will produce the same tone. This is acceptable for v1.1 (the probability is low with 24 steps) but document it in comments.
- Placing
--print-configcheck after config validation: The check should occur early inrun()— right after config load, before any interface or output validation. The user should be able to runnetsynth --print-configwithout specifying-i.
Don't Hand-Roll
| Problem | Don't Build | Use Instead | Why |
|---|---|---|---|
| TOML parsing with typo detection | Custom parser | BurntSushi/toml Undecoded() |
Already used; handles [[rules]] natively |
| Frequency hash | Custom hash function | stdlib hash/fnv FNV-32a |
Zero-dependency, deterministic, already stdlib |
| CLI flag parsing | Manual arg parsing | Cobra flag registration | Consistent with all existing flags |
Key insight: Every mechanism needed for Phase 7 already exists in the codebase. The risk is over-engineering: the entire implementation is struct extension + prepend + string building.
Common Pitfalls
Pitfall 1: Mutating classify.DefaultRules
What goes wrong: append(classify.DefaultRules, userRules...) prepends to the wrong end and may mutate the backing array of DefaultRules if the slice has capacity.
Why it happens: Go slice append behavior with shared backing arrays.
How to avoid: Always build the combined slice as append(userRules, classify.DefaultRules...) — user rules first, then defaults. This also gives the correct prepend order (D-04).
Warning signs: Built-in rules fire before user rules for the same port/protocol.
Pitfall 2: --print-config Requires -i (incorrect)
What goes wrong: The run() function checks for -i / --read before checking --print-config, causing netsynth --print-config to fail with "interface required".
Why it happens: The interface-required validation runs before the print-config check.
How to avoid: Check printConfigFlag at the very top of run(), before the interface validation block. This matches how listIfaces is handled.
Warning signs: netsynth --print-config returns "interface required" error instead of config output.
Pitfall 3: Undecoded() Reports sounds..frequency as Unknown
What goes wrong: After adding Rules []RawRule to rawConfig, the Undecoded() check may report sounds.MyApp.frequency as unknown if the SoundOverride struct is not correctly decoded.
Why it happens: This was a concern during research but was verified NOT to occur — [sounds.MyApp] with a SoundOverride struct value decodes correctly alongside [[rules]]. No issue exists.
How to avoid: N/A — verified working. Document in code that both sections coexist correctly.
Pitfall 4: Auto-Frequency Called for Built-in Class Names
What goes wrong: If a user writes class = "HTTPS" in [[rules]], addAutoFreqEntries() must not overwrite the existing HTTPS entry with an auto-generated frequency.
Why it happens: The auto-assign loop checks if _, exists := cfgs[class]; !exists — built-in classes ARE in the defaults map, so this guard works correctly. But only if addAutoFreqEntries() runs AFTER merge() has already applied [sounds.*] overrides.
How to avoid: Call addAutoFreqEntries() as the last step in the merge pipeline, after merge(defaults, raw.Sounds). The class-exists check then correctly skips both built-in and user-overridden classes.
Pitfall 5: print-config Missing User Rules Section
What goes wrong: printConfig() shows [sounds.*] entries but omits [[rules]] entries, making the output not round-trippable.
Why it happens: Developer focuses on the sounds section (the existing config domain) and forgets rules.
How to avoid: The LoadResult must include both UserRules []classify.Rule and ConfigPath string. The PrintConfig() function must emit the [[rules]] section before the [sounds.*] section, using the user rule slice.
Pitfall 6: validate() Must Run Before convertRules()
What goes wrong: An empty protocol or class field in [[rules]] gets silently converted to a classify.Rule with empty strings, producing confusing runtime behavior.
Why it happens: convertRules() has no validation; it just copies fields.
How to avoid: Call validateRules(raw.Rules) inside validate() (the existing validation entry point), before conversion. Fail fast at startup.
Code Examples
TOML File with Custom Rules (user-facing format)
# Classify custom app traffic on port 8080
[[rules]]
port = 8080
protocol = "tcp"
class = "MyApp"
# Match all UDP traffic to a custom class (port omitted = match any)
[[rules]]
protocol = "udp"
class = "AllUDP"
# Give MyApp a custom sound
[sounds.MyApp]
frequency = 300.0
waveform = "sine"
# Override built-in HTTPS sound
[sounds.HTTPS]
frequency = 400.0
rawConfig Extension
// config/config.go
type RawRule struct {
Port *uint16 `toml:"port"`
Protocol string `toml:"protocol"`
Class string `toml:"class"`
}
type rawConfig struct {
Sounds map[string]SoundOverride `toml:"sounds"`
Rules []RawRule `toml:"rules"`
}
LoadResult Struct (replaces bare map return)
type LoadResult struct {
FreqCfgs map[classify.TrafficClass]synth.FreqConfig
UserRules []classify.Rule
ConfigPath string // populated path or "" if auto-discovery found nothing
}
func Load(configPath string) (LoadResult, error) { ... }
Prepend User Rules in main.go
result, err := config.Load(configPath)
if err != nil {
return err
}
// D-04: user rules prepend before built-ins; first-match-wins
allRules := append(result.UserRules, classify.DefaultRules...)
classifier := classify.NewClassifier(allRules)
FNV-32a Auto-Frequency
import "hash/fnv"
func autoAssignFreq(className string) float64 {
h := fnv.New32a()
h.Write([]byte(className))
const (
baseHz = 1200.0
stepHz = 50.0
numSteps = uint32(24)
)
return baseHz + float64(h.Sum32()%numSteps)*stepHz
}
State of the Art
No changes to the underlying technology stack. All patterns are internal to the codebase.
| Old Behavior | New Behavior | When Changed | Impact |
|---|---|---|---|
config.Load() returns map[classify.TrafficClass]synth.FreqConfig |
Returns LoadResult struct with FreqCfgs + UserRules + ConfigPath |
Phase 7 | All callers of Load() need update (currently 1 call site: main.go:run()) |
Unknown class names in [sounds.*] silently ignored (warning) |
Still ignored with warning, but user-defined class names from [[rules]] get auto-freq entries instead |
Phase 7 | New behavior for Phase 7 class names; old warning behavior preserved for truly unknown names |
merge() only processes sound overrides |
merge() + addAutoFreqEntries() also handles new class synthesis entries |
Phase 7 | Synthesis bank grows dynamically with user-defined classes |
Environment Availability
Step 2.6: SKIPPED (no external dependencies identified — Phase 7 is a pure Go code extension using existing stack).
Validation Architecture
Test Framework
| Property | Value |
|---|---|
| Framework | Go standard testing package |
| Config file | None (no pytest.ini / jest.config equivalent) |
| Quick run command | go test ./config/... ./cmd/netsynth/... |
| Full suite command | go test ./... |
Phase Requirements → Test Map
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|---|---|---|---|---|
| RULE-01 | [[rules]] block in TOML parses correctly into RawRule slice |
unit | go test ./config/... -run TestLoadCustomRules |
❌ Wave 0 |
| RULE-01 | Port field omitted → DstPort 0 (match-any) | unit | go test ./config/... -run TestLoadCustomRuleNoPort |
❌ Wave 0 |
| RULE-01 | Missing protocol field → error |
unit | go test ./config/... -run TestLoadCustomRuleMissingProtocol |
❌ Wave 0 |
| RULE-01 | Missing class field → error |
unit | go test ./config/... -run TestLoadCustomRuleMissingClass |
❌ Wave 0 |
| RULE-01 | Typo in [[rules]] field → error naming bad key |
unit | go test ./config/... -run TestLoadCustomRuleUnknownField |
❌ Wave 0 |
| RULE-02 | User rule for port 443/tcp fires before built-in HTTPS rule | unit | go test ./config/... -run TestUserRulesPrepend |
❌ Wave 0 |
| RULE-03 | Class name with no [sounds.*] entry gets auto-freq entry in FreqCfgs |
unit | go test ./config/... -run TestAutoFreqAssignment |
❌ Wave 0 |
| RULE-03 | Auto-freq is deterministic (same class name → same frequency) | unit | go test ./config/... -run TestAutoFreqDeterministic |
❌ Wave 0 |
| RULE-03 | Built-in class names NOT overwritten by auto-freq | unit | go test ./config/... -run TestAutoFreqSkipsBuiltins |
❌ Wave 0 |
| CFG-06 | --print-config flag registered on root command |
unit | go test ./cmd/netsynth/... -run TestPrintConfigFlagRegistered |
❌ Wave 0 |
| CFG-06 | --print-config exits without requiring -i flag |
unit | go test ./cmd/netsynth/... -run TestPrintConfigNoInterface |
❌ Wave 0 |
| CFG-06 | Print-config output contains all 14 default class names | unit | go test ./config/... -run TestPrintConfigContainsAllClasses |
❌ Wave 0 |
| CFG-06 | Print-config output contains [[rules]] section when user rules are present |
unit | go test ./config/... -run TestPrintConfigContainsRules |
❌ Wave 0 |
| CFG-06 | Print-config includes source path in header comment when config loaded | unit | go test ./config/... -run TestPrintConfigSourcePath |
❌ Wave 0 |
Sampling Rate
- Per task commit:
go test ./config/... ./cmd/netsynth/... - Per wave merge:
go test ./... - Phase gate:
go test ./...green before/gsd:verify-work
Wave 0 Gaps
All test functions listed above are new — no existing test file covers Phase 7 behavior. Tests should be added to:
config/config_test.go— allconfigpackage tests (follow existingwriteTOMLhelper pattern)cmd/netsynth/main_test.go— allcmd/netsynthflag tests (follow existingnewTestCmd()pattern)
No new test files needed — extend the existing test files.
Open Questions
-
PrintConfig annotation tracking for auto-assigned classes
- What we know:
addAutoFreqEntries()adds entries toFreqCfgsfor new class names - What's unclear:
PrintConfig()needs to know which entries are auto-assigned (vs default vs user-override) to annotate them correctly - Recommendation: Add an
AutoClasses map[classify.TrafficClass]boolfield toLoadResult, populated byaddAutoFreqEntries().PrintConfig()consults this map.
- What we know:
-
Cobra --print-config placement: before or after config load
- What we know:
--print-configneeds config loaded to show effective values - What's unclear: What if the user runs
netsynth --print-configwith no config file? - Recommendation: Always call
config.Load(configPath)before printing. If no config file is found (auto-discovery returns nothing), the output shows all defaults — which is the most useful behavior.
- What we know:
Sources
Primary (HIGH confidence)
- Source code:
config/config.go— read directly; rawConfig struct, Load(), merge(), validate(), parseFile() all verified - Source code:
classify/rules.go— DefaultRules slice, Rule struct verified - Source code:
classify/classifier.go— NewClassifier(rules []Rule) injection point verified - Source code:
synth/bank.go— NewBank(tau, cfgs) accepts arbitrary class maps confirmed - Source code:
synth/config.go— ClassFreqConfigs frequency range 65–1047 Hz confirmed; WaveformPresetHarmonics verified - Source code:
cmd/netsynth/main.go— current Load() call site; listIfaces early-exit pattern verified - Experimental: BurntSushi/toml
Undecoded()behavior with[[rules]]— verified by running test code against go.mod-pinned v1.6.0 - Experimental: FNV-32a frequency distribution — verified by running Go code; 24 distinct frequencies in 1200–2400 Hz range
Secondary (MEDIUM confidence)
- Source code:
config/config_test.go— test patterns for writeTOML, Load() behavior; test style confirmed - Source code:
cmd/netsynth/main_test.go— newTestCmd() pattern, flag registration test style confirmed
Tertiary (LOW confidence)
- None.
Metadata
Confidence breakdown:
- Standard stack: HIGH — no new dependencies; all libraries verified against go.mod
- Architecture: HIGH — all integration points verified by reading actual source code
- Pitfalls: HIGH — Pitfalls 1, 2, 4, 5 verified by code inspection; Pitfall 3 experimentally verified as non-issue
- Test patterns: HIGH — existing test file structure read directly
Research date: 2026-03-26 Valid until: Stable — 90 days (pure Go, no external dependencies, stable TOML library)