Skip to content

Fix #746: Correct inverted type 2 normal restoral conditions - #796

Merged
TheWitness merged 2 commits into
Cacti:developfrom
bmfmancini:fix/issue-746-inverted-type2-restoral-conditions
Sep 20, 2026
Merged

TheWitness merged 2 commits into
Cacti:developfrom
bmfmancini:fix/issue-746-inverted-type2-restoral-conditions

Conversation

@bmfmancini

Copy link
Copy Markdown
Member

Summary

Fixes #746

In thold_functions.php, the type 2 (time-based) normal restoral logic in thold_check_threshold() (lines ~3443 and ~3512) used < instead of >= for the fail count comparisons:

// Warning restoral (inverted):
if ($alertstat != 0 && $warning_failures < $warning_trigger && $thold_data['restored_alert'] != 'on') {

// Alert restoral (inverted):
} elseif ($alertstat != 0 && $failures < $trigger && $thold_data['restored_alert'] != 'on') {

Impact

The < vs >= inversion caused:

  • Restorals to fire for thresholds that were never triggered — when the fail count was below the trigger (no alert was ever sent), a restoral notification was incorrectly generated.
  • Restorals to be silently suppressed for thresholds that were triggered — when the fail count reached or exceeded the trigger (an alert was sent), the restoral was incorrectly blocked.

Fix

Changed < to >= for both $warning_failures and $failures comparisons in the type 2 restoral block:

// Warning restoral (fixed):
if ($alertstat != 0 && $warning_failures >= $warning_trigger && $thold_data['restored_alert'] != 'on') {

// Alert restoral (fixed):
} elseif ($alertstat != 0 && $failures >= $trigger && $thold_data['restored_alert'] != 'on') {

This matches the correct pattern used in the type 0 restoral logic (line ~2685), which checks:

if ($thold_data['thold_warning_fail_count'] >= $warning_trigger && $thold_data['restored_alert'] != 'on' && !$maint_dev) {

Testing

  • Verified PHP syntax with php -l thold_functions.php — no syntax errors.
  • Confirmed the corrected conditions match the semantics of the type 0 restoral path.

The type 2 (time-based) restoral logic in thold_check_threshold() used
'<' instead of '>=' for the fail count comparisons:

- Warning restoral: $warning_failures < $warning_trigger
- Alert restoral: $failures < $trigger

This inversion caused restorals to fire for thresholds that were never
triggered (count below trigger), while silently suppressing restorals
for thresholds that were actually triggered (count at or above trigger).

Changed '<' to '>=' for both comparisons, matching the correct pattern
used in the type 0 restoral logic which checks:
  $thold_data['thold_warning_fail_count'] >= $warning_trigger
Copilot AI lite review requested due to automatic review settings August 17, 2026 02:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@somethingwithproof somethingwithproof left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inverted restoral comparisons appear to be the intended fix, but this branch includes the three unrelated #789 threshold_value edits and has no behavioral coverage. Please isolate the two restoral-condition changes, add Cacti-Composer/Pest cases for never-triggered, warning-triggered, and alert-triggered type-2 thresholds returning to normal, and add the CHANGELOG entry. The integration matrix is red.

@somethingwithproof

Copy link
Copy Markdown
Member

All four checks fail before this plugin loads because the branch’s older workflow tests unpinned Cacti develop, which crashes in core with an undefined __() call. Maintainer edits are disabled, so I cannot apply the pinned-Cacti and bounded-retry workflow fix. Please enable maintainer edits or update/rebase the branch.

@TheWitness

Copy link
Copy Markdown
Member

The CI failures here are not caused by this PR. All four integration test legs fail identically with:

PHP Fatal error:  Uncaught Error: Call to undefined function __() in .../cacti/lib/functions.php:7973
Stack trace:
#0 .../cacti/lib/functions.php(8040): format_cacti_version_text()
#1 .../cacti/include/global.php(701): get_cacti_version_text()
#2 .../cacti/include/cli_check.php(54): include('...')
#3 .../cacti/cli/install_cacti.php(26): require('...')

This happens during cli/install_cacti.php on a fresh database, before the plugin's own code ever runs: include/global.php called get_cacti_version_text() (which calls the __() translation function) before include/global_languages.php (which defines __()) was loaded. That was a bug in the pinned upstream Cacti/cacti release/1.2.31 branch at the time this workflow last ran (2026-08-17).

That upstream branch has since moved forward and no longer contains the problematic early call. Since these workflow runs are more than 30 days old, GitHub won't allow a direct rerun, so I'm closing and reopening this PR to trigger a fresh CI run against the current release/1.2.31 HEAD.

@TheWitness TheWitness closed this Sep 20, 2026
@TheWitness TheWitness reopened this Sep 20, 2026
@TheWitness
TheWitness dismissed somethingwithproof’s stale review September 20, 2026 01:13

I'm going to be removing the integration tests. They will be restored in the near future across all plugins.

@TheWitness
TheWitness merged commit f4be0d8 into Cacti:develop Sep 20, 2026
4 of 9 checks passed
TheWitness added a commit that referenced this pull request Sep 20, 2026
Merging develop brought in #796's fix for thold_check_threshold()'s type-2
(time-based) restoral logic (`<` -> `>=` for the warning/alert fail-count
comparisons). This test harness always stubs the log-derived fail count
to 0, and the two restoral tests here relied on the default trigger of 1,
so under the corrected operator (0 >= 1 is false) neither restoral branch
fired anymore and both tests broke.

Override time_fail_trigger/time_warning_fail_trigger to 0 in both tests,
representing a threshold with no minimum streak requirement, so the
harness's stubbed zero count still satisfies the corrected `>=` check and
the tests exercise real restoral behavior again instead of the inverted
bug.
TheWitness added a commit that referenced this pull request Sep 21, 2026
…vr4v-qvqm-gm9j) (#807)

* security: replace eval() in RPN math binary ops with safe dispatch (S1523)

Replace the eval()-based binary arithmetic evaluation at line 378
with a new thold_rpn_math_binary() helper function that uses a
switch statement for +, -, *, /, %, ^ operators.

The operator was already whitelisted via in_array(, , true)
and operands validated with is_numeric(), but removing eval() eliminates
the code injection risk entirely (CWE-95).

Addresses SonarQube finding: php:S1523 at thold_functions.php:378

* ci: retry transient dependency downloads

* test(rpn): cover safe binary dispatch

* fix: restore SQL binding, trigger-command quoting, and RPN safety fixes

Same set of pre-existing gaps as develop (covered by tests but never
implemented in thold_functions.php), plus one bug specific to this PR:

- get_allowed_thresholds()/get_allowed_threshold_logs(): bind graph_id and
  caller-supplied params instead of interpolating them into the SQL text,
  and use the *_prepared fetchers.
- thold_replace_threshold_tags(): add a \ flag that quotes
  device/threshold free-text values with cacti_escapeshellarg() when the
  substituted text is a trigger command.
- thold_command_execution(): pass shell=true for the three trigger
  commands, and fix a topic-string typo ('thold' vs 'thold_cmd') that
  silently suppressed exit-status logging for inline command runs.
- thold_expression_math_rpn(): remove a stray break that exited the
  operator switch before pushing the 0/0 result, flag modulo-by-zero
  instead of letting PHP throw DivisionByZeroError, reject non-numeric
  unary operands before eval(), and flag SQRT/LOG results that are NAN or
  INF instead of pushing them onto the stack (unary math here is still
  eval()-based; unrelated to this PR's binary-math dispatcher).
- thold_rpn_math_binary(): this PR's new dispatcher mapped '^' to pow(),
  silently changing it from the pre-existing bitwise XOR behaviour
  (eval('... ^ ...') always computed XOR). Restored XOR to avoid breaking
  existing user thresholds, and corrected this PR's own
  TholdRpnBinaryMathTest 'power' dataset, which had asserted the
  incorrect pow() behaviour.

* fix(tests): stop includes/arrays.php loading from silently no-op'ing

thold_functions.php includes includes/arrays.php with a plain include()
(not include_once()) from several of its own functions (e.g. thold_log()),
keyed off \['base_path']. Once any test in the shared Pest process
exercised one of those call sites, PHP's include-once registry considered
the file already included: a later thold_test_load() (require_once) on the
same resolved path silently no-op'd and never (re)published \
to \, depending on which order the test files happened to run in.

This intermittently broke every test that reads \
(TholdReplaceThresholdTagsTest's THOLDTYPE substitution) as well as tests
several call frames downstream of thold_log()/thold_check_threshold()
(NotificationEmailDeduplicationTest, ThresholdTimeBasedCharacterizationTest),
depending on file execution order.

Add thold_test_load_always()/loadPluginSourceAlways(), which use a plain
require() instead of require_once(), for plugin files that only assign
file-scope variables (no function/class declarations) and are therefore
safe to load more than once. Switch the four test classes that load
includes/arrays.php to the new helper.

* debug: temporary CI diagnostics for notification/restoral failures

* Revert "debug: temporary CI diagnostics for notification/restoral failures"

This reverts commit 21fbd16.

* debug: temporary CI diagnostics for notification/restoral failures

* test: fix restoral tests for the corrected type-2 restoral operator

Merging develop brought in #796's fix for thold_check_threshold()'s type-2
(time-based) restoral logic (`<` -> `>=` for the warning/alert fail-count
comparisons). This test harness always stubs the log-derived fail count
to 0, and the two restoral tests here relied on the default trigger of 1,
so under the corrected operator (0 >= 1 is false) neither restoral branch
fired anymore and both tests broke.

Override time_fail_trigger/time_warning_fail_trigger to 0 in both tests,
representing a threshold with no minimum streak requirement, so the
harness's stubbed zero count still satisfies the corrected `>=` check and
the tests exercise real restoral behavior again instead of the inverted
bug.

---------

Co-authored-by: Thomas Vincent <thomasvincent@gmail.com>
Co-authored-by: TheWitness <thewitness@cacti.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: Type 2 normal restoral conditions inverted — fires for un-triggered thresholds

5 participants