diff --git a/CHANGELOG.md b/CHANGELOG.md
index c504e9c66..a47f6b071 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,14 @@ here.**
### Fixed
+- **C#: the two shared helpers now carry the `// ` marker (#460).**
+ `DecimalWire[.].g.cs` and `MetaNetValidation[.].g.cs` were the only
+ emitted files without it, so a runner with no `GenStateDir` wrote them once and then logged
+ `refusing to overwrite hand-written file` on every later run, leaving a stale copy (and the
+ `@precision`/`@scale` input check inside it) behind. A copy already on disk has no marker, so
+ regenerating alone keeps refusing it: delete the existing `DecimalWire*.g.cs` and
+ `MetaNetValidation*.g.cs` files, then regenerate. The marker is the only change to the
+ helpers' text.
- The 1.1.0 entry's handler-code figure of 439 lines retired is corrected to about 460, as
measured in one adopter's pull request.
diff --git a/server/csharp/MetaObjects.Codegen.Tests/DecimalWireTests.cs b/server/csharp/MetaObjects.Codegen.Tests/DecimalWireTests.cs
index beae89d55..2fb9c2817 100644
--- a/server/csharp/MetaObjects.Codegen.Tests/DecimalWireTests.cs
+++ b/server/csharp/MetaObjects.Codegen.Tests/DecimalWireTests.cs
@@ -539,4 +539,54 @@ public void Helpers_for_packages_bound_to_different_namespaces_get_distinct_file
Assert.Contains("MetaNetValidation.Acme.Shipping.g.cs", paths);
Compile(files);
}
+
+ [Fact]
+ public void Shared_helpers_carry_the_generated_marker_on_their_first_line()
+ {
+ var files = EmitFitness(out _);
+ var helpers = files.Where(f =>
+ f.Path.StartsWith("DecimalWire", StringComparison.Ordinal) ||
+ f.Path.StartsWith("MetaNetValidation", StringComparison.Ordinal)).ToList();
+ Assert.Contains(helpers, f => f.Path.StartsWith("DecimalWire", StringComparison.Ordinal));
+ Assert.Contains(helpers, f => f.Path.StartsWith("MetaNetValidation", StringComparison.Ordinal));
+ foreach (var f in helpers)
+ Assert.Equal(CodegenRunner.GeneratedMarker, f.Content.Split('\n')[0].Replace("// ", ""));
+ }
+
+ [Fact]
+ public void Rerun_without_a_state_dir_rewrites_the_helpers_in_every_namespace()
+ {
+ // #460: with no GenStateDir the runner falls back to the marker rule, and the helpers
+ // lacked the marker, so every run after the first refused to overwrite them.
+ var dir = Path.Combine(Path.GetTempPath(), "mo-helper-marker-" + Guid.NewGuid().ToString("N"));
+ try
+ {
+ var r = new MetaDataLoader().Load([
+ new InMemoryStringSource(TwoNamespaceModel, id: "billing.json"),
+ new InMemoryStringSource(SecondNamespaceModel, id: "shipping.json")]);
+ Assert.Empty(r.Errors);
+ var config = new GenConfig
+ {
+ OutDir = dir,
+ Namespace = "Acme",
+ PackageNamespaces = new() { ["acme::billing"] = "Acme.Billing", ["acme::shipping"] = "Acme.Shipping" },
+ };
+ string[] helpers =
+ [
+ "DecimalWire.Acme.Billing.g.cs", "DecimalWire.Acme.Shipping.g.cs",
+ "MetaNetValidation.Acme.Billing.g.cs", "MetaNetValidation.Acme.Shipping.g.cs",
+ ];
+ CodegenRunner.Run(config, r.Root, [new EntityGenerator()]);
+ foreach (var h in helpers) File.WriteAllText(Path.Combine(dir, h), File.ReadAllText(Path.Combine(dir, h)) + "// stale\n");
+
+ var second = CodegenRunner.Run(config, r.Root, [new EntityGenerator()]);
+ Assert.DoesNotContain(second.Warnings, w => w.Contains("refusing to overwrite", StringComparison.Ordinal));
+ foreach (var h in helpers)
+ {
+ Assert.Contains(second.Files, f => f.Path == h && f.Status == "written");
+ Assert.DoesNotContain("// stale", File.ReadAllText(Path.Combine(dir, h)), StringComparison.Ordinal);
+ }
+ }
+ finally { try { Directory.Delete(dir, true); } catch { } }
+ }
}
diff --git a/server/csharp/MetaObjects.Codegen/Generators/EntityGenerator.cs b/server/csharp/MetaObjects.Codegen/Generators/EntityGenerator.cs
index d823ea6cc..bef3a811f 100644
--- a/server/csharp/MetaObjects.Codegen/Generators/EntityGenerator.cs
+++ b/server/csharp/MetaObjects.Codegen/Generators/EntityGenerator.cs
@@ -1049,6 +1049,7 @@ private static void AppendNetConverterAttribute(StringBuilder sb, MetaField fiel
// the namespace swap. It lives HERE (not read from the HelperRuntime resource) so an
// ejected EntityGenerator stays self-contained, the way the MetaNetValidation text is.
private const string DecimalWireSourceTemplate = """
+//
// DecimalWire — the wire form of a field.decimal (ADR-0019 Amendments 1 and 2, W1-W4).
//
// Output follows the project's `wire.decimals` setting (Amendment 2). Under `native`, the
@@ -1417,6 +1418,7 @@ private static void AppendDecimalConverterAttribute(StringBuilder sb, MetaField
private static EmittedFile EmitNetValidationFile(string ns, string fileName)
{
var sb = new StringBuilder();
+ sb.AppendLine("// ");
sb.AppendLine("// Generated by MetaObjects entity-generator. Do not edit by hand.");
sb.AppendLine("#nullable enable");
sb.AppendLine("using System;");
diff --git a/server/csharp/MetaObjects.Codegen/Runtime/DecimalWire.cs b/server/csharp/MetaObjects.Codegen/Runtime/DecimalWire.cs
index 41083c658..c587c91a0 100644
--- a/server/csharp/MetaObjects.Codegen/Runtime/DecimalWire.cs
+++ b/server/csharp/MetaObjects.Codegen/Runtime/DecimalWire.cs
@@ -1,3 +1,4 @@
+//
// DecimalWire — the wire form of a field.decimal (ADR-0019 Amendments 1 and 2, W1-W4).
//
// Output follows the project's `wire.decimals` setting (Amendment 2). Under `native`, the
diff --git a/server/csharp/MetaObjects.IntegrationTests/Generated/DecimalWire.g.cs b/server/csharp/MetaObjects.IntegrationTests/Generated/DecimalWire.g.cs
index efd47a183..12630ac1a 100644
--- a/server/csharp/MetaObjects.IntegrationTests/Generated/DecimalWire.g.cs
+++ b/server/csharp/MetaObjects.IntegrationTests/Generated/DecimalWire.g.cs
@@ -1,3 +1,4 @@
+//
// DecimalWire — the wire form of a field.decimal (ADR-0019 Amendments 1 and 2, W1-W4).
//
// Output follows the project's `wire.decimals` setting (Amendment 2). Under `native`, the
diff --git a/server/csharp/MetaObjects.IntegrationTests/Generated/MetaNetValidation.g.cs b/server/csharp/MetaObjects.IntegrationTests/Generated/MetaNetValidation.g.cs
index df770557a..f254cb0a6 100644
--- a/server/csharp/MetaObjects.IntegrationTests/Generated/MetaNetValidation.g.cs
+++ b/server/csharp/MetaObjects.IntegrationTests/Generated/MetaNetValidation.g.cs
@@ -1,3 +1,4 @@
+//
// Generated by MetaObjects entity-generator. Do not edit by hand.
#nullable enable
using System;