WeightSerializer
Decodes a Weight through Weight.of, so a value that arrived over the wire is indistinguishable from one the factory built (#1647).
The plugin-generated serializer would deserialize straight into Weight's primary constructor, skipping the only invariant-enforcing path. An unreduced 2/4 would then be structurally unequal to 1/2, and a negative denominator would invert Weight.compareTo — whose cross-multiplication assumes a positive denominator — so a corrupt or hostile peer could flip sibling ordering cluster-wide by encoding 1/2 as -1/-2.
Repair where a canonical form exists; refuse only where none does. Both decode boundaries that carry a Weight — us.tractat.kuilt.quilter.Quilter's delta dispatch and the heddle control plane's applyEntry — already swallow a decode failure and drop the entire frame. Rejecting a merely denormalized encoding would therefore discard every legitimate record travelling with it, and anti-entropy would re-send the same frame forever: a silent, permanent convergence wedge between two peers over a difference with no semantic content. Sign-normalization and reduction have a unique correct answer, so they are applied.
Values that name no weight at all — a zero or negative share, a zero denominator — are a different case: there is nothing to repair to, and substituting a default would fabricate a fairness claim (a conservation-relevant lie) out of hostile input. Those throw SerializationException at the decode boundary, before any lattice state is touched; the frame is dropped and the merge loop survives.