A thread-portable snapshot of the REPORT’s integer status counters
(everything EXCEPT the arena-SymStr-keyed undefined/missing maps,
whose keys are interner ids local to one thread’s arena). Used to forward a
worker thread’s diagnostic tally back to the main thread: REPORT is
#[thread_local], so an Error!/Warn! raised on a spawned post-processing
worker increments only that worker’s counters and is invisible to the
main-thread status_code unless merged here. See
crate::util::logger::capture / crate::util::logger::replay_captured.
Threshold for “same error fired this many times in a row → bail.”
Set well above any legitimate same-error pattern (a paper with
500+ identical errors would already be near-useless output) but
well below the 10000 MAX_ERRORS cap so runaway papers don’t
accumulate huge noise logs. Empirically, the pathological
\tabalign-in-math-mode runaway hits >9000 consecutive same
errors; this catches that at 500 instead. The threshold was
tightened from an initial 2000 after verifying no test in the
1112-test suite exceeds it.
Clear the sticky report.fatal flag. Used by best-effort
helpers (e.g. \maketitle’s deferred frontmatter digest) that
silently swallow a digest error and want to undo the
note_status(Fatal) side-effect so the overall conversion
status reflects the silently-handled fact.
Build a status message matching Perl’s getStatusMessage().
Format: “N warnings; M errors; K fatal error; L undefined macros[\foo, \bar]; P missing
files[x.sty]” Returns “No obvious problems” when no issues detected.
The canonical machine-readable conversion-status line, Status:conversion:N.
One shared probe for the LATEXML_DEBUG_FATAL diagnostics (first-fatal
backtrace, gullet pushback dump, recent-token ring). Lazy-cached so hot
paths pay a single bool test, and a single seam if the env contract grows
(PR #249 review P3-13).
See emit_info. Unlike the Error! macro this cannot escalate by
returning — there is no Err channel in the contexts it serves — so
when the error count or the consecutive-error count crosses its cap it
emits the Fatal:TooManyErrors record and latches the sticky fatal
instead: the run continues (its caller has no unwind path) but the
conversion’s verdict and status code report the fatal honestly.
See emit_info. Latches the sticky fatal and emits the canonical
Fatal:<category>:<object> line — NEVER suppressed (see emit_record).
The CALLER owns any early-exit control flow (e.g. latexml_post’s
Fatal! returns its own PostError after this) — a fatal, unlike an
error, needs no cap bookkeeping.
The single diagnostic vehicle, function form — for contexts that cannot
use the Error!/Warn!/Info! macros because those are return-based
(Error! escalates to Fatal!, which return Err(...)s, so it only
typechecks in Result<_, error::Error> functions).
The ONE emission primitive every diagnostic flows through (DRY pass,
user directive 2026-08-02): count on the emitting thread’s REPORT,
then log with the pre-formatted target, respecting output suppression —
EXCEPT for Error and Fatal records, which are emitted UNCONDITIONALLY
(user decision 2026-08-03): frameworks such as cortex aggregate success
rates from Error:/Fatal: lines, so muting either would hide exactly
the signal they measure. Suppression mutes Debug/Info/Warning only.
The MacroDiagGuard marks the emission so the logger backend does not
count it a second time.
Add a worker thread’s ReportCounts into the current (main) thread’s
REPORT. Only the integer counts + the sticky fatal flag are merged; the
arena-keyed undefined/missing maps are NOT (a worker has its own
thread-local arena, so those keys are not portable).
Record an error signature; returns the new consecutive count.
Call from the Error! macro after note_status. Resets count to 1
on a different signature, increments on a match.
Count a diagnostic record observed by the logger backend, unless it was
emitted by a macro (already counted at raise time) or the report is
mid-borrow (a raw log call from inside a report_mut! scope must not
panic the conversion over a tally increment — matching the logger’s own
try_borrow discipline for LOG_BUFFER).
Record a fatal into the resource-fatal latch — only Timeout-target fatals
with payload-free categories are kept (the latch exists for resource
fatals; payload-carrying ErrorCategory variants are not cloneable and
are never resource-class).
Clear the arena-SymStr-keyed report maps (undefined, missing). MUST be
called whenever the arena is reset (see crate::reset_thread_engine): their
keys are arena interner ids, so after arena::reset() a stale key resolves to
whatever string now occupies that id — e.g. a \special_relax family-token
name — producing phantom “undefined macro” reports across conversions.
Overwrite the current thread’s REPORT counters with a prior snapshot.
The isolation primitive for RECURSIVE/auxiliary digestions whose
diagnostics must not count against the document (Perl analog: the
recursive MakeBibliography session keeps its tally out of the outer
document). Pair with set_suppress_log_output so neither the lines
nor the counts leak: snapshot -> suppress -> digest -> restore.
Take (and clear) the latched resource fatal, if any. Returns the
structured Error so a boundary that received only a flattened string can
propagate the real thing.