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>
This commit is contained in:
2026-03-27 16:48:53 +01:00
co-authored by Claude Opus 4.6
parent 0a4d48c9c1
commit 494385b528
42 changed files with 290 additions and 166 deletions
@@ -0,0 +1,214 @@
---
phase: 08-test-and-constant-cleanup
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- synth/config.go
- synth/config_test.go
autonomous: true
requirements:
- CLEAN-01
must_haves:
truths:
- "NumLayers and GainPerLayer constants do not exist in the synth package"
- "go test ./... passes with zero failures after all edits"
- "TestFrequenciesInRange accepts any BaseHz in (0, Nyquist) without manual test surgery"
- "TestNumLayersMatchesAllClasses is renamed to TestClassFreqConfigsMatchAllClasses"
artifacts:
- path: "synth/config.go"
provides: "Cleaned constant block without NumLayers or GainPerLayer"
contains: "WhisperFloor"
- path: "synth/config_test.go"
provides: "Future-proof test assertions"
contains: "TestClassFreqConfigsMatchAllClasses"
key_links:
- from: "synth/bank.go"
to: "synth/config.go"
via: "gainPerLayer computed dynamically in NewBank — no longer any static constant to conflict with"
pattern: "1\\.0 / float64\\(len\\(cfgs\\)\\)"
- from: "synth/config_test.go"
to: "synth/config.go"
via: "TestFrequenciesInRange validates BaseHz against SampleRate-derived Nyquist"
pattern: "float64\\(synth\\.SampleRate\\)"
---
<objective>
Remove stale exported constants and hardcoded test bounds from the synth package so that subsequent v1.2 phases can add new traffic classes and frequencies without triggering false CI failures.
Purpose: Phase 8 is the gatekeeper for all v1.2 work. NumLayers=14 is dead code (NewBank computes gain dynamically), and TestFrequenciesInRange's [60, 1100] bound will reject any new class above 1100 Hz. Both must be cleaned up before Phase 9-11 proceed.
Output: Two edited files (synth/config.go, synth/config_test.go) with all 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
@.planning/phases/08-test-and-constant-cleanup/08-CONTEXT.md
@.planning/phases/08-test-and-constant-cleanup/08-RESEARCH.md
<interfaces>
<!-- Key constants and types the executor needs from synth/config.go -->
From synth/config.go (current constant block, lines 5-12):
```go
const (
SampleRate = 44100 // D-13: CD quality
WindowMs = 500 // matches aggregate.DefaultWindowMs
SamplesPerWindow = SampleRate * WindowMs / 1000 // 22050
NumLayers = 14 // <-- DELETE per D-01
GainPerLayer = 1.0 / float64(NumLayers) // D-10: ~0.0714 <-- DELETE per D-01
WhisperFloor = 0.03 // D-08/D-09: 3% of max amplitude
)
```
From synth/bank.go (line 21 — confirms constants are dead code):
```go
gainPerLayer: 1.0 / float64(len(cfgs)), // dynamic computation, no constant needed
```
From synth/config_test.go (current test functions to modify):
```go
// Lines 18-25: TestFrequenciesInRange — hardcoded [60, 1100] to replace
// Lines 53-59: TestClassFreqConfigsComplete — duplicate of TestAllClassesHaveConfig
// Lines 61-66: TestNumLayersMatchesAllClasses — rename to TestClassFreqConfigsMatchAllClasses
```
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Delete stale NumLayers and GainPerLayer constants from synth/config.go</name>
<files>synth/config.go</files>
<read_first>
- synth/config.go (see current constant block at lines 5-12)
- synth/bank.go (line 21 confirms gainPerLayer is computed dynamically)
</read_first>
<action>
Per D-01: Delete lines 9-10 from synth/config.go — the `NumLayers = 14` and `GainPerLayer = 1.0 / float64(NumLayers)` constants.
The resulting constant block must be:
```go
const (
SampleRate = 44100 // D-13: CD quality
WindowMs = 500 // matches aggregate.DefaultWindowMs
SamplesPerWindow = SampleRate * WindowMs / 1000 // 22050
WhisperFloor = 0.03 // D-08/D-09: 3% of max amplitude
)
```
Do NOT modify any other lines in config.go. Do NOT touch bank.go — NewBank's dynamic `1.0 / float64(len(cfgs))` is already correct.
Optionally, add a traceability comment to synth/bank.go line 21:
`gainPerLayer: 1.0 / float64(len(cfgs)), // D-10: 1/N so all layers at full amplitude sum to 1.0`
This is low-priority — skip if it feels like noise.
</action>
<verify>
<automated>cd /home/dev/workspace/yoloyolo && grep -n "NumLayers\|GainPerLayer" synth/config.go; echo "EXIT:$?"</automated>
</verify>
<acceptance_criteria>
- `grep -c "NumLayers" synth/config.go` returns 0
- `grep -c "GainPerLayer" synth/config.go` returns 0
- `synth/config.go` still contains `SampleRate = 44100`
- `synth/config.go` still contains `WhisperFloor = 0.03`
- `go build ./synth/...` exits 0 (no compilation errors from removing the constants)
</acceptance_criteria>
<done>NumLayers and GainPerLayer constants no longer exist in synth/config.go. The constant block contains only SampleRate, WindowMs, SamplesPerWindow, and WhisperFloor. Code compiles cleanly.</done>
</task>
<task type="auto">
<name>Task 2: Update synth/config_test.go — future-proof frequency bounds, rename test, remove duplicate</name>
<files>synth/config_test.go</files>
<read_first>
- synth/config_test.go (full file — see all 6 test functions)
- synth/config.go (after Task 1 edits — confirm SampleRate is exported)
</read_first>
<action>
Three changes to synth/config_test.go:
**Change 1 (per D-02):** Replace the body of `TestFrequenciesInRange` (lines 18-25) with Nyquist-based validation. The new function body:
```go
func TestFrequenciesInRange(t *testing.T) {
const nyquist = float64(synth.SampleRate) / 2.0 // 22050 Hz
for class, cfg := range synth.ClassFreqConfigs {
if cfg.BaseHz <= 0 {
t.Errorf("class %q BaseHz=%.1f must be positive", class, cfg.BaseHz)
}
if cfg.BaseHz >= nyquist {
t.Errorf("class %q BaseHz=%.1f exceeds Nyquist (%.1f Hz)", class, cfg.BaseHz, nyquist)
}
}
}
```
This accepts any BaseHz in (0, 22050) — no manual edit needed when Phase 10 adds classes above 1100 Hz.
**Change 2 (per D-03):** Rename `TestNumLayersMatchesAllClasses` (line 61) to `TestClassFreqConfigsMatchAllClasses`. Keep the function body identical:
```go
func TestClassFreqConfigsMatchAllClasses(t *testing.T) {
if len(synth.ClassFreqConfigs) != len(classify.AllClasses()) {
t.Errorf("ClassFreqConfigs has %d entries but AllClasses() has %d entries",
len(synth.ClassFreqConfigs), len(classify.AllClasses()))
}
}
```
**Change 3 (Claude's discretion — consolidation):** Delete `TestClassFreqConfigsComplete` (lines 53-59) entirely. It is a semantic duplicate of `TestAllClassesHaveConfig` (lines 10-16) — both iterate `AllClasses()` and check for a map entry. Removing it leaves three non-overlapping tests:
- `TestAllClassesHaveConfig` — every AllClasses() member has a map entry
- `TestClassFreqConfigsMatchAllClasses` — count parity (catches extra entries)
- `TestFrequenciesInRange` — all BaseHz positive and below Nyquist
The remaining tests (`TestFrequenciesUnique`, `TestHarmonicsNonEmpty`, `TestPanPositionsInRange`) are untouched.
</action>
<verify>
<automated>cd /home/dev/workspace/yoloyolo && go test ./synth/... -v -count=1 2>&1 | head -40</automated>
</verify>
<acceptance_criteria>
- `grep -c "TestNumLayersMatchesAllClasses" synth/config_test.go` returns 0
- `grep -c "TestClassFreqConfigsMatchAllClasses" synth/config_test.go` returns 1
- `grep -c "TestClassFreqConfigsComplete" synth/config_test.go` returns 0
- `grep "nyquist" synth/config_test.go` returns at least one match
- `grep "1100" synth/config_test.go` returns 0 matches (hardcoded bound removed)
- `go test ./synth/... -run TestFrequenciesInRange` exits 0
- `go test ./synth/... -run TestClassFreqConfigsMatchAllClasses` exits 0
- `go test ./...` exits 0 (full suite green)
</acceptance_criteria>
<done>TestFrequenciesInRange uses Nyquist-based validation (no hardcoded upper bound). TestNumLayersMatchesAllClasses renamed to TestClassFreqConfigsMatchAllClasses. Duplicate TestClassFreqConfigsComplete removed. Full test suite passes.</done>
</task>
</tasks>
<verification>
After both tasks complete:
1. `grep -rn "NumLayers\|GainPerLayer" synth/*.go` — zero matches in production and test code
2. `grep -n "1100" synth/config_test.go` — zero matches (hardcoded bound gone)
3. `go test ./...` — all packages pass with zero failures
4. `go test ./synth/... -run TestFrequenciesInRange` — passes
5. `go test ./synth/... -run TestClassFreqConfigsMatchAllClasses` — passes
6. `go test ./synth/... -run TestClassFreqConfigsComplete` — no such test (removed)
</verification>
<success_criteria>
- NumLayers and GainPerLayer constants deleted from synth/config.go
- TestFrequenciesInRange validates against Nyquist (22050 Hz), not hardcoded 1100
- TestNumLayersMatchesAllClasses renamed to TestClassFreqConfigsMatchAllClasses
- Duplicate TestClassFreqConfigsComplete removed
- `go test ./...` passes with zero failures
- Adding a new class with BaseHz=2000 in a future phase would NOT require editing any test
</success_criteria>
<output>
After completion, create `.planning/phases/08-test-and-constant-cleanup/08-01-SUMMARY.md`
</output>
@@ -0,0 +1,74 @@
---
phase: 08-test-and-constant-cleanup
plan: 01
subsystem: synth
tags: [cleanup, constants, tests, ci]
dependency_graph:
requires: []
provides: [clean-constant-block, future-proof-frequency-tests]
affects: [synth/config.go, synth/config_test.go]
tech_stack:
added: []
patterns: [Nyquist-based validation instead of hardcoded bounds]
key_files:
created: []
modified:
- synth/config.go
- synth/config_test.go
decisions:
- NumLayers and GainPerLayer deleted — NewBank computes gain dynamically as 1/len(cfgs); static constants were dead code after v1.1
- TestFrequenciesInRange now validates against Nyquist (22050 Hz) so any BaseHz in (0, 22050) is accepted without test surgery
- TestClassFreqConfigsComplete removed as duplicate of TestAllClassesHaveConfig; TestNumLayersMatchesAllClasses renamed to TestClassFreqConfigsMatchAllClasses
metrics:
duration: "1 min"
completed: "2026-03-27"
tasks_completed: 2
files_modified: 2
requirements_satisfied:
- CLEAN-01
---
# Phase 8 Plan 1: Constant Cleanup and Future-Proof Tests Summary
Removed stale exported constants and hardcoded test bounds from the synth package so v1.2 phases can add new traffic classes and frequencies without triggering false CI failures.
## Tasks Completed
| Task | Name | Commit | Files |
|------|------|--------|-------|
| 1 | Delete stale NumLayers and GainPerLayer constants | fd17061 | synth/config.go |
| 2 | Update synth/config_test.go — future-proof frequency bounds, rename test, remove duplicate | 4800e8e | synth/config_test.go |
## What Was Done
**Task 1** deleted `NumLayers = 14` and `GainPerLayer = 1.0 / float64(NumLayers)` from `synth/config.go`. These constants were dead code since `NewBank` computes `gainPerLayer` dynamically as `1.0 / float64(len(cfgs))`. The constant block now contains only `SampleRate`, `WindowMs`, `SamplesPerWindow`, and `WhisperFloor`.
**Task 2** made three improvements to `synth/config_test.go`:
1. `TestFrequenciesInRange` now validates each `BaseHz` is in `(0, 22050)` using `float64(synth.SampleRate) / 2.0` as the Nyquist bound — no hardcoded upper limit that would reject new protocol classes above 1100 Hz.
2. `TestNumLayersMatchesAllClasses` renamed to `TestClassFreqConfigsMatchAllClasses` — name now accurately describes what it tests.
3. `TestClassFreqConfigsComplete` deleted — it was a semantic duplicate of `TestAllClassesHaveConfig` (both iterate `AllClasses()` and check for a map entry). Three clean, non-overlapping tests remain.
## Verification Results
All six plan verification checks passed:
- Zero `NumLayers`/`GainPerLayer` references in `synth/*.go`
- Zero hardcoded `1100` bounds in `synth/config_test.go`
- `go test ./...` — all 7 packages green
- `TestFrequenciesInRange` passes
- `TestClassFreqConfigsMatchAllClasses` passes
- `TestClassFreqConfigsComplete` no longer exists (correctly)
## Deviations from Plan
None — plan executed exactly as written.
## Known Stubs
None.
## Self-Check: PASSED
- synth/config.go exists and contains WhisperFloor, no NumLayers/GainPerLayer
- synth/config_test.go exists with nyquist validation, TestClassFreqConfigsMatchAllClasses, no TestClassFreqConfigsComplete
- Commits fd17061 and 4800e8e exist
- Full test suite green
@@ -0,0 +1,88 @@
# Phase 8: Test and Constant Cleanup - Context
**Gathered:** 2026-03-27
**Status:** Ready for planning
<domain>
## Phase Boundary
Remove stale exported constants (`NumLayers`, `GainPerLayer`) from the synth package and update hardcoded test assertions (`TestFrequenciesInRange`) so that subsequent v1.2 phases can add new traffic classes and frequencies without triggering false CI failures. This is pure cleanup — no new features, no new protocols.
</domain>
<decisions>
## Implementation Decisions
### Constant Removal Strategy
- **D-01:** Delete `NumLayers` and `GainPerLayer` constants entirely from `synth/config.go`. They are dead code — `NewBank` already computes `gainPerLayer` dynamically as `1.0 / float64(len(cfgs))` (bank.go:21). No external callers reference either constant outside the test file.
### Frequency Range Test Bounds
- **D-02:** Replace the hardcoded `[60, 1100]` bounds in `TestFrequenciesInRange` with dynamic validation — derive the valid range from the `ClassFreqConfigs` data itself (e.g., check that all frequencies are positive and below Nyquist) rather than hardcoding a new magic number that would need manual updating when Phase 9/10 add classes above 1100 Hz. The specific approach (positive+Nyquist check, or a generous static bound like `[20, 8000]`) is at Claude's discretion — the key constraint is that adding a new class in the 1100-4000 Hz range must not require editing this test.
### Test Naming
- **D-03:** Rename `TestNumLayersMatchesAllClasses` to `TestClassFreqConfigsMatchAllClasses` (or similar) to reflect the actual invariant being tested after `NumLayers` removal. The test body already uses `len(synth.ClassFreqConfigs)` and `len(classify.AllClasses())` — only the name references the deleted constant.
### Claude's Discretion
- Whether to use a generous static upper bound vs a computed Nyquist-based bound for D-02 — either approach satisfies the constraint
- Whether `WhisperFloor` or other constants in config.go need any adjustment (they don't reference NumLayers, so likely no)
- Whether `TestClassFreqConfigsComplete` (line 53) should be consolidated with the renamed test since both verify the same invariant
### Folded Todos
- **"Expand Traffic Classes"** (from `.planning/todos/pending/001-expand-traffic-classes.md`) — This todo requests adding protocols like IMAP, POP3, SNMP, FTP and researching common traffic classes. Phase 8 enables this work by removing the test/constant blockers, but the actual protocol additions are Phase 10's scope. Folded here as context, not as direct Phase 8 work.
</decisions>
<canonical_refs>
## Canonical References
**Downstream agents MUST read these before planning or implementing.**
### Synth Package (primary targets)
- `synth/config.go` — Contains `NumLayers` and `GainPerLayer` constants to remove (lines 9-10)
- `synth/config_test.go` — Contains `TestFrequenciesInRange` (lines 18-24), `TestNumLayersMatchesAllClasses` (lines 61-66), and `TestClassFreqConfigsComplete` (lines 53-59)
- `synth/bank.go``NewBank` already computes `gainPerLayer` dynamically (line 21) — confirms constants are dead code
### Research Context
- `.planning/research/PITFALLS.md` — Pitfall C4 documents `TestFrequenciesInRange` hardcoding issue
- `.planning/research/ARCHITECTURE.md` — Lines 414+ document NumLayers/ClassFreqConfigs mismatch risk
- `.planning/research/SUMMARY.md` — Lines 69, 85, 100 describe Phase 8 cleanup scope
</canonical_refs>
<code_context>
## Existing Code Insights
### Reusable Assets
- `synth/bank.go:NewBank` already has the correct dynamic gain computation — no new code needed for gain behavior
### Established Patterns
- Test file `synth/config_test.go` uses table-driven validation against `ClassFreqConfigs` map and `classify.AllClasses()` — new/renamed tests should follow this pattern
- `GainPerLayer` constant at line 10 has a comment referencing "D-10" — cleanup should not leave orphan decision references
### Integration Points
- Only `synth/config.go` and `synth/config_test.go` are modified — no downstream package changes expected
- `go test ./...` is the verification gate — must pass with zero new failures
</code_context>
<specifics>
## Specific Ideas
No specific requirements — this is a straightforward cleanup phase with clear targets identified in research.
</specifics>
<deferred>
## Deferred Ideas
### Reviewed Todos (not folded)
None — the matched todo was folded as milestone context.
None — discussion stayed within phase scope.
</deferred>
---
*Phase: 08-test-and-constant-cleanup*
*Context gathered: 2026-03-27*
@@ -0,0 +1,60 @@
# Phase 8: Test and Constant Cleanup - Discussion Log
> **Audit trail only.** Do not use as input to planning, research, or execution agents.
> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.
**Date:** 2026-03-27
**Phase:** 08-test-and-constant-cleanup
**Areas discussed:** Constant removal strategy, Frequency range bound, Test naming
**Mode:** Auto (all decisions auto-selected)
---
## Constant Removal Strategy
| Option | Description | Selected |
|--------|-------------|----------|
| Delete entirely | Remove NumLayers and GainPerLayer from config.go — bank.go already computes dynamically | ✓ |
| Deprecate with comment | Keep but mark as deprecated for backward compatibility | |
| Replace with function | Convert to a function that returns len(ClassFreqConfigs) | |
**User's choice:** [auto] Delete entirely (recommended default)
**Notes:** NewBank already computes gainPerLayer as 1.0/len(cfgs). No external callers reference either constant.
---
## Frequency Range Test Bound
| Option | Description | Selected |
|--------|-------------|----------|
| Dynamic validation | Derive valid range from data (positive + below Nyquist) — no magic numbers | ✓ |
| Generous static bound | Replace 1100 with e.g. 8000 Hz — simple but still hardcoded | |
| Remove range test | Delete TestFrequenciesInRange entirely — other tests cover correctness | |
**User's choice:** [auto] Dynamic validation (recommended default)
**Notes:** Key constraint: adding a class in 1100-4000 Hz range must not require editing this test.
---
## Test Naming
| Option | Description | Selected |
|--------|-------------|----------|
| Rename to TestClassFreqConfigsMatchAllClasses | Reflects actual invariant after NumLayers removal | ✓ |
| Keep current name | Leave as-is despite referencing deleted constant | |
| Delete test | TestClassFreqConfigsComplete already covers same invariant | |
**User's choice:** [auto] Rename to TestClassFreqConfigsMatchAllClasses (recommended default)
**Notes:** Test body already uses len() comparisons, only the name references NumLayers.
---
## Claude's Discretion
- Specific approach for dynamic frequency range validation (Nyquist-based vs generous static bound)
- Whether to consolidate TestClassFreqConfigsComplete with renamed test
- Whether WhisperFloor needs adjustment (likely not)
## Deferred Ideas
None — discussion stayed within phase scope.
@@ -0,0 +1,338 @@
# Phase 8: Test and Constant Cleanup - Research
**Researched:** 2026-03-27
**Domain:** Go test cleanup, dead code removal, test assertion generalization
**Confidence:** HIGH
<user_constraints>
## User Constraints (from CONTEXT.md)
### Locked Decisions
- **D-01:** Delete `NumLayers` and `GainPerLayer` constants entirely from `synth/config.go`. They are dead code — `NewBank` already computes `gainPerLayer` dynamically as `1.0 / float64(len(cfgs))` (bank.go:21). No external callers reference either constant outside the test file.
- **D-02:** Replace the hardcoded `[60, 1100]` bounds in `TestFrequenciesInRange` with dynamic validation — derive the valid range from the `ClassFreqConfigs` data itself (e.g., check that all frequencies are positive and below Nyquist) rather than hardcoding a new magic number that would need manual updating when Phase 9/10 add classes above 1100 Hz.
- **D-03:** Rename `TestNumLayersMatchesAllClasses` to `TestClassFreqConfigsMatchAllClasses` (or similar) to reflect the actual invariant being tested after `NumLayers` removal.
### Claude's Discretion
- Whether to use a generous static upper bound vs a computed Nyquist-based bound for D-02 — either approach satisfies the constraint
- Whether `WhisperFloor` or other constants in config.go need any adjustment (they don't reference NumLayers, so likely no)
- Whether `TestClassFreqConfigsComplete` (line 53) should be consolidated with the renamed test since both verify the same invariant
### Deferred Ideas (OUT OF SCOPE)
- Adding protocols (IMAP, POP3, SNMP, FTP, etc.) — this is Phase 10 scope; Phase 8 only removes blockers
</user_constraints>
<phase_requirements>
## Phase Requirements
| ID | Description | Research Support |
|----|-------------|------------------|
| CLEAN-01 | Remove stale `NumLayers` constant and hardcoded frequency range test assertions that would block new class additions | D-01 removes the constants; D-02 replaces hardcoded bounds with future-proof validation; D-03 renames stale test function |
</phase_requirements>
---
## Summary
Phase 8 is a pure cleanup phase with two narrowly scoped targets: (1) two dead exported constants in `synth/config.go` and (2) three test functions in `synth/config_test.go` that need renaming or rewiring. No new features, no new packages, no new dependencies.
`NumLayers = 14` and `GainPerLayer = 1.0 / float64(NumLayers)` in `synth/config.go` are provably dead code. `synth/bank.go:NewBank` computes `gainPerLayer` dynamically at line 21 as `1.0 / float64(len(cfgs))`. Neither constant is referenced anywhere in the production code path — only in the test file's function name `TestNumLayersMatchesAllClasses` (which itself does not use either constant in its body). Deleting both constants removes a misleading signal and eliminates the risk of future callers accidentally hardcoding the stale count 14.
`TestFrequenciesInRange` asserts `cfg.BaseHz < 60 || cfg.BaseHz > 1100`. Phase 9 will redistribute frequencies and Phase 10 will add classes whose frequencies will exceed 1100 Hz. The test will produce false failures the moment any `ClassFreqConfigs` entry above 1100 Hz is added. Replacing the hardcoded upper bound with a Nyquist-based check (or a generous static bound like 8000 Hz) makes the test structurally future-proof without encoding new domain knowledge in this phase.
**Primary recommendation:** Three surgical edits to two files — delete 2 lines in `config.go`, update 1 test function body + rename 2 test functions in `config_test.go`. Total change surface is under 15 lines.
---
## Standard Stack
No new dependencies. This phase touches only existing Go source files.
### Core
| Library | Version | Purpose | Why Standard |
|---------|---------|---------|--------------|
| `testing` | stdlib | Test assertions | Already used throughout the codebase |
**Installation:** None required — no new packages.
---
## Architecture Patterns
### Files Modified (exhaustive list)
```
synth/
├── config.go # Delete NumLayers and GainPerLayer constants (lines 9-10)
└── config_test.go # Update TestFrequenciesInRange body; rename two test functions
```
No other files are modified. The CONTEXT.md explicitly states: "Only `synth/config.go` and `synth/config_test.go` are modified — no downstream package changes expected."
### Pattern 1: Dead Constant Removal
**What:** Delete lines 9-10 from `synth/config.go`.
**Current state:**
```go
// synth/config.go lines 5-12
const (
SampleRate = 44100
WindowMs = 500
SamplesPerWindow = SampleRate * WindowMs / 1000 // 22050
NumLayers = 14
GainPerLayer = 1.0 / float64(NumLayers) // D-10: ~0.0714
WhisperFloor = 0.03
)
```
**After deletion:**
```go
const (
SampleRate = 44100
WindowMs = 500
SamplesPerWindow = SampleRate * WindowMs / 1000 // 22050
WhisperFloor = 0.03
)
```
**Verification:** `grep -r "NumLayers\|GainPerLayer" .` must return zero hits in `*.go` files after deletion. The only non-test reference is in `.planning/research/ARCHITECTURE.md` (planning docs — not compiled).
### Pattern 2: Nyquist-Based Frequency Range Validation
**What:** Replace the hardcoded `[60, 1100]` upper bound in `TestFrequenciesInRange`.
**Recommended approach (Nyquist-based):** Phase 9 will add classes up to ~4000 Hz. Nyquist at 44100 Hz sample rate is 22050 Hz. A Nyquist check is mathematically correct and never needs updating regardless of how many new classes are added:
```go
func TestFrequenciesInRange(t *testing.T) {
const nyquist = float64(synth.SampleRate) / 2.0 // 22050 Hz
for class, cfg := range synth.ClassFreqConfigs {
if cfg.BaseHz <= 0 {
t.Errorf("class %q BaseHz=%.1f must be positive", class, cfg.BaseHz)
}
if cfg.BaseHz >= nyquist {
t.Errorf("class %q BaseHz=%.1f exceeds Nyquist (%.1f Hz)", class, cfg.BaseHz, nyquist)
}
}
}
```
**Alternative approach (generous static bound):** A bound of `[20, 8000]` also satisfies D-02's constraint since all planned Phase 9/10 frequencies are under 4000 Hz. However the Nyquist approach is self-documenting — it explains *why* there's an upper bound rather than encoding an arbitrary number. Either is acceptable per Claude's Discretion.
**Key invariant preserved:** Both approaches ensure adding a new class at any humanly-audible frequency (20 Hz 20 kHz, well within Nyquist) will NOT require editing this test.
### Pattern 3: Test Function Rename
**What:** Rename `TestNumLayersMatchesAllClasses` at line 61. The test body already tests the correct invariant (`len(synth.ClassFreqConfigs) == len(classify.AllClasses())`); only the name is stale.
**Current:**
```go
func TestNumLayersMatchesAllClasses(t *testing.T) {
if len(synth.ClassFreqConfigs) != len(classify.AllClasses()) {
t.Errorf("ClassFreqConfigs has %d entries but AllClasses() has %d entries",
len(synth.ClassFreqConfigs), len(classify.AllClasses()))
}
}
```
**After rename:**
```go
func TestClassFreqConfigsMatchAllClasses(t *testing.T) {
if len(synth.ClassFreqConfigs) != len(classify.AllClasses()) {
t.Errorf("ClassFreqConfigs has %d entries but AllClasses() has %d entries",
len(synth.ClassFreqConfigs), len(classify.AllClasses()))
}
}
```
### Pattern 4: Consolidation Decision (Claude's Discretion)
`TestAllClassesHaveConfig` (lines 10-16) and `TestClassFreqConfigsComplete` (lines 53-59) test the same invariant: every class in `AllClasses()` has an entry in `ClassFreqConfigs`. They are exact duplicates in semantics (different error messages but identical logic). The renamed `TestClassFreqConfigsMatchAllClasses` (formerly `TestNumLayersMatchesAllClasses`) tests the converse: lengths match.
**Recommendation:** Remove `TestClassFreqConfigsComplete` (lines 53-59) as a duplicate of `TestAllClassesHaveConfig`. This leaves three non-overlapping coverage tests:
- `TestAllClassesHaveConfig` — every AllClasses() member has a map entry
- `TestClassFreqConfigsMatchAllClasses` — count parity (catches extra entries not in AllClasses)
- `TestFrequenciesInRange` — all BaseHz values are positive and below Nyquist
Alternatively, leave both functions if deduplication is not worth the discussion. Both pass and both protect the invariant. This is truly Claude's discretion.
### Anti-Patterns to Avoid
- **Updating `NumLayers` instead of deleting it:** The decision (D-01) is deletion, not update. An updated constant would still be a maintenance burden.
- **Replacing [60, 1100] with [60, 4000]:** A new hardcoded number has the same fragility as the old one — it becomes stale when the frequency spectrum changes again in a future milestone.
- **Touching `bank.go`:** The dynamic `gainPerLayer` computation in `bank.go` is already correct. No changes needed.
- **Touching `classify/types.go`:** `AllClasses()` is not modified in this phase.
- **Touching `WhisperFloor`:** It does not reference `NumLayers` or `GainPerLayer`; leave it unchanged.
---
## Don't Hand-Roll
Not applicable. This phase contains no algorithmic code — it is deletion and test rewriting.
---
## Common Pitfalls
### Pitfall 1: Leaving the `GainPerLayer` comment reference orphaned
**What goes wrong:** `GainPerLayer` at config.go line 10 has a comment `// D-10: ~0.0714`. After deletion, the decision reference D-10 disappears from the source. This is fine — D-10 is still documented in the planning research files. But if the comment is moved to `bank.go` line 21 (where the dynamic computation lives), it improves traceability without leaving an orphan.
**How to avoid:** Either delete both lines cleanly with no compensation, or add `// D-10: gain is 1/N computed dynamically` to `bank.go:21`. Both are acceptable.
**Warning signs:** Go compiler catches unused constants — if `NumLayers` or `GainPerLayer` are deleted and the code still compiles, they were indeed dead.
### Pitfall 2: Using `synth.SampleRate` in the test without verifying the export
**What goes wrong:** `SampleRate` is an exported constant in `synth/config.go`. The test file is in package `synth_test` (external test package), so it accesses `synth.SampleRate`. Verify `SampleRate` is exported (capital S) before referencing it from the test.
**How to avoid:** Already confirmed — `SampleRate = 44100` is exported at config.go line 6. No issue.
**Warning signs:** Compiler error `synth.sampleRate undefined` would indicate a lowercase constant.
### Pitfall 3: Test duplication confusion
**What goes wrong:** `TestAllClassesHaveConfig` and `TestClassFreqConfigsComplete` look different but test the same invariant. During code review or future debugging, someone might wonder why there are two tests for the same thing.
**How to avoid:** If consolidating (removing `TestClassFreqConfigsComplete`), add a comment to `TestAllClassesHaveConfig` noting it replaced the duplicate. If not consolidating, no action needed.
---
## Code Examples
### Resulting `synth/config.go` constant block
```go
// Source: synth/config.go — after Phase 8 cleanup
const (
SampleRate = 44100
WindowMs = 500
SamplesPerWindow = SampleRate * WindowMs / 1000 // 22050
WhisperFloor = 0.03 // D-08/D-09: 3% of max amplitude
)
```
### Resulting `TestFrequenciesInRange` (Nyquist approach)
```go
// Source: synth/config_test.go — after Phase 8 cleanup
func TestFrequenciesInRange(t *testing.T) {
const nyquist = float64(synth.SampleRate) / 2.0
for class, cfg := range synth.ClassFreqConfigs {
if cfg.BaseHz <= 0 {
t.Errorf("class %q BaseHz=%.1f must be positive", class, cfg.BaseHz)
}
if cfg.BaseHz >= nyquist {
t.Errorf("class %q BaseHz=%.1f exceeds Nyquist (%.1f Hz)", class, cfg.BaseHz, nyquist)
}
}
}
```
### Resulting `TestClassFreqConfigsMatchAllClasses`
```go
// Source: synth/config_test.go — after rename from TestNumLayersMatchesAllClasses
func TestClassFreqConfigsMatchAllClasses(t *testing.T) {
if len(synth.ClassFreqConfigs) != len(classify.AllClasses()) {
t.Errorf("ClassFreqConfigs has %d entries but AllClasses() has %d entries",
len(synth.ClassFreqConfigs), len(classify.AllClasses()))
}
}
```
---
## State of the Art
| Old Approach | Current Approach | When Changed | Impact |
|--------------|------------------|--------------|--------|
| `NumLayers = 14` static constant | Dynamic `1.0 / float64(len(cfgs))` in `NewBank` | Phase 6/7 (v1.1) | Static constant is now dead code; remove it |
| `TestFrequenciesInRange` checks `[60, 1100]` | Nyquist-based check (this phase) | Phase 8 (v1.2) | Test survives any future frequency allocation |
---
## Validation Architecture
### Test Framework
| Property | Value |
|----------|-------|
| Framework | `testing` stdlib, Go 1.24 |
| Config file | none (standard `go test`) |
| Quick run command | `go test ./synth/...` |
| Full suite command | `go test ./...` |
### Phase Requirements → Test Map
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|--------|----------|-----------|-------------------|-------------|
| CLEAN-01 (constant removal) | `NumLayers` and `GainPerLayer` are not exported from `synth` package | unit — compile check | `go build ./synth/...` | ✅ (config.go exists; delete lines) |
| CLEAN-01 (no broken references) | Full test suite passes after deletion | integration | `go test ./...` | ✅ |
| CLEAN-01 (range test future-proof) | `TestFrequenciesInRange` passes with any BaseHz in (0, Nyquist) range | unit | `go test ./synth/... -run TestFrequenciesInRange` | ✅ (config_test.go exists; update body) |
| CLEAN-01 (test rename) | `TestClassFreqConfigsMatchAllClasses` exists and passes | unit | `go test ./synth/... -run TestClassFreqConfigsMatchAllClasses` | ✅ (rename existing function) |
### Sampling Rate
- **Per task commit:** `go test ./synth/...`
- **Per wave merge:** `go test ./...`
- **Phase gate:** `go test ./...` green before `/gsd:verify-work`
### Wave 0 Gaps
None — existing test infrastructure covers all phase requirements. No new test files, fixtures, or framework setup needed.
---
## Environment Availability
Step 2.6: SKIPPED (no external dependencies — pure Go source edits, no new tools or services required).
Current test suite state confirmed: `go test ./...` passes on all 7 packages.
---
## Open Questions
1. **Consolidate `TestAllClassesHaveConfig` and `TestClassFreqConfigsComplete`?**
- What we know: They test the same invariant; both currently pass; no correctness issue either way
- What's unclear: Whether the planner wants one clean authoritative test or is fine leaving both
- Recommendation: Remove `TestClassFreqConfigsComplete` (lines 53-59) as it duplicates `TestAllClassesHaveConfig`. The named `TestAllClassesHaveConfig` is more expressive. If this causes any concern, leave both — both are correct.
2. **Add D-10 comment to `bank.go` after deleting `GainPerLayer`?**
- What we know: `GainPerLayer` carries `// D-10: ~0.0714`; bank.go line 21 is where the actual computation lives
- What's unclear: Whether the project wants decision-reference comments preserved at the implementation site
- Recommendation: Add `// D-10: gainPerLayer = 1/N so all N layers at full amplitude sum to 1.0` to bank.go line 21. Low-cost, improves traceability.
---
## Sources
### Primary (HIGH confidence)
- `synth/config.go` — Direct inspection: `NumLayers = 14`, `GainPerLayer = 1.0 / float64(NumLayers)` at lines 9-10; `SampleRate = 44100` at line 6
- `synth/bank.go` — Direct inspection: `gainPerLayer: 1.0 / float64(len(cfgs))` at line 21 — confirms constants are dead code
- `synth/config_test.go` — Direct inspection: `TestFrequenciesInRange` body at lines 18-25; `TestNumLayersMatchesAllClasses` at lines 61-66; `TestClassFreqConfigsComplete` at lines 53-59
- `classify/types.go` — Direct inspection: `AllClasses()` returns 14 entries; `SampleRate = 44100` used for Nyquist calculation
- `.planning/phases/08-test-and-constant-cleanup/08-CONTEXT.md` — Locked decisions D-01, D-02, D-03
- `.planning/research/PITFALLS.md` — Pitfall C3 (NumLayers stale constant) and C4 (TestFrequenciesInRange hardcoding)
- `.planning/research/ARCHITECTURE.md` lines 414-422 — NumLayers/GainPerLayer dead code analysis
### Secondary (MEDIUM confidence)
- `go test ./...` output — All 7 packages pass; current baseline confirmed
---
## Metadata
**Confidence breakdown:**
- Standard stack: HIGH — no new dependencies; pure stdlib
- Architecture: HIGH — all target lines verified by direct file inspection
- Pitfalls: HIGH — sourced from project research files and direct code inspection
**Research date:** 2026-03-27
**Valid until:** Until Phase 9 begins (frequency redistribution) — this research is tied to current `synth/config.go` line numbers which Phase 9 will change
@@ -0,0 +1,71 @@
---
phase: 8
slug: test-and-constant-cleanup
status: draft
nyquist_compliant: false
wave_0_complete: false
created: 2026-03-27
---
# Phase 8 — Validation Strategy
> Per-phase validation contract for feedback sampling during execution.
---
## Test Infrastructure
| Property | Value |
|----------|-------|
| **Framework** | go test (stdlib) |
| **Config file** | none — built-in Go test runner |
| **Quick run command** | `go test ./synth/...` |
| **Full suite command** | `go test ./...` |
| **Estimated runtime** | ~5 seconds |
---
## Sampling Rate
- **After every task commit:** Run `go test ./synth/...`
- **After every plan wave:** Run `go test ./...`
- **Before `/gsd:verify-work`:** Full suite must be green
- **Max feedback latency:** 5 seconds
---
## Per-Task Verification Map
| Task ID | Plan | Wave | Requirement | Test Type | Automated Command | File Exists | Status |
|---------|------|------|-------------|-----------|-------------------|-------------|--------|
| 08-01-01 | 01 | 1 | CLEAN-01 | unit | `go test ./synth/... -run TestFrequenciesInRange` | ✅ | ⬜ pending |
| 08-01-02 | 01 | 1 | CLEAN-01 | compile | `go build ./synth/...` | ✅ | ⬜ pending |
| 08-01-03 | 01 | 1 | CLEAN-01 | unit | `go test ./synth/... -run TestClassFreqConfigs` | ✅ | ⬜ pending |
| 08-01-04 | 01 | 1 | CLEAN-01 | integration | `go test ./...` | ✅ | ⬜ pending |
*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky*
---
## Wave 0 Requirements
*Existing infrastructure covers all phase requirements.*
---
## Manual-Only Verifications
*All phase behaviors have automated verification.*
---
## Validation Sign-Off
- [ ] All tasks have `<automated>` verify or Wave 0 dependencies
- [ ] Sampling continuity: no 3 consecutive tasks without automated verify
- [ ] Wave 0 covers all MISSING references
- [ ] No watch-mode flags
- [ ] Feedback latency < 5s
- [ ] `nyquist_compliant: true` set in frontmatter
**Approval:** pending
@@ -0,0 +1,91 @@
---
phase: 08-test-and-constant-cleanup
verified: 2026-03-27T10:00:00Z
status: passed
score: 4/4 must-haves verified
re_verification: false
---
# Phase 8: Test and Constant Cleanup Verification Report
**Phase Goal:** Pre-existing test assertions and a stale exported constant that would block or mislead all subsequent v1.2 work are removed
**Verified:** 2026-03-27
**Status:** passed
**Re-verification:** No — initial verification
## Goal Achievement
### Observable Truths
| # | Truth | Status | Evidence |
|---|-------|--------|----------|
| 1 | NumLayers and GainPerLayer constants do not exist in the synth package | VERIFIED | `grep -n "NumLayers\|GainPerLayer" synth/config.go synth/bank.go synth/config_test.go` returns zero matches (exit 1 = no matches) |
| 2 | `go test ./...` passes with zero failures after all edits | VERIFIED | All 7 packages green: aggregate, capture, classify, cmd/netsynth, config, encode, synth |
| 3 | TestFrequenciesInRange accepts any BaseHz in (0, Nyquist) without manual test surgery | VERIFIED | `const nyquist = float64(synth.SampleRate) / 2.0` at line 19; hardcoded `1100` bound absent (grep returns exit 1); test passes |
| 4 | TestNumLayersMatchesAllClasses is renamed to TestClassFreqConfigsMatchAllClasses | VERIFIED | `TestClassFreqConfigsMatchAllClasses` present at line 56; `TestNumLayersMatchesAllClasses` absent; `TestClassFreqConfigsComplete` absent |
**Score:** 4/4 truths verified
### Required Artifacts
| Artifact | Expected | Status | Details |
|----------|----------|--------|---------|
| `synth/config.go` | Cleaned constant block without NumLayers or GainPerLayer; contains WhisperFloor | VERIFIED | Constant block contains SampleRate, WindowMs, SamplesPerWindow, WhisperFloor only (lines 5-10). NumLayers and GainPerLayer absent. |
| `synth/config_test.go` | Future-proof test assertions; contains TestClassFreqConfigsMatchAllClasses | VERIFIED | Nyquist-based validation in TestFrequenciesInRange (line 19). TestClassFreqConfigsMatchAllClasses present (line 56). TestClassFreqConfigsComplete absent. |
### Key Link Verification
| From | To | Via | Status | Details |
|------|----|-----|--------|---------|
| `synth/bank.go` | `synth/config.go` | gainPerLayer computed dynamically in NewBank — no static constant conflicts | VERIFIED | `gainPerLayer: 1.0 / float64(len(cfgs))` at line 21 of bank.go; no reference to the deleted NumLayers constant anywhere in synth package |
| `synth/config_test.go` | `synth/config.go` | TestFrequenciesInRange validates BaseHz against SampleRate-derived Nyquist | VERIFIED | `float64(synth.SampleRate) / 2.0` at line 19 of config_test.go; test runs and passes |
### Data-Flow Trace (Level 4)
Not applicable. This phase modifies a constants file and test file only — no dynamic data rendering involved.
### Behavioral Spot-Checks
| Behavior | Command | Result | Status |
|----------|---------|--------|--------|
| TestFrequenciesInRange passes | `go test ./synth/... -run TestFrequenciesInRange -count=1` | PASS | VERIFIED |
| TestClassFreqConfigsMatchAllClasses passes | `go test ./synth/... -run TestClassFreqConfigsMatchAllClasses -count=1` | PASS | VERIFIED |
| Full suite green | `go test ./...` | All 7 packages ok | VERIFIED |
| Both task commits exist | `git show --stat fd17061 4800e8e` | Both commits present with correct file changes | VERIFIED |
### Requirements Coverage
| Requirement | Source Plan | Description | Status | Evidence |
|-------------|------------|-------------|--------|----------|
| CLEAN-01 | 08-01-PLAN.md | Remove stale NumLayers constant and hardcoded frequency range test assertions that would block new class additions | SATISFIED | NumLayers and GainPerLayer deleted from synth/config.go (commit fd17061); hardcoded 1100 Hz bound replaced with Nyquist-based validation in config_test.go (commit 4800e8e) |
**Orphaned requirements check:** REQUIREMENTS.md maps only CLEAN-01 to Phase 8. The plan declares CLEAN-01. No orphaned requirements.
### Anti-Patterns Found
| File | Line | Pattern | Severity | Impact |
|------|------|---------|----------|--------|
| — | — | None found | — | — |
No TODOs, FIXMEs, placeholders, empty returns, or stub indicators in the modified files.
### Human Verification Required
None. All phase deliverables are code-verifiable (constant deletion and test assertions checked programmatically).
### Gaps Summary
No gaps. All four must-have truths are verified against the actual codebase:
- `synth/config.go` constant block contains exactly SampleRate, WindowMs, SamplesPerWindow, and WhisperFloor — the two stale constants are gone.
- `synth/config_test.go` uses Nyquist-derived bounds (22050 Hz) with no hardcoded 1100 Hz upper limit — adding a new class at any frequency up to 22050 Hz requires no test edits.
- The old `TestNumLayersMatchesAllClasses` name is gone; `TestClassFreqConfigsMatchAllClasses` replaced it with identical body.
- The duplicate `TestClassFreqConfigsComplete` is removed.
- All 7 packages pass `go test ./...` with zero failures.
The phase goal is fully achieved: no stale constants or hardcoded test bounds remain to block v1.2 work.
---
_Verified: 2026-03-27_
_Verifier: Claude (gsd-verifier)_