The problem arises because you are stripping away the heading element when you use #text(it.body, ...). This makes the show rule return a par element, which matches with the other show rule. Simply keeping the entire heading, i.e. #text(it, ...) fixes the issue.
Also, you should prefer using show-set rules whenever possible, as this allows for more customisation later (see: Is it possible to call set multiple times in a single show rule? - #8 by ensko)
In your example, this would be
#show heading.where(level: 4): set text(size: 1.3em, fill: red, weight: "light")
#show heading.where(level: 4): set block(below: 0.7em)
#show heading.where(level: 4): it => {
show divider: set line(length: 100%, stroke: 1pt + red)
show divider: box
it + divider() + color-state.update(true)
}
So that later in the document, if you wanted to e.g. change the line to asterix’s you could just write
#show heading.where(level: 4): it => {
show divider: set align(center)
show divider: block[\* \* \*]
show block: set text(fill: red)
it
}
Instead of re-writing the whole show rule.