Found during #289 (G2-T3), r6 application, 2026-08-22 — while hand-writing PartialEq for DecoratedCospan. The PartialEq case was fixed there; Clone and Debug carry the identical defect and were left alone as out of scope.
The defect
catgraph-applied/src/decorated_cospan.rs:137:
#[derive(Clone, Debug)]
pub struct DecoratedCospan<Lambda, D>
where
Lambda: Eq + Copy + Debug,
D: Decoration,
{
pub cospan: Cospan<Lambda>,
pub decoration: D::Apex, // <-- D::Apex, not D
}
derive bounds the type parameters, not the field types. So it generates impl<Lambda, D> Clone for DecoratedCospan<Lambda, D> where Lambda: Clone, D: Clone, requiring D: Clone — but D is a marker that is never stored. The field is D::Apex.
No Decoration marker in the workspace implements Clone:
| marker |
derives |
visibility |
PetriDecoration (petri_net.rs:613) |
Debug |
pub |
Trivial (decorated_cospan.rs:371) |
Debug |
test-module-private |
Counter (decorated_cospan.rs:457) |
Debug |
test-module-private |
Circuit (tests/, examples/) |
none |
local |
LocalTrivial (tests/decorated_cospan.rs:98) |
none |
local |
So DecoratedCospan is uncloneable for every decoration that exists — including PetriDecoration, the only one a downstream consumer can name. Measured on audit/G2-T3: cloning a DecoratedCospan<char, Trivial> gives E0599, "the method clone exists for struct DecoratedCospan<char, Trivial>, but its trait bounds were not satisfied … Trivial: Clone is not satisfied".
Debug has the same shape but bites less: PetriDecoration, Trivial and Counter do derive Debug, so it only fails for Circuit / LocalTrivial.
Why this is the same bug #289 already fixed once
#289's r6 pass added PartialEq to this type and hit exactly this. A #[derive(PartialEq)] there compiles but applies to no decoration in the workspace — measured, three E0369s. The fix that shipped was a hand-written impl bounded on D: Decoration and D::Apex: PartialEq rather than D: PartialEq. Clone and Debug need the same treatment and did not get it.
Work
Replace #[derive(Clone, Debug)] with hand-written impls asking only what is stored:
impl<Lambda, D> Clone for DecoratedCospan<Lambda, D>
where
Lambda: Eq + Copy + Debug,
D: Decoration,
D::Apex: Clone,
{ /* ... */ }
Mirror for Debug. Follow the PartialEq impl that shipped in #289 — it is the worked example, in the same file.
Falsify: add a test that clones a DecoratedCospan<char, Trivial> (and Debug-formats a DecoratedCospan<_, Circuit>). Confirm it fails to compile against the derives before the fix — this is a trait-bound defect, so the falsification is a compile failure, not a red assertion — then passes after. Record the E0599 text in the commit.
Consider while there: whether the markers should simply derive Clone/Copy/Debug as unit structs. That also fixes the symptom and is a one-line change per marker, but it leaves the wrong bound in place, so the next marker anyone adds reintroduces it. The impls are the fix; deriving on the markers is a workaround.
Severity
Minor-to-important. No in-tree caller clones a DecoratedCospan, so nothing is broken today — but it is a public generic type that cannot be cloned for any decoration that exists, and the failure lands on the consumer as a confusing E0599 about a bound they never wrote. Group: applied; no dependency on the audit sweep's remaining rows.
Found during #289 (G2-T3), r6 application, 2026-08-22 — while hand-writing
PartialEqforDecoratedCospan. ThePartialEqcase was fixed there;CloneandDebugcarry the identical defect and were left alone as out of scope.The defect
catgraph-applied/src/decorated_cospan.rs:137:derivebounds the type parameters, not the field types. So it generatesimpl<Lambda, D> Clone for DecoratedCospan<Lambda, D> where Lambda: Clone, D: Clone, requiringD: Clone— butDis a marker that is never stored. The field isD::Apex.No
Decorationmarker in the workspace implementsClone:PetriDecoration(petri_net.rs:613)DebugpubTrivial(decorated_cospan.rs:371)DebugCounter(decorated_cospan.rs:457)DebugCircuit(tests/,examples/)LocalTrivial(tests/decorated_cospan.rs:98)So
DecoratedCospanis uncloneable for every decoration that exists — includingPetriDecoration, the only one a downstream consumer can name. Measured onaudit/G2-T3: cloning aDecoratedCospan<char, Trivial>givesE0599, "the methodcloneexists for structDecoratedCospan<char, Trivial>, but its trait bounds were not satisfied …Trivial: Cloneis not satisfied".Debughas the same shape but bites less:PetriDecoration,TrivialandCounterdo deriveDebug, so it only fails forCircuit/LocalTrivial.Why this is the same bug #289 already fixed once
#289's r6 pass added
PartialEqto this type and hit exactly this. A#[derive(PartialEq)]there compiles but applies to no decoration in the workspace — measured, threeE0369s. The fix that shipped was a hand-written impl bounded onD: DecorationandD::Apex: PartialEqrather thanD: PartialEq.CloneandDebugneed the same treatment and did not get it.Work
Replace
#[derive(Clone, Debug)]with hand-written impls asking only what is stored:Mirror for
Debug. Follow thePartialEqimpl that shipped in #289 — it is the worked example, in the same file.Falsify: add a test that clones a
DecoratedCospan<char, Trivial>(andDebug-formats aDecoratedCospan<_, Circuit>). Confirm it fails to compile against the derives before the fix — this is a trait-bound defect, so the falsification is a compile failure, not a red assertion — then passes after. Record theE0599text in the commit.Consider while there: whether the markers should simply derive
Clone/Copy/Debugas unit structs. That also fixes the symptom and is a one-line change per marker, but it leaves the wrong bound in place, so the next marker anyone adds reintroduces it. The impls are the fix; deriving on the markers is a workaround.Severity
Minor-to-important. No in-tree caller clones a
DecoratedCospan, so nothing is broken today — but it is a public generic type that cannot be cloned for any decoration that exists, and the failure lands on the consumer as a confusingE0599about a bound they never wrote. Group: applied; no dependency on the audit sweep's remaining rows.