Summary
The MOOSE Phase A boundary guard inspects only inputs for stateful roles. An add_output("h_new", expr, roles::History) or any output with a user-defined Role{.is_stateful=true} passes through silently and produces a generated MOOSE Material that declares the property as a regular (non-stateful) `MaterialProperty`. The Phase B machinery (old/new pair, stateful initialisation) isn't generated, so the material would compile in MOOSE but behave incorrectly at runtime — no error message indicating the recipe needs Phase B.
This was a pre-existing asymmetry surfaced (but not introduced) by the Role-struct refactor in 4d95600. The earlier behaviour was the same because `OutputRole::HistoryNew` was also unchecked.
Where
`include/numsim_codegen/targets/moose_material.h` — the stateful check in `emit()` only iterates `model.inputs()`. There is no symmetric iteration over `model.outputs()`.
Fix
Mirror the existing input check for outputs:
```cpp
for (auto const &o : model.outputs()) {
if (o.role.is_stateful) {
throw std::runtime_error(
"MooseMaterialTarget: stateful role '" + std::string(o.role.name) +
"' on output '" + o.name +
"' requires the History machinery (old/new MaterialProperty pair, "
"stateful initialisation) which is not implemented in this phase. "
"See the numsim-codegen Phase B roadmap.");
}
}
```
Pair with a negative test in `tests/MooseTargetTest.cpp` asserting that an output with `roles::History` (or any `is_stateful` role) triggers the throw. This will be naturally covered by the negative-path test suite tracked in #11.
Refs
Summary
The MOOSE Phase A boundary guard inspects only inputs for stateful roles. An
add_output("h_new", expr, roles::History)or any output with a user-definedRole{.is_stateful=true}passes through silently and produces a generated MOOSE Material that declares the property as a regular (non-stateful) `MaterialProperty`. The Phase B machinery (old/new pair, stateful initialisation) isn't generated, so the material would compile in MOOSE but behave incorrectly at runtime — no error message indicating the recipe needs Phase B.This was a pre-existing asymmetry surfaced (but not introduced) by the Role-struct refactor in 4d95600. The earlier behaviour was the same because `OutputRole::HistoryNew` was also unchecked.
Where
`include/numsim_codegen/targets/moose_material.h` — the stateful check in `emit()` only iterates `model.inputs()`. There is no symmetric iteration over `model.outputs()`.
Fix
Mirror the existing input check for outputs:
```cpp
for (auto const &o : model.outputs()) {
if (o.role.is_stateful) {
throw std::runtime_error(
"MooseMaterialTarget: stateful role '" + std::string(o.role.name) +
"' on output '" + o.name +
"' requires the History machinery (old/new MaterialProperty pair, "
"stateful initialisation) which is not implemented in this phase. "
"See the numsim-codegen Phase B roadmap.");
}
}
```
Pair with a negative test in `tests/MooseTargetTest.cpp` asserting that an output with `roles::History` (or any `is_stateful` role) triggers the throw. This will be naturally covered by the negative-path test suite tracked in #11.
Refs