Files
yoloyolo/.planning/phases/10-classification-layer/10-RESEARCH.md
T

29 KiB
Raw Blame History

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 ClassFreqConfigs in this phase — they are treated like user-defined custom classes for frequency purposes. Phase 11 will add their ClassFreqConfigs entries using autoAssignFreq-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 DefaultRules with 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 — extend TestClassify with ~26 new subtests (one per port/protocol combination listed above)
  • classify/classifier_test.go — update TestAllClassesCount hardcoded value from 14 to 32

Open Questions

  1. 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 != 32 now (matching the post-Phase-10 reality), and update again to != 35 in Phase 11. This is clearer than a dynamic check that could mask missing entries.
  2. 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