Production logs showed socket.create_connection to smtp-relay.brevo.com:587
timing out. HuggingFace Spaces blocks outbound 25/465/587 to prevent spam abuse,
so smtplib can never connect there regardless of credentials.
Mail now goes over HTTPS via the Brevo v3 API when BREVO_API_KEY is set, falling
back to SMTP for self-hosted deployments where the ports are open, and to logging
when neither is configured. Brevo's rejection body is logged, so an unverified
sender or bad key is diagnosable instead of silent.
co-writer: enforce the word limit and flag directives typed into the draft
BUG-037: stating the limit in the prompt did not hold it, so output is now
trimmed to the requested word count, preferring a sentence boundary.
BUG-045: the constraint was typed into the draft, where it is ignored by design
(that is what stops the BUG-043 injection). The response now advises the user to
use the Instructions field, which does honour it.
co-writer: strip JSON punctuation from truncated suggestion output
A suggestion appeared in production beginning with '["' when the model's array
was cut off by the token limit; the line fallback stripped quotes but not
brackets.
co-writer: reject suggestions citing figures not in the draft
Prompt rules did not stop it (QA re-test of BUG-036/042: 'Startup' produced
specific growth percentages and revenue figures). Suggestions are now checked for
statistic-like tokens absent from the draft and dropped; if every option is
fabricated, one retry states the ban explicitly.
grammar: name the explanation language explicitly
Live output returned Spanish explanations for English text. The prompt said the
message should be 'in the same language as the text', which the model applied
unreliably; the language is now named (English, Hindi, ...).
grammar: supplement LanguageTool with an LLM pass
The public LanguageTool API returns zero matches for QA's three cases at both
default and picky level (verified directly against api.languagetool.org): tense
in 'I have gone to the market yesterday.', the missing question mark in 'Hello
how are you', and the 'Although ... but' conjunction. Those rules require the
premium or self-hosted instance.
LanguageTool findings are now supplemented with an LLM pass for supported
languages, LanguageTool winning on overlapping spans. The pass is best-effort:
if it fails, LanguageTool's result is returned unchanged.
Refuse non-prose input in verdict and generation tools
QA re-tested the previous batch and rejected warn-but-still-process for 11 bugs
(021, 023-027, 035, 040, 041). The tools kept returning a confident answer — a
tone for "123456789", 100% plagiarism for "@#$%", a paragraph invented from
{"name":"John"} — with the caution alongside it.
The line QA drew: warning is fine where the output is a transformation the user
can judge (paraphrase, summarize — both marked Solved), but not where the tool
emits a verdict or invented content. Those now return 422 with an actionable
message instead of a result:
- tone: refuses digits/emoji-only and JSON/code
- plagiarism: refuses when either field has no words
- grammar: refuses code and JSON, whose "corrections" corrupt the input
- co-writer: refuses digits/emoji/symbols-only and JSON
Paraphrase and summarize keep the advisory unchanged.
fix: translator auto-detect returned an empty translation
Regression from the BUG-013 change: translation and language detection were
requested as a single JSON object, and the model frequently ignored that
contract. QA testing measured roughly 3 in 4 auto-detect translations coming
back empty, while explicit-source translations were unaffected.
Detection is now a separate best-effort call that cannot affect translation, and
an empty model response raises instead of surfacing a blank result.
Also tightens the password rule per QA follow-up on BUG-007: spaces and emoji
were accepted whenever a letter and a digit were also present.