diff --git a/Darling/Darling.Tests/ManagedConfFileTests.cs b/Darling/Darling.Tests/ManagedConfFileTests.cs
index 33017fde4..eacd6d19b 100644
--- a/Darling/Darling.Tests/ManagedConfFileTests.cs
+++ b/Darling/Darling.Tests/ManagedConfFileTests.cs
@@ -368,6 +368,20 @@ public void RenderBody_ContainsCheckpointTimeout()
Assert.Contains("checkpoint_timeout = '15min'", body, StringComparison.Ordinal);
}
+ /// Pin: always renders listen_addresses as
+ /// exactly loopback (DarlingManagedPostgres.BuildConfAppend's v1 line) — never the network
+ /// address an exposed store's command line adds. Every verification path that reads this rendered text
+ /// (, )
+ /// depends on that being true — it is the reason the command line always outranks this file for the key,
+ /// on every exposed store, every start.
+ [Fact]
+ public void RenderBody_ListenAddresses_IsAlwaysLoopbackOnly()
+ {
+ var body = ManagedConfFile.RenderBody(SampleInputs());
+
+ Assert.Contains("listen_addresses = '127.0.0.1'", body, StringComparison.Ordinal);
+ }
+
///
/// #4246 (claude-desktop's parity pin, 14:47Z): for EVERY marker in
/// , every key that marker's OWN legacy builder
diff --git a/Darling/Darling.Tests/ManagedConfMigrationRunnerTests.cs b/Darling/Darling.Tests/ManagedConfMigrationRunnerTests.cs
index 9eae4c5b3..7bd90cc59 100644
--- a/Darling/Darling.Tests/ManagedConfMigrationRunnerTests.cs
+++ b/Darling/Darling.Tests/ManagedConfMigrationRunnerTests.cs
@@ -503,6 +503,67 @@ public void VerifyStepB_NewErrorFromManagedFile_Fails()
Assert.Contains("work_mem", outcome.MismatchedKeys);
}
+ /// Pin: is the
+ /// MigratedUnstamped re-verification's own scan (DarlingManagedPostgres.MigrateManagedConfAsync),
+ /// which must skip the same as
+ /// does, for the same reason: an exposed store's
+ /// listen_addresses is always overridden by the command line, so darling-managed.conf's rendered
+ /// (always loopback-only) line reports error = "setting could not be applied" on every start even
+ /// though nothing needs healing. 192.0.2.10 (RFC 5737) stands in for the store's own address.
+ ///
+ [Fact]
+ public void FindUnstampedManagedFileErrors_ListenAddressesOverriddenByCommandLine_IsNotAnError()
+ {
+ var managedPath = Path.Combine(_dataDir, ManagedConfFile.FileName);
+ var rows = new List
+ {
+ new(SourceFile: managedPath, SourceLine: 1, Name: "listen_addresses", Setting: "127.0.0.1", Applied: false, Error: "setting could not be applied"),
+ Applied("work_mem", "16MB", file: managedPath, line: 2),
+ };
+
+ var (hasError, mismatchedKeys) = ManagedConfMigrationRunner.FindUnstampedManagedFileErrors(rows);
+
+ Assert.False(hasError);
+ Assert.Empty(mismatchedKeys);
+ }
+
+ /// Pin: the same skip for port — also command-line-owned
+ /// ().
+ [Fact]
+ public void FindUnstampedManagedFileErrors_PortOverriddenByCommandLine_IsNotAnError()
+ {
+ var managedPath = Path.Combine(_dataDir, ManagedConfFile.FileName);
+ var rows = new List
+ {
+ new(SourceFile: managedPath, SourceLine: 1, Name: "port", Setting: "5555", Applied: false, Error: "setting could not be applied"),
+ Applied("work_mem", "16MB", file: managedPath, line: 2),
+ };
+
+ var (hasError, mismatchedKeys) = ManagedConfMigrationRunner.FindUnstampedManagedFileErrors(rows);
+
+ Assert.False(hasError);
+ Assert.Empty(mismatchedKeys);
+ }
+
+ /// Pin: a REAL error row on another key (not command-line-owned) still reports as an error,
+ /// with that key named — the skip is narrow, not a blanket "ignore darling-managed.conf errors".
+ [Fact]
+ public void FindUnstampedManagedFileErrors_RealErrorOnOtherKey_StillReportsError()
+ {
+ var managedPath = Path.Combine(_dataDir, ManagedConfFile.FileName);
+ var rows = new List
+ {
+ new(SourceFile: managedPath, SourceLine: 1, Name: "listen_addresses", Setting: "127.0.0.1", Applied: false, Error: "setting could not be applied"),
+ new(SourceFile: managedPath, SourceLine: 2, Name: "work_mem", Setting: "16MB", Applied: false, Error: "invalid value"),
+ };
+
+ var (hasError, mismatchedKeys) = ManagedConfMigrationRunner.FindUnstampedManagedFileErrors(rows);
+
+ Assert.True(hasError);
+ Assert.DoesNotContain("listen_addresses", mismatchedKeys);
+ Assert.Contains("work_mem", mismatchedKeys);
+ }
+
/// Pin: a store with a configured network endpoint always starts PostgreSQL with
/// listen_addresses forced onto the command line ().
/// The rendered darling-managed.conf line stays loopback-only, so PostgreSQL reports THAT file
diff --git a/Darling/PerformanceMonitor.Darling.Service/DarlingManagedPostgres.cs b/Darling/PerformanceMonitor.Darling.Service/DarlingManagedPostgres.cs
index d9accae73..dc312314a 100644
--- a/Darling/PerformanceMonitor.Darling.Service/DarlingManagedPostgres.cs
+++ b/Darling/PerformanceMonitor.Darling.Service/DarlingManagedPostgres.cs
@@ -3037,23 +3037,15 @@ outcome is now unknown. Keep the prior non-null outcome rather than erasing it w
same here: migrated, no pending file, stale stamp.
Re-verify against what is on disk NOW: no new error row may come from darling-managed.conf
relative to the file's own current bytes — the file itself is the ground truth once no
- pending snapshot survives to compare against. */
+ pending snapshot survives to compare against. Except DarlingStoreHostProfile.CommandLineOnlyKeys
+ (port, listen_addresses): an exposed store always starts PostgreSQL with both forced onto
+ the pg_ctl command line, which outranks the file unconditionally, so the rendered
+ (always loopback-only) listen_addresses line reports an error row here on every start of
+ an exposed store even though nothing is actually wrong — same trap and same fix as
+ ManagedConfMigrationRunner.VerifyStepB below. */
var rows = await snapshot(cancellationToken);
var managedConfPath = Path.Combine(_dataDirectory, ManagedConfFile.FileName);
- var newErrorFromManagedFile = false;
- var mismatchedKeys = new List();
- foreach (var row in rows)
- {
- if (row.Error is not null && row.SourceFile is not null
- && string.Equals(Path.GetFileName(row.SourceFile), ManagedConfFile.FileName, StringComparison.OrdinalIgnoreCase))
- {
- newErrorFromManagedFile = true;
- if (row.Name is not null)
- {
- mismatchedKeys.Add(row.Name);
- }
- }
- }
+ var (newErrorFromManagedFile, mismatchedKeys) = ManagedConfMigrationRunner.FindUnstampedManagedFileErrors(rows);
if (newErrorFromManagedFile)
{
diff --git a/Darling/PerformanceMonitor.Darling.Service/ManagedConfMigrationRunner.cs b/Darling/PerformanceMonitor.Darling.Service/ManagedConfMigrationRunner.cs
index 42378eadd..847471d52 100644
--- a/Darling/PerformanceMonitor.Darling.Service/ManagedConfMigrationRunner.cs
+++ b/Darling/PerformanceMonitor.Darling.Service/ManagedConfMigrationRunner.cs
@@ -296,6 +296,49 @@ private static ManagedConfMigrationOutcome CompareAndFinish(
ManagedConfVerificationStatus.Failed, mismatches, backupPath, ManagedConfMigrationStep.A);
}
+ ///
+ /// The re-verification's own scan of a
+ /// fresh pg_file_settings snapshot: every error row whose sourcefile names
+ /// , by (not a suffix
+ /// match — a stray old-darling-managed.conf in an include directory is a different file), EXCEPT
+ /// (port, listen_addresses) — the
+ /// same skip applies, and for the same reason: an exposed store's command line
+ /// always outranks the file for both keys, so PostgreSQL reports the rendered (always loopback-only) file
+ /// row with error = 'setting could not be applied' on every start, never a real mismatch. Returns
+ /// the matched keys (empty when clean, whether because nothing matched or every match was named for a
+ /// skipped key) so the caller can log and build the Failed outcome exactly as before this method
+ /// existed — this is a pure extraction, not a behavior change beyond the skip. HasError is true
+ /// whenever a genuine (non-skipped) error row was found, INCLUDING one with no Name — the
+ /// pre-extraction code failed on that row too, even though it never had a key to name.
+ ///
+ internal static (bool HasError, IReadOnlyList MismatchedKeys) FindUnstampedManagedFileErrors(
+ IReadOnlyList rows)
+ {
+ var hasError = false;
+ var mismatchedKeys = new List();
+ foreach (var row in rows)
+ {
+ if (row.Error is null || row.SourceFile is null
+ || !string.Equals(Path.GetFileName(row.SourceFile), ManagedConfFile.FileName, StringComparison.OrdinalIgnoreCase))
+ {
+ continue;
+ }
+
+ if (row.Name is not null && Array.IndexOf(DarlingStoreHostProfile.CommandLineOnlyKeys, row.Name) >= 0)
+ {
+ continue;
+ }
+
+ hasError = true;
+ if (row.Name is not null)
+ {
+ mismatchedKeys.Add(row.Name);
+ }
+ }
+
+ return (hasError, mismatchedKeys);
+ }
+
///
/// Step B: after a normal derivation has already rendered and written
/// darling-managed.conf, checks that (a fresh pg_file_settings