# 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 (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. --- ## 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 | --- ## 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) ```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: ```go // 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) ```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 ```go // 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