Measured on portaliq origin/development @ 79bbf68, full-tree, hydra-gates @ 756fe89.
[gate-3] stub-scan: FAIL — 1 finding(s)
lib/BackgroundJob/NotificationDispatchJob.php: run() body has no non-logger statements (stub)
That file is 530 lines with 11 private methods implementing a complete notification pipeline (account lookup → render → send → append-only log → fallback flag). It is not a stub by any reading.
The arithmetic
scripts/run-hydra-gates.sh:840-842:
_body=$(awk "/function run\(/,/^ }/" "${job}" \
| grep -vE "^\s*(//|\*|\s*\{|\s*\}|\s*$)" \
| grep -vE "function run|logger->(info|warning|debug|error|notice)|try\s*\{|\}\s*catch|return;?$")
_lc=$(echo "${_body}" | grep -cE "\S")
[ "${_lc}" -lt 2 ] && echo "... run() body has no non-logger statements (stub)"
The filters strip try {, } catch, and every logger call. For the canonical fail-safe wrapper —
protected function run($argument): void
{
try {
$this->doRun(argument: $argument);
} catch (Throwable $e) {
$this->logger->error("...", ["reason" => $e->getMessage()]);
}
}
— exactly one line survives, and the threshold is < 2.
Fixtures
Running the gate's own pipeline over three run() bodies:
| fixture |
surviving lines |
verdict |
A — genuine stub (logger->info() only) |
0 |
FLAGGED ✅ correct |
B — real fail-safe delegation to doRun() |
1 |
FLAGGED ❌ false positive |
C — B plus one inert $unused = 1; |
2 |
passes |
C is the problem. The gate is closed by adding a dead line and cannot be closed by writing correct code. The only "fixes" available to a builder are:
- add a no-op statement (C) — satisfies the gate, changes nothing;
- inline the whole pipeline back into
run() — deletes the single try/catch that keeps an exception from escaping to the NC cron runner.
Both make the code worse. This is the same family as #184/#191/#220/#224: a text-shaped heuristic standing in for a semantic property, except here the remediation is actively harmful rather than merely noisy.
Why the pattern exists
Wrapping one delegate in one try/catch is how a QueuedJob guarantees it never disrupts the cron runner — the job's docblock says exactly that. It is a good shape, and the gate penalises it specifically because the try/catch scaffolding and the logger call are both filtered out, leaving the single meaningful statement below threshold.
Suggested direction
A run() whose only statement is a call to a method defined in the same class is delegation, not a stub — resolve the callee and judge that body, or simply treat "one non-logger statement that is a method call" as implemented. The < 2 line count cannot distinguish "empty" from "one well-factored call", and padding it is a worse outcome than the finding.
No change was made to portaliq for this finding — it is being reported rather than "fixed", because every available fix is a regression.
Measured on portaliq
origin/development@ 79bbf68, full-tree, hydra-gates @756fe89.That file is 530 lines with 11 private methods implementing a complete notification pipeline (account lookup → render → send → append-only log → fallback flag). It is not a stub by any reading.
The arithmetic
scripts/run-hydra-gates.sh:840-842:The filters strip
try {,} catch, and every logger call. For the canonical fail-safe wrapper —— exactly one line survives, and the threshold is
< 2.Fixtures
Running the gate's own pipeline over three
run()bodies:logger->info()only)doRun()$unused = 1;C is the problem. The gate is closed by adding a dead line and cannot be closed by writing correct code. The only "fixes" available to a builder are:
run()— deletes the single try/catch that keeps an exception from escaping to the NC cron runner.Both make the code worse. This is the same family as #184/#191/#220/#224: a text-shaped heuristic standing in for a semantic property, except here the remediation is actively harmful rather than merely noisy.
Why the pattern exists
Wrapping one delegate in one try/catch is how a
QueuedJobguarantees it never disrupts the cron runner — the job's docblock says exactly that. It is a good shape, and the gate penalises it specifically because the try/catch scaffolding and the logger call are both filtered out, leaving the single meaningful statement below threshold.Suggested direction
A
run()whose only statement is a call to a method defined in the same class is delegation, not a stub — resolve the callee and judge that body, or simply treat "one non-logger statement that is a method call" as implemented. The< 2line count cannot distinguish "empty" from "one well-factored call", and padding it is a worse outcome than the finding.No change was made to portaliq for this finding — it is being reported rather than "fixed", because every available fix is a regression.