feat: column roles — skip identifier/sequence columns in point detection (0.7.0)#56
Conversation
…ion (0.7.0)
Every scanned column is classified into a Role {measurement, identifier,
categorical, sequence, constant} and the full map ships in the envelope's new
`roles` array. point.modz skips identifier + sequence columns (a big PID or a
counter's endpoint isn't an anomaly) — attacking noise at the DETECTION layer.
On real 20k journald: point findings ~12,500 → ~240 (skips _PID/_UID/_GID/
JOB_ID/TID/SYSLOG_PID/_AUDIT_SESSION/INVOCATION_ID/MESSAGE_ID/N_RESTARTS/
TIMESTAMP_*/__SEQNUM). On the MSHA parquet, DAYS_LOST stays `measurement` →
all 32,893 genuine findings preserved. --no-column-roles restores old behavior.
Design (honoring "never guess"): identifiers recognized by NAME (the only
reliable signal — a PID column is statistically identical to a discrete
measurement), never by cardinality (a near-constant-with-outliers column has low
cardinality yet is exactly what point should catch). Heuristic but never SILENT:
roles emitted in the envelope (auditable) + one flag from off. Constant columns
still flow through scan_column (self-no-op), so they're not spuriously absent.
ax_core::roles (Role/ColumnRole/Column::role); DetectConfig.column_roles in the
config_version fingerprint (cr=); roles in envelope + schema; --no-column-roles
CLI flag. Additive; PROTOCOL unchanged.
Gates: proptest + cargo-mutants 0-missed on roles.rs (35 caught), envelope.rs,
point.rs+config.rs (32 caught), main.rs+schema.rs (68 caught). Includes direct
tests for the name tokenizer, is_strictly_monotonic boundaries, and role/skip.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis PR introduces deterministic column role classification to anomalyx, enabling the detection system to skip columns where statistical analysis is not applicable (e.g., identifiers and monotonic sequences). The feature includes role reporting in the output envelope, a ChangesColumn Role Classification and Detection
Sequence DiagramsequenceDiagram
participant User as User
participant CLI as anomalyx CLI
participant Parser as parse_scan_args()
participant ConfigBuilder as config_for()
participant Detector as Point Detector
participant Column as Column
participant Builder as EnvelopeBuilder
participant Output as Envelope JSON
User->>CLI: scan --no-column-roles
CLI->>Parser: parse CLI args
Parser->>Parser: set no_column_roles = true
Parser-->>ConfigBuilder: ScanArgs
ConfigBuilder->>ConfigBuilder: column_roles = !no_column_roles<br/>(= false)
ConfigBuilder-->>Detector: DetectConfig
Detector->>Column: iterate numeric columns
alt column_roles = false
Detector->>Detector: assess all numeric columns
else column_roles = true (default)
Detector->>Column: get role
alt role is Identifier or Sequence
Detector->>Detector: skip column
else
Detector->>Detector: assess column
end
end
Note over CLI,Output: Envelope construction
CLI->>Column: extract role() for each column
Column-->>Builder: ColumnRole { column, role }
CLI->>Builder: roles(vec![...])
Builder->>Output: roles field in JSON
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/ax-core/src/envelope.rs`:
- Around line 115-118: The wire-format was changed by adding the `roles:
Vec<ColumnRole>` field to the `tq1` envelope but `envelope::PROTOCOL` was not
incremented; update `envelope::PROTOCOL` to a new protocol version to signal the
breaking change, then update the `anomalyx schema` to reflect the new `tq1`
shape and run/update the contract tests that validate envelope compatibility
(including any tests referencing `tq1` layout or dense row layout) so consumers
can detect the new payload format.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: f59d1b16-e0e8-4a46-bf8a-13d354243389
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (10)
CHANGELOG.mdCargo.tomlcrates/anomalyx/src/main.rscrates/anomalyx/src/schema.rscrates/ax-core/src/envelope.rscrates/ax-core/src/lib.rscrates/ax-core/src/roles.rscrates/ax-detect/src/config.rscrates/ax-detect/src/point.rsdocs/src/modes.md
| /// The classified [`Role`](crate::Role) of each scanned column. Always | ||
| /// reported (transparent): detectors may skip columns by role, and an agent | ||
| /// can see and override that. Empty only for a column-less corpus. | ||
| pub roles: Vec<ColumnRole>, |
There was a problem hiding this comment.
Bump envelope::PROTOCOL for this wire-format change.
Adding roles changes the tq1 envelope shape, but PROTOCOL still advertises the old contract. That leaves old consumers unable to distinguish the new payload from the previous schema-compatible version. As per coding guidelines, "The tq1 envelope is an API. Changing a field, the dense row layout, an exit code, or a handle form is a breaking change: bump envelope::PROTOCOL, update anomalyx schema, and update the contract tests."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/ax-core/src/envelope.rs` around lines 115 - 118, The wire-format was
changed by adding the `roles: Vec<ColumnRole>` field to the `tq1` envelope but
`envelope::PROTOCOL` was not incremented; update `envelope::PROTOCOL` to a new
protocol version to signal the breaking change, then update the `anomalyx
schema` to reflect the new `tq1` shape and run/update the contract tests that
validate envelope compatibility (including any tests referencing `tq1` layout or
dense row layout) so consumers can detect the new payload format.
What
Every scanned column is classified into a role —
measurement/identifier/categorical/sequence/constant— and the full map ships in the envelope's newrolesarray. The point detector skipsidentifierandsequencecolumns (a big process-id or a counter's endpoint is not an anomaly), attacking noise at the detection layer.--no-column-rolesdisables the skipping; roles are still reported.Impact (dogfooded)
_PID/_UID/_GID/JOB_ID/TID/SYSLOG_PID/_AUDIT_SESSION/INVOCATION_ID/MESSAGE_ID/N_RESTARTS/TIMESTAMP_*/__SEQNUMDAYS_LOSTstaysmeasurement— genuine signal preservedA 98% cut of the journald noise; zero impact on the real measurement.
Design — honoring "never guess"
*_id,uid,gid,pid,tid,session,uuid, …) — the only reliable signal, because a process-id column is statistically indistinguishable from a discrete measurement (_PIDis mid-cardinality with repeats, not near-unique, not monotonic).--no-column-rolesturns skipping off. Setting is inconfig_version(cr=). Constant columns still flow throughscan_column(self-no-op), so they aren't spuriously marked absent.Contract
Additive: new
ax_core::rolesmodule (Role/ColumnRole/Column::role),rolesarray in the envelope +schema.PROTOCOLunchanged.Gate
proptest+cargo-mutants0 missed on all changed files:roles.rs(35 caught),envelope.rs,point.rs+config.rs(32 caught),main.rs+schema.rs(68 caught). Direct tests for the name tokenizer,is_strictly_monotonicboundaries, role classification, and role-based skipping incl.--no-column-roles.🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes - v0.7.0
New Features
--no-column-rolesflag: Disable role-based detector filtering while preserving role reporting.Documentation