testsAndMisc/docs/superpowers/contracts/services-status-downgrade-fix-2026-08-21.json
Krzysztof kuhy Rudnicki 08f3c764b2
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
check_and_enable_services: a warning must never mask an error
Eight checks assigned status="warning" unconditionally, so a check running
later in the same function overwrote the status="error" an earlier one had
set. The five other sites in this family already guard the assignment with
`if [[ $status != "error" ]]`, so this was an inconsistency, not a design.

Only half of it was load-bearing, and the two halves fail differently:

  services_units.sh (4 sites) reports through report_and_fix, which repairs
  ONLY on "error". A downgrade there meant no repair ran at all. The is-active
  check is the LAST status assignment in each of the three functions, so it
  could mask any error above it -- in check_periodic_systems, three separate
  is-enabled checks. A timer that was both disabled and inactive, which is the
  ordinary shape of a broken timer, was reported and then left broken.

  services_hosts.sh (4 sites) gates its own repair on `status != "ok"`, so the
  repair still ran. There the bug printed a real error as a yellow [!] warning
  and understated the summary row.

Demonstrated rather than argued, against a simulated broken machine (timer
disabled AND inactive, unit files present):

  without the guards:  status=warning, repair ran 0 times
  with the guards:     status=error,   repair ran 1 time

The seven assertions that previously pinned the broken behaviour now assert
the correct behaviour; they were rewritten first and failed, then the guards
made them pass. Two further cases cover the masking shape directly: a broken
guard-lib instance must still be repaired when a later check warns.

`--status --dry-run` output is still byte-identical to rev 6691cb26, because
this machine has no disabled timer and no missing pacman hook -- the buggy
path is never taken here, which is precisely why it went unnoticed. The A/B
above is the evidence the live diff cannot provide.

222 assertions across 10 files. services_units.sh holds 100% coverage;
services_hosts.sh rises from 86.05% to 93.02%. test_services_hosts.sh needed
splitting twice to stay under the 250-line cap (hosts_fix, resolved), and
stage_hosts_ok moved into the harness so both new files can use it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F3PzGc75gBcdxP3PkHXGcV
2026-08-21 14:16:01 +02:00

20 lines
1.8 KiB
JSON

{
"title": "Fix the status-downgrade bug so a warning can never mask an error",
"objective": "Stop a later check's status=\"warning\" from overwriting an earlier check's status=\"error\" in check_and_enable_services, which made genuinely broken services report as warnings and then never get repaired. Found while writing the split's tests and deferred out of 9085a4e5 because it changes live enforcement; the user asked for it to be fixed.",
"acceptance_criteria": [
"No bare `status=\"warning\"` assignment remains in any check lib; every one is guarded by `if [[ $status != \"error\" ]]`, matching the idiom already used in services_apps.sh and services_browser.sh.",
"A timer that is BOTH disabled and inactive records \"error\" and triggers its setup script, demonstrated by an A/B run against a simulated broken machine rather than only by assertion.",
"The regression is covered by tests that FAILED before the fix and pass after it.",
"lib/tests/run_all.sh passes with no failures.",
"services_units.sh keeps 100% line coverage.",
"Every file including the test files stays at or under 250 lines.",
"shellcheck reports zero findings and no suppression is added."
],
"out_of_scope": [
"The 5 sites that were already correctly guarded.",
"Changing which statuses report_and_fix acts on: the fix makes the status correct rather than widening the repair gate.",
"The kcov attribution artifact on services_hosts.sh, diagnosed in the previous commit's evidence."
],
"verifier": "lib/tests/run_all.sh (222 assertions), an A/B of check_midnight_shutdown against a disabled+inactive timer with and without the guards, bash meta/scripts/shell_coverage.sh lib/tests/run_all.sh services_units.sh 100, grep for unguarded sites, shellcheck, and check_file_length.sh --all."
}