# Port parity notes — Rust to Python

`scorer.py` transcribes `axium/src/agent/classifier.rs:769-947`. Written 6 August 2026.

## Constructs checked for equivalence

| Rust | Python | Equivalent? |
|---|---|---|
| `trimmed.split_whitespace()` | `trimmed.split()` | Yes — both split on arbitrary whitespace runs and drop empties |
| `(x as f64 / 2.0).min(1.0)` | `min(x / 2.0, 1.0)` | Yes |
| `(x as f64).min(1.0)` | `min(x, 1.0)` | Yes |
| `lower.trim_end_matches(['!', '.', '?', ' ']).trim()` | `.rstrip("!.? ").strip()` | Yes — both strip any of that character set repeatedly from the end |
| `boundaries.iter().map(...).fold(f64::MAX, f64::min)` | `min(...)` | Yes |
| `1.0 / (1.0 + (-10.0 * min_dist).exp())` | `1.0 / (1.0 + math.exp(-10.0 * min_dist))` | Yes |
| `GREETINGS.contains(&lower)` | `lower in GREETINGS` | Yes — exact whole-string match, not substring |
| `IDENTITY_STARTS.iter().any(\|p\| lower.starts_with(p))` | `any(lower.startswith(p) ...)` | Yes |

## Case sensitivity — preserved deliberately

Dimension 2 (code presence) matches against `trimmed`, the **original case** string:

```rust
let c_count = CODE.iter().filter(|k| trimmed.contains(*k)).count();
```

Every other dimension matches against `lower`. The port reproduces this exactly. It matters: `"Class "` with a capital C does not count as code presence, but `"class "` does.

## A live bug found by porting

**`classifier.rs:848` puts `"O("` in the CONSTRAINTS keyword list. `classifier.rs:851` matches that list against `lower`, the lowercased string. A capital `O` can never appear in a lowercased haystack, so `"O("` can never match.**

Verified against three realistic prompts:

| prompt | lowercased | `"O("` found? |
|---|---|---|
| `keep it under O(n log n)` | `keep it under o(n log n)` | no |
| `optimize the O(n^2) loop` | `optimize the o(n^2) loop` | no |
| `what is the complexity O(1)` | `what is the complexity o(1)` | no |

It is a dead keyword. The complexity-notation signal the dimension was written to capture has never fired in production. The other seven CONSTRAINTS keywords are lowercase and work normally, and the dimension carries weight 0.04 with the count capped at 1, so the practical impact is small — but the intent is defeated.

The port keeps the bug rather than fixing it, so the measurement reflects the shipped harness. A fixed version would lowercase the needle or match against `trimmed`.

## Not ported

`quick_classify`'s `info!` logging calls. They have no effect on the returned classification.

## Confidence in the port

Behavioural spot-checks match the expected Rust semantics on greetings (stage 1), the reasoning hard override (score 0.5, confidence 0.90) and the sub-gate arithmetic case. No discrepancy found. The port has not been differentially tested against a compiled Rust build — Axium was not compiled for this exercise, and that is a limitation of the measurement.
