AI spam classification: the model's opinion as one bounded spam signal, and the llm_prompt Sieve function (AI-1 to AI-28)

The classifier sends only the subject and text, between unforgeable markers
after the operator's prompt, to an OpenAI-compatible endpoint the operator
configured; nothing is preset. Its answer maps to an LLM_ tag whose score is
clamped (+5.0, -1.0 by default) and can never discard or reject on its own;
X-Spam-LLM is sanitized, encoded and folded, and a planted one is removed.
Failures, timeouts past the ceiling, a full slot or a paused model leave
mail flowing untagged. llm_prompt answers trusted scripts, and accounts
holding interactAi within an hourly limit. Redirects aren't followed and no
content or secret is logged. The limits live in inbuxa:AiLimits.
Acceptance tests 1 and 3 to 21; test 2 as the re-enabled shared llm case,
whose setup no longer waits on a rules file from a developer's own path;
test 22 written as the ignored ai_compat.
This commit is contained in:
2026-09-19 00:41:00 -07:00
parent cba48cf03b
commit 9490fc4677
39 changed files with 2945 additions and 28 deletions
+6
View File
@@ -794,6 +794,12 @@ impl<T: SessionStream> Session<T> {
// Update size
let original_message = raw_message.as_slice();
let raw_message = edited_message.as_deref().unwrap_or(raw_message.as_slice());
// inbuxa: AI-15: a sender can't plant its own X-Spam-LLM
let stripped_message = spam_result
.is_some()
.then(|| inbuxa_features::ai::answer::strip_header(raw_message))
.flatten();
let raw_message = stripped_message.as_deref().unwrap_or(raw_message);
message.message.size = (raw_message.len() + headers.len()) as u64;
// Verify queue quota