What happens
The same metadata field decides a runtime guard, and the two projections test it differently.
.NET — exact value match (Semantics.SourceGenerators/Generators/QuantitiesGenerator.cs:1372-1373):
bool strictPositive = vectorForm == 0
&& overload.PhysicalConstraints?.MinExclusive == "0";
C++ — presence only (Semantics.Cpp/QuantityVocabulary.cs:155):
overload.PhysicalConstraints is null ? Magnitude.NonNegative : Magnitude.Positive));
MetadataConstraints.MinExclusive defaults to string.Empty (Semantics.Cpp/QuantityMetadata.cs:130), so a non-null constraints object with no minExclusive — or with any value other than "0" — lands on opposite answers.
Failure scenario
Add "physicalConstraints": {}, or {"minExclusive": "1"}, to any V0 overload.
C++ emits the strict guard (Semantics.Cpp/CppQuantityGenerator.cs:239-244):
string comparison = type.Magnitude == Magnitude.Positive ? ">" : ">=";
...
return $"(assert({ValueName}.count() {comparison} 0 && \"{says}\"), {ValueName})";
→ assert(value > 0), rejecting zero.
.NET keeps EnsureNonNegative, accepting zero.
The same declared quantity now has two different domains depending on which language you generate.
Why it matters
Today's three constraint sites — Wavelength, Period, HalfLife, all {"minExclusive": "0"} — agree by coincidence. So nothing is broken right now, and nothing would catch it breaking.
CLAUDE.md's design decision #4 is explicit that the strict rule is opted into by minExclusive: "0" specifically:
A V0 overload can opt into a stricter rule by declaring physicalConstraints: { "minExclusive": "0" } in dimensions.json (#51); the generator then emits Vector0Guards.EnsurePositive and rejects zero too.
The C++ side does not implement that rule — it implements "any constraints at all". The next constraint added to the metadata silently forks the two APIs, and the divergence surfaces as a C++ assertion failing on a value .NET accepts.
Suggested fix
Make QuantityVocabulary test PhysicalConstraints?.MinExclusive == "0", matching QuantitiesGenerator.
Better still, factor the predicate so both generators call one implementation — this is the second case in this repo (see the dimensional-check divergence) where one metadata file is read by two generators with no shared decision logic.
Add a Semantics.Cpp.Test case asserting the Magnitude chosen for an overload with a non-"0" constraint, so the two cannot drift again.
What happens
The same metadata field decides a runtime guard, and the two projections test it differently.
.NET — exact value match (
Semantics.SourceGenerators/Generators/QuantitiesGenerator.cs:1372-1373):C++ — presence only (
Semantics.Cpp/QuantityVocabulary.cs:155):MetadataConstraints.MinExclusivedefaults tostring.Empty(Semantics.Cpp/QuantityMetadata.cs:130), so a non-null constraints object with nominExclusive— or with any value other than"0"— lands on opposite answers.Failure scenario
Add
"physicalConstraints": {}, or{"minExclusive": "1"}, to any V0 overload.C++ emits the strict guard (
Semantics.Cpp/CppQuantityGenerator.cs:239-244):→
assert(value > 0), rejecting zero..NET keeps
EnsureNonNegative, accepting zero.The same declared quantity now has two different domains depending on which language you generate.
Why it matters
Today's three constraint sites —
Wavelength,Period,HalfLife, all{"minExclusive": "0"}— agree by coincidence. So nothing is broken right now, and nothing would catch it breaking.CLAUDE.md's design decision #4 is explicit that the strict rule is opted into by
minExclusive: "0"specifically:The C++ side does not implement that rule — it implements "any constraints at all". The next constraint added to the metadata silently forks the two APIs, and the divergence surfaces as a C++ assertion failing on a value .NET accepts.
Suggested fix
Make
QuantityVocabularytestPhysicalConstraints?.MinExclusive == "0", matchingQuantitiesGenerator.Better still, factor the predicate so both generators call one implementation — this is the second case in this repo (see the dimensional-check divergence) where one metadata file is read by two generators with no shared decision logic.
Add a
Semantics.Cpp.Testcase asserting theMagnitudechosen for an overload with a non-"0"constraint, so the two cannot drift again.