Skip to content

Fix: audit logscale missing - #640

Merged
bvdmitri merged 3 commits into
ReactiveBayes:mainfrom
docxology:fix/audit-logscale-missing
Aug 11, 2026
Merged

Fix: audit logscale missing#640
bvdmitri merged 3 commits into
ReactiveBayes:mainfrom
docxology:fix/audit-logscale-missing

Conversation

@docxology

Copy link
Copy Markdown
Contributor

[Verified audit fix] — branch fix/audit-logscale-missing.

Commit: Skip post-rule annotation processors when rule short-circuits to missing

Changes

src/message.jl | 5 ++++-
src/rules/gcv/w.jl | 6 +++---
2 files changed, 7 insertions(+), 4 deletions(-)

Verification

Confirmed against main in the ReactiveBayes audit (Julia 1.12.6); sources verified read-only. See the branch diff.

docxology and others added 2 commits August 10, 2026 13:43
LogScaleAnnotations.post_rule_annotations! errors when :logscale was never
set and inputs are not all PointMass. When MessageMapping short-circuits
result to missing (any inbound message/marginal is missing), no rule ran, so
the post-rule processors must not run on a deferred message. Guard the loop
so a legitimately-deferred message propagates instead of crashing.
The fix in the previous commit shipped without a test. Adds coverage in
`test/annotations/logscale_tests.jl` for a node whose rule input is not a
`PointMass` and whose body never sets `@logscale` -- the exact configuration
in which `LogScaleAnnotations` would `error()`.

Three testsets, structured so the important one cannot pass vacuously:

* A control asserting that a *concrete* non-PointMass message still reaches
  the processor and still errors. Without this, the `missing` test below
  would also pass if the processor simply stopped running altogether.
* The actual regression: a `missing` message comes back as `missing`, and
  carries no `:logscale` -- no log-scale is invented for a message that never
  went through a rule.
* A deferred message multiplied against a concrete one, in both orderings.

That last testset also documents something worth recording: this fix is
*sufficient* on its own. A `missing`-data message does still reach
`compute_product_of_two_messages`, which calls `post_product_annotations!`
unconditionally -- but the generic `post_product_annotations!` already
dispatches on `::Missing` for `left_dist`/`right_dist` (src/annotations.jl)
and copies the other side's annotations through without consulting the
per-processor method. So `LogScaleAnnotations.post_product_annotations!` is
never reached with an absent `:logscale`, and no `KeyError` replaces the
original `error`. The test pins that behaviour so a future change to those
dispatches cannot silently reintroduce the crash one layer down.

Regression gate: with `src/message.jl` reverted to its pre-fix state the two
`missing` testsets error out; the control testset passes in both runs.

Also records the fix in CHANGELOG.md, which the original commit omitted.

Refs ReactiveBayes#623

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@bvdmitri
bvdmitri force-pushed the fix/audit-logscale-missing branch from 8ca7d88 to aebeeb9 Compare August 10, 2026 11:46
@bvdmitri

Copy link
Copy Markdown
Member

Maintainer review — the defect is real and the one-line fix is correct. I have rebased this branch and pushed a regression test plus a CHANGELOG entry (thanks for enabling maintainer edits).

Rebased to a single concern

As submitted, this branch sat on top of #641's GCV commit, so its diff included src/rules/gcv/w.jl — an unrelated change. (#641#640#639 was the full stack; merging #639 would have landed all three at once.) Rebased onto main, so it now contains only the annotation change plus its test.

Verified reachable

Confirmed by code trace: MessageMapping sets result = missing and skips the rule when any inbound message or marginal is missing (src/message.jl:657-663) — the mechanism behind new_observation!(datavar, missing) — then runs post_rule_annotations! regardless. LogScaleAnnotations.post_rule_annotations! only defaults :logscale = 0 when all inputs are PointMass (logscale.jl:47-49); a missing message is not, so it falls to error(...). A legitimately deferred message became a hard crash, defeating the short-circuit.

One correction to the reasoning, in the fix's favour

I initially expected this fix to be insufficient — a missing-data message still reaches compute_product_of_two_messages, which calls post_product_annotations! unconditionally (message.jl:236), and LogScaleAnnotations.post_product_annotations! reads getlogscale on both sides (logscale.jl:68-69). That looked like it would simply trade the error for a KeyError(:logscale) one layer down.

It does not, and the reason is worth recording: the generic post_product_annotations! already dispatches on ::Missing for left_dist/right_dist (src/annotations.jl:168-190) and copies the other side's annotations through without ever consulting the per-processor method. So LogScaleAnnotations is never reached with an absent :logscale. The fix is sufficient as written — but that was load-bearing behaviour nobody had pinned, so the test now covers it explicitly in both orderings (missing-on-left and missing-on-right).

Regression test added

test/annotations/logscale_tests.jl, three testsets deliberately arranged so the important one cannot pass vacuously:

  1. Control: a concrete non-PointMass message still reaches the processor and still errors. Without this, the missing test would also pass if the processor had simply stopped running altogether.
  2. The regression: a missing message comes back as missing and carries no :logscale — no log-scale is invented for a message that never went through a rule.
  3. Downstream: the deferred message multiplied against a concrete one, both orderings, asserting the concrete side's log-scale propagates.
src/message.jl result
reverted to pre-fix main 2 errors (testsets 2 and 3)
with this PR's fix 5 pass

The control testset passes in both runs.

Note on the submitted description

The PR body listed src/rules/gcv/w.jl under "Changes" and said "Confirmed against main in the ReactiveBayes audit ... sources verified read-only", but carried no test. For a crash on a documented code path in the annotations layer, a test is the deliverable — now added.

Whitespace and line-wrapping only, produced by `make format`; no logic change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

2 participants