diff --git a/Darling/Darling.Tests/ManagedConfMigrationRunnerTests.cs b/Darling/Darling.Tests/ManagedConfMigrationRunnerTests.cs
index 6a9ce820c..9eae4c5b3 100644
--- a/Darling/Darling.Tests/ManagedConfMigrationRunnerTests.cs
+++ b/Darling/Darling.Tests/ManagedConfMigrationRunnerTests.cs
@@ -503,6 +503,94 @@ public void VerifyStepB_NewErrorFromManagedFile_Fails()
Assert.Contains("work_mem", outcome.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
+ /// row with error = "setting could not be applied" once its command-line value differs —
+ /// confirmed against a live PostgreSQL 18 instance.
+ /// must not treat that as a mismatch: listen_addresses is skipped, but a REAL mismatch on another
+ /// key in the same batch still fails.
+ [Fact]
+ public void VerifyStepB_ListenAddressesOverriddenByCommandLine_IsNotAMismatch_OtherKeyStillFails()
+ {
+ var rendered = "listen_addresses = '127.0.0.1'\nwork_mem = '16MB'\n";
+ WriteManaged(rendered);
+
+ 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: "8MB", Applied: true, Error: null),
+ };
+
+ var outcome = ManagedConfMigrationRunner.VerifyStepB(_dataDir, rows, rendered, previousText: null);
+
+ Assert.Equal(ManagedConfVerificationStatus.Failed, outcome.Status);
+ Assert.DoesNotContain("listen_addresses", outcome.MismatchedKeys);
+ Assert.Contains("work_mem", outcome.MismatchedKeys);
+ }
+
+ /// Pin: when EVERY other key matches and the only rendered key that doesn't is
+ /// listen_addresses — overridden by the command line, same as above — Step B now completes
+ /// (Verified), writes the verified stamp against the rendered text, and leaves the managed file
+ /// in place (no restore of ). Before this fix, this row alone drove Step
+ /// B to Failed on every start of any store with a configured network endpoint. The row's exact
+ /// shape (setting reads the file's rendered value, not the command line's; error reads
+ /// "setting could not be applied"; applied is false) was captured from a real
+ /// pg_file_settings row on a PostgreSQL 18 container started with a command-line
+ /// listen_addresses set to loopback plus the container's own address (docker run
+ /// timescale/timescaledb:2.30.1-pg18 -c listen_addresses=127.0.0.1,<container address>, with an
+ /// included conf file rendering the product's usual loopback-only listen_addresses = '127.0.0.1'
+ /// line), then querying directly against it. The address
+ /// below (192.0.2.10) is the RFC 5737 documentation range, standing in for the container's own
+ /// address — the captured row shape does not depend on which address is used.
+ [Fact]
+ public void VerifyStepB_OnlyListenAddressesOverriddenByCommandLine_Verifies()
+ {
+ var rendered = "listen_addresses = '127.0.0.1'\nwork_mem = '16MB'\nmax_connections = '200'\n";
+ WriteManaged(rendered);
+
+ 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),
+ Applied("max_connections", "200", file: managedPath, line: 3),
+ };
+
+ var previousText = "work_mem = '8MB'\n";
+
+ var outcome = ManagedConfMigrationRunner.VerifyStepB(_dataDir, rows, rendered, previousText);
+
+ Assert.Equal(ManagedConfVerificationStatus.Verified, outcome.Status);
+ Assert.Equal(ManagedConfMigrationStep.B, outcome.Step);
+ Assert.True(ManagedConfMigrationSteps.IsVerified(_dataDir));
+ Assert.Equal(rendered, File.ReadAllText(managedPath));
+ }
+
+ /// Pin: the same completion for port — also command-line-owned
+ /// () — whose file row is overridden the same
+ /// way when the command line pins a different port than the rendered file line.
+ [Fact]
+ public void VerifyStepB_OnlyPortOverriddenByCommandLine_Verifies()
+ {
+ var rendered = "port = '5555'\nwork_mem = '16MB'\n";
+ WriteManaged(rendered);
+
+ 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 outcome = ManagedConfMigrationRunner.VerifyStepB(_dataDir, rows, rendered, previousText: null);
+
+ Assert.Equal(ManagedConfVerificationStatus.Verified, outcome.Status);
+ Assert.Equal(ManagedConfMigrationStep.B, outcome.Step);
+ Assert.True(ManagedConfMigrationSteps.IsVerified(_dataDir));
+ }
+
/// Pin (#4336): the exact change-log line for two changed keys.
[Fact]
public void FormatStepBChangeLog_TwoKeys_ExactLine()
diff --git a/Darling/PerformanceMonitor.Darling.Service/ManagedConfMigrationRunner.cs b/Darling/PerformanceMonitor.Darling.Service/ManagedConfMigrationRunner.cs
index d861703cf..42378eadd 100644
--- a/Darling/PerformanceMonitor.Darling.Service/ManagedConfMigrationRunner.cs
+++ b/Darling/PerformanceMonitor.Darling.Service/ManagedConfMigrationRunner.cs
@@ -304,9 +304,19 @@ private static ManagedConfMigrationOutcome CompareAndFinish(
/// (#4336; , not a suffix match — a stray old-darling-managed.conf
/// in an include directory is a different file). A row's
/// applied may be false — an operator line below the include can legitimately override it; only
- /// the value and the absence of an error matter here. All keys match: stamp the new text as verified. Any
- /// key fails: restore (when there was one) so the old stamp matches
- /// again, and report Failed.
+ /// the value and the absence of an error matter here.
+ ///
+ /// (port, listen_addresses) are
+ /// skipped entirely here — never looked up in 's keys, never compared —
+ /// because BuildServerRuntimeOptions always starts PostgreSQL with both on the pg_ctl -o "-c
+ /// ..." command line, which outranks the conf file unconditionally (confirmed live). An exposed
+ /// store's running listen_addresses carries the network IP the rendered file line never does
+ /// (the file always renders loopback-only, #4215/BuildConfAppend); PostgreSQL's own pg_file_settings
+ /// then reports that file row with error = 'setting could not be applied' — not merely
+ /// applied = false — because the value differs from what's actually in force, same as any
+ /// operator line the command line has already overridden. Comparing this key here would fail Step B on
+ /// every start for any store with a configured network endpoint, even though the store's listener is
+ /// exactly what it should be; there is nothing this file could ever own for either key.
///
internal static ManagedConfMigrationOutcome VerifyStepB(
string dataDir,
@@ -335,6 +345,11 @@ internal static ManagedConfMigrationOutcome VerifyStepB(
var mismatchedKeys = new List();
foreach (var (_, name, value) in DarlingManagedPostgres.ParseConfText(renderedText))
{
+ if (Array.IndexOf(DarlingStoreHostProfile.CommandLineOnlyKeys, name) >= 0)
+ {
+ continue;
+ }
+
var ok = byKey.TryGetValue(name, out var candidates)
&& candidates.Exists(r => r.Error is null && string.Equals(r.Setting, value, StringComparison.Ordinal));
if (!ok)