std::stoul returns an unsigned long, and Token assigned it to an unsigned
int monitor or zone id. On 64-bit, 99999999999 parsed and was truncated
to 1215752191, so a LinkedMonitors value could quietly link a different
monitor than the one typed. It only failed because that id didn't exist.
Move the parsing into Token::parse_monitor_link(), which rejects an id
above UINT_MAX like any other value that isn't a number, so the token is
ignored with the same warning. The function is static so the test can
check the parsed ids without a database.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Token's constructors parse a monitor link with std::stoul, and the one
MonitorLinkExpression::parse() uses is noexcept, so a LinkedMonitors
value with a token that isn't a number ("abc", "1:x", a space before a
parenthesis, a number past unsigned long) ended zmc in std::terminate
every time it started. LinkedMonitors is a free-text field and neither
the web UI nor the API checks it.
Catch the parse error, log the token and leave it without a link, which
scores 0 like a linked monitor that isn't connected. The two
constructors had the same body; it is now one helper.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>