29 KiB
Phase 10: Classification Layer - Research
Researched: 2026-03-27 Domain: Go classify package extension — new TrafficClass constants, DefaultRules port entries, AllClasses() update, test coverage Confidence: HIGH — this is pure in-codebase extension with no new dependencies. All patterns are established in the existing code.
<user_constraints>
User Constraints (from CONTEXT.md)
Locked Decisions
- D-01: LDAP, Kerberos, and Syslog get built-in TrafficClass constants and classification rules, but their synthesis frequencies are handled by
autoAssignFreq(FNV hash in [2500, 4000] Hz) rather than designed table slots. No Phase 9 frequency table redesign needed. - D-02: These 3 classes do NOT get entries in
ClassFreqConfigsin this phase — they are treated like user-defined custom classes for frequency purposes. Phase 11 will add theirClassFreqConfigsentries usingautoAssignFreq-compatible Hz values. - D-03: Plaintext and TLS/secure variants of the same protocol share a single TrafficClass. Multiple ports map to the same class constant: IMAP (143) + IMAPS (993) → ClassIMAP, POP3 (110) + POP3S (995) → ClassPOP3, LDAP (389) + LDAPS (636) → ClassLDAP, SIP (5060) + SIPS (5061) → ClassSIP, FTP data (20) + FTP control (21) → ClassFTP.
- D-04: Exception: SMTP (port 25) and SMTP-submission (port 587) remain separate classes (ClassSMTP and ClassSMTPSub) because Phase 9 designed distinct frequency slots for them (214 Hz and 305 Hz respectively).
- D-05: Follow existing uppercase convention for TrafficClass string values: "IMAP", "POP3", "RDP", "MySQL", etc. Matches existing "ICMP", "DNS", "HTTPS" pattern. SMTP-submission uses "SMTP-sub" to match the Phase 9 frequency table label.
- D-06: New port-specific rules insert before the catch-all
{tcp, 0, ClassOtherTCP}and{udp, 0, ClassOtherUDP}entries. Catch-alls remain last. First-match-wins semantics preserved.
Claude's Discretion
- Exact ordering of new rules within the specific-port section (before catch-alls)
- Test structure — whether to extend existing TestClassify or add new test functions
- How to update
TestAllClassesCount(hardcoded to 14) — update the count or make it dynamic - Whether to group rules by family in
DefaultRuleswith comments, or keep flat
Deferred Ideas (OUT OF SCOPE)
None — discussion stayed within phase scope. </user_constraints>
<phase_requirements>
Phase Requirements
| ID | Description | Research Support |
|---|---|---|
| PROTO-01 | Add Mail family protocols: IMAP (143/993), POP3 (110/995), SMTP-submission (587) | 5 new rules (IMAP×2, POP3×2, SMTPSub×1), 3 new constants (ClassIMAP, ClassPOP3, ClassSMTPSub) |
| PROTO-02 | Add File Transfer family protocols: FTP (20-21), SMB/CIFS (445), TFTP (69) | 4 new rules (FTP×2, SMB×1, TFTP×1), 3 new constants (ClassFTP, ClassSMB, ClassTFTP) |
| PROTO-03 | Add Remote Access family protocols: RDP (3389), Telnet (23), VNC (5900) | 3 new rules, 3 new constants (ClassRDP, ClassTelnet, ClassVNC) |
| PROTO-04 | Add Database family protocols: MySQL (3306), PostgreSQL (5432), Redis (6379), MongoDB (27017) | 4 new rules, 4 new constants (ClassMySQL, ClassPostgreSQL, ClassRedis, ClassMongoDB) |
| PROTO-05 | Add Discovery family protocols: mDNS (5353), SSDP/UPnP (1900), SNMP (161-162) | 4 new rules (mDNS×1, SSDP×1, SNMP×2), 3 new constants (ClassMDNS, ClassSSDP, ClassSNMP) |
| PROTO-06 | Add VoIP family: SIP (5060/5061) | 4 new rules (TCP+UDP × 2 ports), 1 new constant (ClassSIP) — D-03 collapses 5060/5061 into one class |
| PROTO-07 | Add Web family extension: QUIC/HTTP3 (UDP 443) | 1 new rule, 1 new constant (ClassQUIC) |
| PROTO-08 | Add Infrastructure family protocols: LDAP (389/636), Kerberos (88), Syslog (514) | 5 new rules (LDAP×2 TCP, Kerberos×2 TCP+UDP, Syslog×1), 3 new constants; D-01/D-02: no ClassFreqConfigs entries this phase |
| PROTO-09 | Existing 10 protocol classifications remain unchanged — no regression | Rule ordering D-06 + test coverage for every existing class |
| </phase_requirements> |
Summary
Phase 10 is a mechanical expansion of three files in the classify package: types.go (new constants + AllClasses() update), rules.go (new Rule entries in DefaultRules), and classifier_test.go (new subtests). No new packages, no new dependencies, no architectural changes.
The critical cross-package constraint is TestClassFreqConfigsMatchAllClasses in synth/config_test.go, which asserts len(ClassFreqConfigs) == len(AllClasses()). Adding new constants to AllClasses() without adding their ClassFreqConfigs entries will fail that test. Decision D-01/D-02 explicitly defers LDAP, Kerberos, and Syslog ClassFreqConfigs entries to Phase 11 — so these three classes must be excluded from AllClasses() for now, OR the synth test must be temporarily adjusted. This is the single most important planning decision of the phase.
For the 18 classes that DO get ClassFreqConfigs entries in Phase 9's design table (all new protocols except LDAP, Kerberos, Syslog), Phase 11 will add those entries — but Phase 10 only adds the constants and rules. This means TestAllClassesHaveConfig and TestClassFreqConfigsMatchAllClasses will fail for any class added to AllClasses() without a matching ClassFreqConfigs entry. The plan must account for this explicitly.
Primary recommendation: Add LDAP, Kerberos, and Syslog as TrafficClass constants and DefaultRules entries, but exclude them from AllClasses() for now. Update TestAllClassesCount from 14 to the new count covering only classes that have (or will have from Phase 9's existing table) ClassFreqConfigs entries. The remaining three get added to AllClasses() in Phase 11 when their ClassFreqConfigs entries are written.
Standard Stack
No new libraries. This phase uses only what is already in the codebase.
| Component | Location | Purpose |
|---|---|---|
TrafficClass string type |
classify/types.go |
Type for all new constants |
Rule struct |
classify/rules.go |
Container for each new port-matching rule |
AllClasses() slice |
classify/types.go |
Must be updated with new constants (carefully — see pitfall below) |
DefaultRules slice |
classify/rules.go |
Must receive new rules before catch-alls |
buildTCPPacket / buildUDPPacket helpers |
classify/classifier_test.go |
Reuse for all new test cases; no new test infrastructure needed |
Installation: No new packages required.
Complete Protocol Inventory
All 21 new TrafficClass constants, their string values (D-05), and all port-rule mappings (D-03/D-04).
Mail Family (PROTO-01)
| Constant | String Value | Protocol | Port | Transport |
|---|---|---|---|---|
ClassIMAP |
"IMAP" |
IMAP + IMAPS | 143, 993 | TCP |
ClassPOP3 |
"POP3" |
POP3 + POP3S | 110, 995 | TCP |
ClassSMTPSub |
"SMTP-sub" |
SMTP submission | 587 | TCP |
Rules needed: 5 (IMAP×2, POP3×2, SMTPSub×1)
File Transfer Family (PROTO-02)
| Constant | String Value | Protocol | Port | Transport |
|---|---|---|---|---|
ClassFTP |
"FTP" |
FTP data + control | 20, 21 | TCP |
ClassSMB |
"SMB" |
SMB/CIFS | 445 | TCP |
ClassTFTP |
"TFTP" |
TFTP | 69 | UDP |
Rules needed: 4 (FTP×2, SMB×1, TFTP×1)
Remote Access Family (PROTO-03)
| Constant | String Value | Protocol | Port | Transport |
|---|---|---|---|---|
ClassRDP |
"RDP" |
Remote Desktop | 3389 | TCP |
ClassTelnet |
"Telnet" |
Telnet | 23 | TCP |
ClassVNC |
"VNC" |
VNC / RFB | 5900 | TCP |
Rules needed: 3
Database Family (PROTO-04)
| Constant | String Value | Protocol | Port | Transport |
|---|---|---|---|---|
ClassMySQL |
"MySQL" |
MySQL/MariaDB | 3306 | TCP |
ClassPostgreSQL |
"PostgreSQL" |
PostgreSQL | 5432 | TCP |
ClassRedis |
"Redis" |
Redis | 6379 | TCP |
ClassMongoDB |
"MongoDB" |
MongoDB | 27017 | TCP |
Rules needed: 4
Discovery / Infrastructure Extension (PROTO-05)
| Constant | String Value | Protocol | Port | Transport |
|---|---|---|---|---|
ClassMDNS |
"mDNS" |
Multicast DNS | 5353 | UDP |
ClassSSDP |
"SSDP" |
SSDP/UPnP | 1900 | UDP |
ClassSNMP |
"SNMP" |
SNMP | 161, 162 | UDP |
Rules needed: 4 (mDNS×1, SSDP×1, SNMP×2)
VoIP Family (PROTO-06)
| Constant | String Value | Protocol | Port | Transport |
|---|---|---|---|---|
ClassSIP |
"SIP" |
SIP + SIPS | 5060, 5061 | TCP + UDP |
Rules needed: 4 (TCP/UDP × 2 ports — D-03 collapses to one class)
Web Extension (PROTO-07)
| Constant | String Value | Protocol | Port | Transport |
|---|---|---|---|---|
ClassQUIC |
"QUIC" |
QUIC / HTTP3 | 443 | UDP |
Rules needed: 1
Infrastructure PROTO-08 (D-01/D-02: no ClassFreqConfigs in Phase 10)
| Constant | String Value | Protocol | Port | Transport |
|---|---|---|---|---|
ClassLDAP |
"LDAP" |
LDAP + LDAPS | 389, 636 | TCP |
ClassKerberos |
"Kerberos" |
Kerberos | 88 | TCP + UDP |
ClassSyslog |
"Syslog" |
Syslog | 514 | UDP |
Rules needed: 5 (LDAP×2, Kerberos×2, Syslog×1)
Total new rules: 30 (5+4+3+4+4+4+1+5) Total new constants: 21
Architecture Patterns
TrafficClass constant pattern (types.go)
// Source: classify/types.go existing pattern
const (
ClassIMAP TrafficClass = "IMAP"
ClassPOP3 TrafficClass = "POP3"
ClassSMTPSub TrafficClass = "SMTP-sub"
ClassFTP TrafficClass = "FTP"
ClassSMB TrafficClass = "SMB"
ClassTFTP TrafficClass = "TFTP"
ClassRDP TrafficClass = "RDP"
ClassTelnet TrafficClass = "Telnet"
ClassVNC TrafficClass = "VNC"
ClassMySQL TrafficClass = "MySQL"
ClassPostgreSQL TrafficClass = "PostgreSQL"
ClassRedis TrafficClass = "Redis"
ClassMongoDB TrafficClass = "MongoDB"
ClassMDNS TrafficClass = "mDNS"
ClassSSDP TrafficClass = "SSDP"
ClassSNMP TrafficClass = "SNMP"
ClassSIP TrafficClass = "SIP"
ClassQUIC TrafficClass = "QUIC"
// D-01: LDAP/Kerberos/Syslog classified but no ClassFreqConfigs until Phase 11
ClassLDAP TrafficClass = "LDAP"
ClassKerberos TrafficClass = "Kerberos"
ClassSyslog TrafficClass = "Syslog"
)
AllClasses() update with LDAP/Kerberos/Syslog exclusion
The 18 classes with designed Phase 9 frequency slots go into AllClasses(). LDAP, Kerberos, and Syslog are excluded until Phase 11:
// Source: classify/types.go
func AllClasses() []TrafficClass {
return []TrafficClass{
// Infrastructure
ClassICMP, ClassDNS, ClassNTP, ClassDHCP,
ClassMDNS, ClassSSDP, ClassSNMP,
// Web
ClassHTTPS, ClassHTTP, ClassQUIC,
// Mail
ClassSMTP, ClassIMAP, ClassPOP3, ClassSMTPSub,
// Remote Access
ClassSSH, ClassRDP, ClassTelnet, ClassVNC,
// File Transfer
ClassFTP, ClassSMB, ClassTFTP,
// Database
ClassMySQL, ClassPostgreSQL, ClassRedis, ClassMongoDB,
// VoIP
ClassSIP,
// Unknown/catch-all
ClassUnknown1, ClassUnknown2, ClassUnknown3, ClassUnknown4,
ClassOtherTCP, ClassOtherUDP,
}
}
// Count: 32 classes (14 existing + 18 new, excluding LDAP/Kerberos/Syslog)
DefaultRules pattern with family grouping (rules.go)
// Source: classify/rules.go existing pattern
var DefaultRules = []Rule{
// --- Infrastructure (existing) ---
{Protocol: "icmp", DstPort: 0, Class: ClassICMP},
{Protocol: "udp", DstPort: 53, Class: ClassDNS},
{Protocol: "tcp", DstPort: 53, Class: ClassDNS},
// ... NTP, DHCP ...
// --- Infrastructure extension (PROTO-05) ---
{Protocol: "udp", DstPort: 5353, Class: ClassMDNS},
{Protocol: "udp", DstPort: 1900, Class: ClassSSDP},
{Protocol: "udp", DstPort: 161, Class: ClassSNMP},
{Protocol: "udp", DstPort: 162, Class: ClassSNMP},
// --- Web (existing + PROTO-07) ---
{Protocol: "tcp", DstPort: 443, Class: ClassHTTPS},
{Protocol: "udp", DstPort: 443, Class: ClassQUIC},
{Protocol: "tcp", DstPort: 80, Class: ClassHTTP},
// --- Mail (existing SMTP + PROTO-01) ---
{Protocol: "tcp", DstPort: 25, Class: ClassSMTP},
{Protocol: "tcp", DstPort: 143, Class: ClassIMAP},
{Protocol: "tcp", DstPort: 993, Class: ClassIMAP},
{Protocol: "tcp", DstPort: 110, Class: ClassPOP3},
{Protocol: "tcp", DstPort: 995, Class: ClassPOP3},
{Protocol: "tcp", DstPort: 587, Class: ClassSMTPSub},
// --- Remote Access (existing SSH + PROTO-03) ---
{Protocol: "tcp", DstPort: 22, Class: ClassSSH},
{Protocol: "tcp", DstPort: 3389, Class: ClassRDP},
{Protocol: "tcp", DstPort: 23, Class: ClassTelnet},
{Protocol: "tcp", DstPort: 5900, Class: ClassVNC},
// --- File Transfer (PROTO-02) ---
{Protocol: "tcp", DstPort: 20, Class: ClassFTP},
{Protocol: "tcp", DstPort: 21, Class: ClassFTP},
{Protocol: "tcp", DstPort: 445, Class: ClassSMB},
{Protocol: "udp", DstPort: 69, Class: ClassTFTP},
// --- Database (PROTO-04) ---
{Protocol: "tcp", DstPort: 3306, Class: ClassMySQL},
{Protocol: "tcp", DstPort: 5432, Class: ClassPostgreSQL},
{Protocol: "tcp", DstPort: 6379, Class: ClassRedis},
{Protocol: "tcp", DstPort: 27017, Class: ClassMongoDB},
// --- VoIP (PROTO-06) ---
{Protocol: "tcp", DstPort: 5060, Class: ClassSIP},
{Protocol: "tcp", DstPort: 5061, Class: ClassSIP},
{Protocol: "udp", DstPort: 5060, Class: ClassSIP},
{Protocol: "udp", DstPort: 5061, Class: ClassSIP},
// --- Infrastructure / Auth (PROTO-08, D-01) ---
{Protocol: "tcp", DstPort: 389, Class: ClassLDAP},
{Protocol: "tcp", DstPort: 636, Class: ClassLDAP},
{Protocol: "tcp", DstPort: 88, Class: ClassKerberos},
{Protocol: "udp", DstPort: 88, Class: ClassKerberos},
{Protocol: "udp", DstPort: 514, Class: ClassSyslog},
// Catch-alls (must be last):
{Protocol: "tcp", DstPort: 0, Class: ClassOtherTCP},
{Protocol: "udp", DstPort: 0, Class: ClassOtherUDP},
}
Test pattern — new subtests in TestClassify
// Source: classify/classifier_test.go existing subtest pattern
t.Run("TestClassifyIMAP_port143", func(t *testing.T) {
pkt := buildTCPPacket(t, 143)
got := c.Classify(pkt)
if got.Class != classify.ClassIMAP {
t.Errorf("IMAP port 143: got class %q, want %q", got.Class, classify.ClassIMAP)
}
})
t.Run("TestClassifyIMAP_port993", func(t *testing.T) {
pkt := buildTCPPacket(t, 993)
got := c.Classify(pkt)
if got.Class != classify.ClassIMAP {
t.Errorf("IMAPS port 993: got class %q, want %q", got.Class, classify.ClassIMAP)
}
})
Don't Hand-Roll
| Problem | Don't Build | Use Instead |
|---|---|---|
| Multi-port classes (D-03) | A special data structure for port groups | Two separate Rule entries pointing to the same Class constant — the classifier's first-match-wins loop handles it automatically |
| Protocol detection | Application-layer DPI parsing | Port-number matching via existing Rule struct — confirmed sufficient for all v1.2 protocols |
| Test helpers | New packet builders | Reuse buildTCPPacket and buildUDPPacket from the existing test file — they accept any port number |
Common Pitfalls
Pitfall 1: TestClassFreqConfigsMatchAllClasses breaks when LDAP/Kerberos/Syslog are added to AllClasses()
What goes wrong: synth/config_test.go:TestClassFreqConfigsMatchAllClasses asserts len(ClassFreqConfigs) == len(AllClasses()). If LDAP, Kerberos, or Syslog are added to AllClasses() in Phase 10 (without their ClassFreqConfigs entries — deferred to Phase 11 per D-02), this test fails.
Why it happens: The test enforces bidirectional coverage: every class in AllClasses() must have a ClassFreqConfigs entry and vice versa.
How to avoid: Exclude LDAP, Kerberos, Syslog from AllClasses() in Phase 10. They get constants and rules, but AllClasses() includes them only when Phase 11 adds their ClassFreqConfigs entries. This means go test ./classify/... passes, but go test ./synth/... also passes because those three classes never appear in AllClasses() yet.
Warning signs: TestAllClassesHaveConfig in synth/config_test.go reports "class X has no entry in ClassFreqConfigs" — this means a class was added to AllClasses() without its synth config.
Pitfall 2: TestAllClassesCount must be updated from 14
What goes wrong: classify/classifier_test.go:TestAllClassesCount hardcodes if len(classes) != 14. After adding 18 classes to AllClasses(), the count becomes 32. The test fails with "got 32, want 14".
Why it happens: The count is hardcoded, not derived from the constants.
How to avoid: Update the assertion to != 32 (or make it dynamic with len(classify.AllClasses()) in a separate count-verification approach). The CONTEXT.md grants discretion on this — updating the hardcoded value is the simplest approach.
Warning signs: Test output: AllClasses() returned 32 classes, want 14.
Pitfall 3: Catch-all rules pushed out of last position
What goes wrong: If new rules are appended after the catch-alls {tcp, 0, ClassOtherTCP} and {udp, 0, ClassOtherUDP}, the catch-alls match first and new specific rules are unreachable. Every packet on new ports would hit ClassOtherTCP or ClassOtherUDP instead.
Why it happens: First-match-wins. DstPort 0 means "match any port" — it catches everything if it appears before specific-port rules.
How to avoid: All new specific-port rules must appear BEFORE the two catch-alls. The catch-all comment // Catch-alls (must be last): is the insertion boundary. D-06 enforces this explicitly.
Warning signs: Classifier test for new protocols returns ClassOtherTCP or ClassOtherUDP instead of the expected class.
Pitfall 4: UDP 443 (QUIC) rule must come BEFORE the TCP 443 (HTTPS) rule — or after, depending on implementation
What goes wrong: The classifier checks TCP before UDP (see classifier.go — TCP layer checked first). A UDP packet on port 443 will NOT match the TCP 443 rule because the classifier correctly identifies the transport. However, if someone accidentally writes the QUIC rule as {Protocol: "tcp", DstPort: 443, Class: ClassQUIC}, it would shadow the existing HTTPS rule.
How to avoid: Confirm the QUIC rule uses Protocol: "udp". The existing TCP 443 HTTPS rule is unchanged.
Warning signs: HTTPS test (TCP port 443) returns ClassQUIC, or QUIC test (UDP port 443) returns ClassHTTPS.
Pitfall 5: SIP dual-transport requires 4 rules, not 2
What goes wrong: SIP runs on both TCP and UDP for ports 5060 and 5061. D-03 collapses both ports into one class, but four separate rules are needed: TCP/5060, TCP/5061, UDP/5060, UDP/5061.
How to avoid: Write all four rules explicitly. The test coverage should include at minimum a UDP/5060 test and a TCP/5060 test to verify both transport paths.
Pitfall 6: SMTP port 465 (SMTPS) not required by PROTO-01
What goes wrong: FEATURES.md mentions port 465 as a SMTP submission port alongside 587. PROTO-01 only lists 587.
How to avoid: Only add port 587 for ClassSMTPSub per the requirement. Port 465 can be added as a user custom rule if needed. Do not over-reach the requirement.
Integration Point: synth/config_test.go Cross-Check
The TestAllClassesHaveConfig test in synth/config_test.go iterates AllClasses() and checks each against ClassFreqConfigs. This creates a hard dependency: AllClasses() must only contain classes that have ClassFreqConfigs entries at any given commit.
Phase 9 ClassFreqConfigs currently has: 14 entries (the original classes). Phase 9's frequency table in the comment at synth/config.go:74-110 lists entries labeled (Phase 10) but those entries do not yet exist in ClassFreqConfigs — they are design intent only.
What Phase 10 adds to AllClasses(): 18 new classes (all except LDAP, Kerberos, Syslog).
What Phase 11 must add to ClassFreqConfigs: Those same 18 classes PLUS LDAP, Kerberos, Syslog.
Consequence: After Phase 10, go test ./synth/... will fail TestAllClassesHaveConfig for all 18 new classes added to AllClasses(), because their ClassFreqConfigs entries don't exist yet. The plan must acknowledge this: Phase 10's go test ./classify/... passes (the success criterion), but go test ./synth/... is expected to fail until Phase 11.
Alternatively, the plan can note that go test ./synth/... is intentionally broken between Phase 10 and Phase 11, and the phase gate is go test ./classify/... only, per the success criteria stated in CONTEXT.md.
Validation Architecture
Test Framework
| Property | Value |
|---|---|
| Framework | Go testing (stdlib) |
| Config file | none |
| Quick run command | go test ./classify/... |
| Full suite command | go test ./... |
Phase Requirements → Test Map
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|---|---|---|---|---|
| PROTO-01 | IMAP port 143 → ClassIMAP | unit | go test ./classify/... -run TestClassify/TestClassifyIMAP |
❌ Wave 0 |
| PROTO-01 | IMAPS port 993 → ClassIMAP | unit | go test ./classify/... -run TestClassify/TestClassifyIMAP |
❌ Wave 0 |
| PROTO-01 | POP3 port 110 → ClassPOP3 | unit | go test ./classify/... -run TestClassify/TestClassifyPOP3 |
❌ Wave 0 |
| PROTO-01 | POP3S port 995 → ClassPOP3 | unit | go test ./classify/... -run TestClassify/TestClassifyPOP3 |
❌ Wave 0 |
| PROTO-01 | SMTP-sub port 587 → ClassSMTPSub | unit | go test ./classify/... -run TestClassify/TestClassifySMTPSub |
❌ Wave 0 |
| PROTO-02 | FTP port 20 → ClassFTP | unit | go test ./classify/... -run TestClassify/TestClassifyFTP |
❌ Wave 0 |
| PROTO-02 | FTP port 21 → ClassFTP | unit | go test ./classify/... -run TestClassify/TestClassifyFTP |
❌ Wave 0 |
| PROTO-02 | SMB port 445 → ClassSMB | unit | go test ./classify/... -run TestClassify/TestClassifySMB |
❌ Wave 0 |
| PROTO-02 | TFTP port 69 → ClassTFTP | unit | go test ./classify/... -run TestClassify/TestClassifyTFTP |
❌ Wave 0 |
| PROTO-03 | RDP port 3389 → ClassRDP | unit | go test ./classify/... -run TestClassify/TestClassifyRDP |
❌ Wave 0 |
| PROTO-03 | Telnet port 23 → ClassTelnet | unit | go test ./classify/... -run TestClassify/TestClassifyTelnet |
❌ Wave 0 |
| PROTO-03 | VNC port 5900 → ClassVNC | unit | go test ./classify/... -run TestClassify/TestClassifyVNC |
❌ Wave 0 |
| PROTO-04 | MySQL port 3306 → ClassMySQL | unit | go test ./classify/... -run TestClassify/TestClassifyMySQL |
❌ Wave 0 |
| PROTO-04 | PostgreSQL port 5432 → ClassPostgreSQL | unit | go test ./classify/... -run TestClassify/TestClassifyPostgreSQL |
❌ Wave 0 |
| PROTO-04 | Redis port 6379 → ClassRedis | unit | go test ./classify/... -run TestClassify/TestClassifyRedis |
❌ Wave 0 |
| PROTO-04 | MongoDB port 27017 → ClassMongoDB | unit | go test ./classify/... -run TestClassify/TestClassifyMongoDB |
❌ Wave 0 |
| PROTO-05 | mDNS port 5353 → ClassMDNS | unit | go test ./classify/... -run TestClassify/TestClassifyMDNS |
❌ Wave 0 |
| PROTO-05 | SSDP port 1900 → ClassSSDP | unit | go test ./classify/... -run TestClassify/TestClassifySSDDP |
❌ Wave 0 |
| PROTO-05 | SNMP port 161 → ClassSNMP | unit | go test ./classify/... -run TestClassify/TestClassifySNMP |
❌ Wave 0 |
| PROTO-05 | SNMP port 162 → ClassSNMP | unit | go test ./classify/... -run TestClassify/TestClassifySNMP |
❌ Wave 0 |
| PROTO-06 | SIP TCP 5060 → ClassSIP | unit | go test ./classify/... -run TestClassify/TestClassifySIP |
❌ Wave 0 |
| PROTO-06 | SIP UDP 5060 → ClassSIP | unit | go test ./classify/... -run TestClassify/TestClassifySIP |
❌ Wave 0 |
| PROTO-07 | QUIC UDP 443 → ClassQUIC | unit | go test ./classify/... -run TestClassify/TestClassifyQUIC |
❌ Wave 0 |
| PROTO-08 | LDAP port 389 → ClassLDAP | unit | go test ./classify/... -run TestClassify/TestClassifyLDAP |
❌ Wave 0 |
| PROTO-08 | Kerberos port 88 → ClassKerberos | unit | go test ./classify/... -run TestClassify/TestClassifyKerberos |
❌ Wave 0 |
| PROTO-08 | Syslog port 514 → ClassSyslog | unit | go test ./classify/... -run TestClassify/TestClassifySyslog |
❌ Wave 0 |
| PROTO-09 | All 10 existing classes unchanged | unit | go test ./classify/... -run TestClassify |
✅ exists |
Sampling Rate
- Per task commit:
go test ./classify/... - Per wave merge:
go test ./classify/... - Phase gate:
go test ./classify/...green before/gsd:verify-work(synth/ is allowed to fail until Phase 11)
Wave 0 Gaps
All new test subtests for PROTO-01 through PROTO-08 must be added in the implementation task. They extend the existing TestClassify function in classify/classifier_test.go — no new test file required, no new test infrastructure.
classify/classifier_test.go— extendTestClassifywith ~26 new subtests (one per port/protocol combination listed above)classify/classifier_test.go— updateTestAllClassesCounthardcoded value from 14 to 32
Open Questions
-
Should TestAllClassesCount be updated to a hardcoded 32, or made dynamic?
- What we know: CONTEXT.md grants discretion on this. The current implementation is
!= 14. - What's unclear: A hardcoded count becomes stale again when Phase 11 adds LDAP/Kerberos/Syslog.
- Recommendation: Update to
!= 32now (matching the post-Phase-10 reality), and update again to!= 35in Phase 11. This is clearer than a dynamic check that could mask missing entries.
- What we know: CONTEXT.md grants discretion on this. The current implementation is
-
Do LDAP, Kerberos, Syslog go into AllClasses() in Phase 10 or Phase 11?
- What we know: D-02 says their ClassFreqConfigs entries are deferred to Phase 11. TestAllClassesHaveConfig and TestClassFreqConfigsMatchAllClasses will fail if they appear in AllClasses() without ClassFreqConfigs entries.
- What's unclear: Whether the plan should explicitly skip/ignore
go test ./synth/...in Phase 10 or avoid the failure entirely by keeping those three out of AllClasses(). - Recommendation: Keep them OUT of AllClasses() in Phase 10. Constants and rules exist, but they function as "classified but not yet in the display set." This approach keeps all test suites green simultaneously. Phase 11 adds them to AllClasses() when it also adds their ClassFreqConfigs entries.
Environment Availability
Step 2.6: SKIPPED — this phase is purely code/config changes within the classify package. No external tools, databases, or CLI utilities are required. go test ./classify/... requires only the Go toolchain already verified as in use.
Project Constraints (from CLAUDE.md)
| Directive | Impact on Phase 10 |
|---|---|
| Language: Go | All new code is Go |
| Single binary output | No new external packages; classify package has no CGo |
| Non-interactive capture model | No interaction model changes |
| GSD Workflow Enforcement | All edits via GSD workflow (execute-phase) |
Stack: gopacket/gopacket v1.5.0 |
No change — used only for test packet building |
No google/gopacket |
No change — already using community fork |
No go-audio/generator |
N/A — not audio-related |
| Conventions: populate as patterns emerge | Grouping rules by family with comments is consistent with established Phase 9 comment style in synth/config.go |
Sources
Primary (HIGH confidence)
classify/types.go— current 14 constants, AllClasses() implementation (direct code read)classify/rules.go— current 12 rules, DefaultRules pattern (direct code read)classify/classifier.go— Classify() method, rule matching logic, first-match-wins semantics (direct code read)classify/classifier_test.go— existing test helpers, TestAllClassesCount=14 (direct code read)synth/config_test.go— TestClassFreqConfigsMatchAllClasses, TestAllClassesHaveConfig cross-checks (direct code read)synth/config.go lines 74-110— Phase 9 frequency allocation table comment showing all Phase 10 target classes with Hz values (direct code read).planning/phases/10-classification-layer/10-CONTEXT.md— locked decisions D-01 through D-06 (direct read).planning/REQUIREMENTS.md— PROTO-01 through PROTO-09 definitions (direct read).planning/research/FEATURES.md— complete protocol list, port assignments, family groupings (direct read)
Secondary (MEDIUM confidence)
- IANA Service Name and Transport Protocol Port Number Registry — port assignments for IMAP (143/993), POP3 (110/995), SMTP-sub (587), FTP (20/21), SMB (445), TFTP (69), RDP (3389), mDNS (5353), SSDP (1900), SNMP (161/162), SIP (5060/5061), LDAP (389/636), Kerberos (88), Syslog (514) — all standard, well-known ports
Metadata
Confidence breakdown:
- Standard stack: HIGH — no new dependencies; all patterns directly observed in codebase
- Architecture: HIGH — mechanical extension of established Rule/TrafficClass pattern; no design uncertainty
- Pitfalls: HIGH — derived directly from the existing test assertions and cross-package contracts
Research date: 2026-03-27 Valid until: Stable — pure code extension, no external dependency versions to track