Finish clean local verification of the restart-adoption and loopback fix
Written 2026-10-19 21:15 UTC.
RE-VERIFY: This is a write-time snapshot. Check https://github.com/CodexCoder21Organization/ContainerNursery/pull/664 with gh pr view --json state,isDraft,headRefOid,statusCheckRollup,mergedAt, and verify the branch with git ls-remote. A successful CI run does not replace the failed local gate below.
Design decisions in force
The 2026-10-19 brief requires: “Production behaviour without the option must be unchanged.” “Keep the forwarding socket-audit test, described honestly as proving outbound forwarding stays on loopback.” “Wait up to 60 minutes for it; never bypass it.” “Forbidden: merging, enqueueing, publishing, deploying; never dequeue anything (the supervisor may enqueue).” “kotlin.build (remote) is slowed by a known CI coordinator problem: report its state and do not re-request it.” No raised timeouts, reduced iterations, reflection, weakened assertions, or APIs made public for tests. These supersede older suggestions to retain backend configuration without a failing control.
Current state and remote work
The RPC decision is complete and pushed. The local full-suite gate is not green, so the PR remains OPEN/DRAFT. Final fetch/rebase and exact force-with-lease push at 21:12 UTC left the branch unchanged; remote main was 87764adfcbbe8eaf5b8750f063282c7a8c94b1d0. The source checkout is clean with no stash, extra worktree, or unpushed commit. A fresh main clone was used only for a bounded mechanical comparison; it has no source edits.
| Repo |
Branch |
Verified remote SHA |
PR |
Work |
State |
| ContainerNursery |
fix/flake-adopt-or-drain-restart |
af1f228857c1574873f7e3a7d8430e34ce6bfe86 |
https://github.com/CodexCoder21Organization/ContainerNursery/pull/664 |
Resolver isolation, restored real-main tests, forwarding audit; remove unused RPC constructor change |
Selected 11/11 first attempts; full XML654/657, clean first attempts653/657; OPEN/DRAFT |
Nothing was merged, enqueued/dequeued, published, or deployed by this lane. No live-code state was changed. The full runner and CI watcher have ended. Cleanup terminated the one leftover listener JVM and five sleep children in the completed full-suite process group832672; a second process audit found no live member. This lane's generated jars and unique aibuildcache were removed; the shared lock and other lanes were untouched. The baseline comparison ended exit1 at21:13UTC, XML1/4 with the same three banner failures; no second invocation was made. Its runner/children are gone and generated jars are removed; parent independently reviewed its XML attempt histories. The failed tests have three default internal runner attempts each; “no rerun” means no new command, not absence of those histories.
Motivation, decision, counts, and remaining gate
testAdoptOrDrain_restartAdoptsRunningChildInsteadOfKilling first failed during the JAR deployment-history work with Cold-start GET /pid on CN #1 failed, before adoption began (original run). The owner requested a reproduced fix, then asked to fix external-peer contact, the other HTTP providers, and incomplete response parsing. The restored real-main test later failed its loopback assertion by dialing its own runner’s public address (649/650 run).
The controlled cold-response reproduction holds Ktor’s type-metadata dependency during the first response writer. The previous generic bodyAsChannel() path blocks there until Netty closes the pending response; the JAR, Docker, and Cloud Run writers now use the public body(TypeInfo) overload with explicit byte-channel type information. This keeps their receive pipeline and streaming behavior. The controlled experiment establishes this mechanism; the original CI log alone does not establish its exact cause.
The restart test again launches the real ContainerNurseryServer.main, including provider registration, the production shutdown hook, CN_RESTART_HANDOFF, and child adoption. Its helper requires complete Content-Length or chunked framing and the exact PID body; its framing preflight accepts four valid responses and rejects eleven incomplete, ambiguous, or incorrect responses. The cold-response stress fixture uses assigned ports and still requires the same child PID across restart. The Docker and Cloud Run regressions use real local HTTP transports and require the first complete response before releasing the metadata gate.
--no-url-bootstrap-peers supplies an empty peer list. That alone still allowed wildcard-address discovery and gossip to the resolver’s own public address. The provider-created resolver now receives an explicit loopback transport; pinned resolver 0.0.1320 disables automatic LAN discovery for that explicit-network branch. The normal launch preserves the previous constructors, key seed, persisted port, and public network behavior. CLI help/error cases and the README describe the option; the restart fixture’s positive socket audit and loopback-only assertion remain intact.
The real-main discovery guard injects a reserved public-NIC result through the dependency’s existing public API. Before the fix, the real runner reached Ready and failed with expected 0 discoveries, actual 2 (0/1 passed). Earlier fixed core and neighbour evidence remains in the continuation record; final-head results appear below.
The backend RPC isolation constructor change has been removed after checking every reachable call site and pinned protocol 0.0.1420 bytecode. The facade’s sole backend stream-opening call constructs url://localhost.tcp:<integer hostPort>/. For a nonnegative port the protocol returns TcpRpcStream.connect("localhost", port, ...) before peer lookup or libp2p/LAN startup. The lazy push-bridge path calls TcpRpcStream.connect("127.0.0.1", ...) directly. Negative ports, including the admin provider’s -1, miss the TCP pattern but only perform local name/peer lookup in the fresh empty in-memory registry and resolver, then report no peer; the facade exposes no path that adds peers to that private instance. Neither construction nor closure starts its network. A URL route names a local container, not a remote backend peer.
The retained forwarding test checks the provider-created resolver’s unmodified published loopback addresses, sends a real libp2p request through the facade to a real local TCP backend, and requires the exact reply, one backend creation, one forwarded request, zero public-address discoveries, and a positive loopback-only JFR socket read/write audit. It proves outbound forwarding stays on loopback. It does not claim to detect the removed backend network setting: the previous lane’s valid constructor-only mutation and restored runs both passed 1/1, consistent with the code trace.
Validation of the final head:
- At af1f2288, the selected local runner passed 11/11 on first attempts: core 2/2, forwarding socket audit 1/1, URL/RPC/CLI/port neighbours 8/8. Both independent review passes found no actionable gap in the isolation reduction.
- The required full local command acquired
/tmp/cn-fullsuite.lock after about 52 minutes, used -Xmx2g, and completed with runner XML 654/657, 3 failed. The XML also records testReaperAndManagementRaceFailedGeneration failing its first attempt and passing the runner’s internal retry, so the clean first-attempt count is 653/657. The suite is not green.
- Three persistent failures came from the heap environment reaching child fixtures:
JAVA_TOOL_OPTIONS=-Xmx2g adds Picked up JAVA_TOOL_OPTIONS: -Xmx2g to child output. testDangerJarFile_oversizedStdoutAndStderrLinesAreTruncated then sees an extra ERROR entry, testDangerJarFile_tailForwarderPreservesUtf8AcrossChunkBoundary sees a non-é sink line, and testContainerNurseryFullConstructorBinaryCompatibility sees the banner before LINKED. Their assertions and source are unchanged from main. One requested four-scenario PR rerun reproduced these same three failures and passed the cleanup race (1/4); this does not replace the failed full-suite verdict.
- The source and first-attempt log point to a separate cleanup race: management captures a failed generation and pauses at the pre-publication clock read in
quarantineContainer. The initial and post-settlement cleanup can finish and remove the record before management resumes computeIfAbsent, which recreates the old generation’s quarantine and sends a third kill instead of two. The full first-attempt log shows settlement cleanup completing before management republishes quarantine. The scenario and ContainerNursery.kt are unchanged from main; no unrelated code or assertion is changed here. A deterministic ordering reproducer remains to be added before changing lifecycle code.
- The single identical four-scenario comparison on clean origin/main 87764adf also recorded 1/4 passed, with the exact same three JVM-banner failures and the reaper scenario passing its first body. Source comparison shows the reaper scenario and ContainerNursery.kt are identical to main. This rules out these three failures as PR changes; the first-attempt reaper failure remains unresolved. No extra invocation was made to obtain a pass; default runner attempt histories are retained.
- New-head bld-build completed 657/657 passed in its log; release also passed. The local run emitted terminal-memory metadata warnings (child output followed the expected final memory record); the successful CI log has the same warning class. These are recorded separately from test counts. Both remote checks were in progress at the latest recorded CI snapshot; none was re-requested.
The PR remains draft because the full local run was not green. Open verification work is to supply the runner heap directly without propagating the JVM banner to fixtures, address the existing cleanup race, then obtain a clean full local suite under the same shared lock.
No merge, queue change, publication, or deployment was performed. No reflection, test visibility increase, timeout increase, iteration reduction, or weakened assertion was introduced.
Continuation record: Finish full-suite and CI verification of the loopback isolation change.
Exact follow-up
- Re-verify the open draft, branch head, and main before making any change or running a build. Preserve all original lane prohibitions. The RPC decision is settled; do not re-add that constructor configuration without a public failing test.
- Correct the invocation so
-Xmx2g does not propagate through JAVA_TOOL_OPTIONS into fixture child output. scripts/test.bash overrides external JAVA_OPTS; merely switching that variable at the call site is insufficient. Its ignored generated jars/KompileCli.jar shell launcher ends with exec java $JAVA_OPTS -cp "$CLASSPATH" kompile.cli.CliKt "$@". Supplying the heap directly there (while retaining the marker and unsetting JAVA_TOOL_OPTIONS) is a possible local-only route; it was inspected but not run here. Verify heap placement in runner/forked JVMs before the next full gate. Do not change output assertions to accept the banner.
- Investigate the unrelated reaper/management race with a deterministic public-path ordering test. The current test signals managementTransitionObserved on management's second clock read, before quarantine compute/publication. Hold that call until initial and detached post-settlement cleanup finish and public status reports the generation absent, then release and require no third kill. Write an invariant table for active/quarantined/detached ownership before changing lifecycle code. Source lines in ContainerNursery.kt: quarantineContainer1922-1947; detached settlement~2680-2705; killContainerWithResult~5530-5567. Both this production file and the scenario are identical to main. A rare pass on the targeted rerun does not settle this failure.
- Once the environment and existing race are handled, acquire
/tmp/cn-fullsuite.lock with a hard3600s wait; never bypass another lane. Run the complete suite from the intended final head, record actual XML and first-attempt histories. The completed command in this lane was JAVA_TOOL_OPTIONS=-Xmx2g timeout --signal=TERM --kill-after=10s 6000s flock -w3600 /tmp/cn-fullsuite.lock scripts/test.bash --local --test . --log /tmp/fix664f-full.xml; the environment choice caused three failures. Lock waited~52minutes, execution finished21:06UTC. No additional full suite was started.
- Update the existing PR body through REST with the final verdict. Mark ready only after the full local gate is green. Never merge or change its queue state. bld-build is already SUCCESS657/657; the two remote kotlin.build checks were IN_PROGRESS at21:12UTC and must not be re-requested.
Preserved evidence and limits
Essential selected XML, independent review, and all four first-failure stack traces are inline below, so a fresh worker does not depend on this filesystem. Full raw XML3.15MB and verbose logs remain at the original lane's /tmp paths and findings path as additional local evidence; they are deliberately not committed as source or included wholesale because they contain large repeated fixture output. Their counts, exact failures, decisive log ordering, and diagnostic commands are preserved here. No source work exists only locally.
Full local first-failure stack traces
containernursery.testDangerJarFile_oversizedStdoutAndStderrLinesAreTruncated
java.lang.IllegalArgumentException: Collection contains more than one matching element.
at containernursery.TestDangerJarFile_oversizedStdoutAndStderrLinesAreTruncatedKt.testDangerJarFile_oversizedStdoutAndStderrLinesAreTruncated(testDangerJarFile_oversizedStdoutAndStderrLinesAreTruncated.kt:113)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at kompile.TestRunner.executeTest(TestRunner.kt:49)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at community.kotlin.kompile.testrunner.bootstrap.BootstrapRunner.main(BootstrapRunner.java:449)
containernursery.testDangerJarFile_tailForwarderPreservesUtf8AcrossChunkBoundary
java.lang.AssertionError: All sink entries should be verbatim 'é'. Found 1 corrupted lines (sample first 5 with code points): [[0]='Picked up JAVA_TOOL_OPTIONS: -Xmx2g' (codepoints=[50, 69, 63, 6b, 65, 64, 20, 75, 70, 20, 4a, 41, 56, 41, 5f, 54, 4f, 4f, 4c, 5f, 4f, 50, 54, 49, 4f, 4e, 53, 3a, 20, 2d, 58, 6d, 78, 32, 67])]. This indicates the tail forwarder is decoding each fixed-size byte chunk independently with `String(buf, UTF-8)`, which mangles any multi-byte codepoint that straddles the chunk boundary into U+FFFD replacement characters. The fix is to use a stateful CharsetDecoder that carries partial multi-byte sequences across chunk boundaries.
at kotlin.test.DefaultAsserter.fail(DefaultAsserter.kt:16)
at kotlin.test.Asserter$DefaultImpls.assertTrue(Assertions.kt:652)
at kotlin.test.DefaultAsserter.assertTrue(DefaultAsserter.kt:11)
at kotlin.test.Asserter$DefaultImpls.assertTrue(Assertions.kt:662)
at kotlin.test.DefaultAsserter.assertTrue(DefaultAsserter.kt:11)
at kotlin.test.AssertionsKt__AssertionsKt.assertTrue(Assertions.kt:44)
at kotlin.test.AssertionsKt.assertTrue(Unknown Source)
at containernursery.TestDangerJarFile_tailForwarderPreservesUtf8AcrossChunkBoundaryKt.testDangerJarFile_tailForwarderPreservesUtf8AcrossChunkBoundary(testDangerJarFile_tailForwarderPreservesUtf8AcrossChunkBoundary.kt:106)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at kompile.TestRunner.executeTest(TestRunner.kt:49)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at community.kotlin.kompile.testrunner.bootstrap.BootstrapRunner.main(BootstrapRunner.java:449)
containernursery.testContainerNurseryFullConstructorBinaryCompatibility
java.lang.AssertionError: The compatibility fixture did not confirm constructor linkage. Output:
Picked up JAVA_TOOL_OPTIONS: -Xmx2g
LINKED: java.lang.NullPointerException
at kotlin.test.DefaultAsserter.fail(DefaultAsserter.kt:16)
at kotlin.test.Asserter$DefaultImpls.assertTrue(Assertions.kt:652)
at kotlin.test.DefaultAsserter.assertTrue(DefaultAsserter.kt:11)
at kotlin.test.Asserter$DefaultImpls.assertTrue(Assertions.kt:662)
at kotlin.test.DefaultAsserter.assertTrue(DefaultAsserter.kt:11)
at kotlin.test.AssertionsKt__AssertionsKt.assertTrue(Assertions.kt:44)
at kotlin.test.AssertionsKt.assertTrue(Unknown Source)
at containernursery.TestContainerNurseryFullConstructorBinaryCompatibilityKt.testContainerNurseryFullConstructorBinaryCompatibility(testContainerNurseryFullConstructorBinaryCompatibility.kt:113)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at kompile.TestRunner.executeTest(TestRunner.kt:49)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at community.kotlin.kompile.testrunner.bootstrap.BootstrapRunner.main(BootstrapRunner.java:449)
containernursery.testReaperAndManagementRaceFailedGeneration first attempt
java.lang.AssertionError: Generation 1 must receive one shared initial disposition and one post-settlement disposition. Expected <2>, actual <3>.
at kotlin.test.DefaultAsserter.fail(DefaultAsserter.kt:16)
at kotlin.test.Asserter$DefaultImpls.assertTrue(Assertions.kt:652)
at kotlin.test.DefaultAsserter.assertTrue(DefaultAsserter.kt:11)
at kotlin.test.Asserter$DefaultImpls.assertEquals(Assertions.kt:671)
at kotlin.test.DefaultAsserter.assertEquals(DefaultAsserter.kt:11)
at kotlin.test.AssertionsKt__AssertionsKt.assertEquals(Assertions.kt:63)
at kotlin.test.AssertionsKt.assertEquals(Unknown Source)
at containernursery.TestReaperAndManagementRaceFailedGenerationKt.testReaperAndManagementRaceFailedGeneration(testReaperAndManagementRaceFailedGeneration.kt:351)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at kompile.TestRunner.executeTest(TestRunner.kt:49)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at community.kotlin.kompile.testrunner.bootstrap.BootstrapRunner.main(BootstrapRunner.java:449)
Decisive first-attempt log ordering (selected complete lines):
[1970-01-01 00:00:10.000] [ContainerNursery] Quarantined container for routeKey=https:reaper-management-race.test:443 before cleanup: periodic cleanup found a timed-out start in state=FAILED (quarantined=1)
[1970-01-01 00:00:10.000] [ContainerNursery] Moving active container for routeKey=https:reaper-management-race.test:443 to quarantine for force cleanup
[1970-01-01 00:00:10.000] [ContainerNursery] Quarantined container for routeKey=https:reaper-management-race.test:443 before cleanup: detached start settled after lifecycle ownership ended (quarantined=1)
[1970-01-01 00:00:10.000] [ContainerNursery] Quarantine kill confirmed container death for routeKey=https:reaper-management-race.test:443
[1970-01-01 00:00:10.000] [ContainerNursery] Quarantined container for routeKey=https:reaper-management-race.test:443 before cleanup: management force-kill requested (quarantined=1)
[1970-01-01 00:00:10.000] [ContainerNursery] Quarantine kill confirmed container death for routeKey=https:reaper-management-race.test:443
[1970-01-01 00:00:10.000] [ContainerNursery] Shutting down ContainerNursery, stopping 0 active, 0 draining, and 0 quarantined container(s)
Caused by: java.lang.AssertionError: Generation 1 must receive one shared initial disposition and one post-settlement disposition. Expected <2>, actual <3>.
New evidence preserved inline
Selected runner XML
<tests>
<buildrule name="containernursery.buildMaven()" success="true" time="542681" output-files="195" output-bytes="117449926" executions="2"/>
<test file="/tmp/claude-1000/-code/28a83ccc-c521-474c-b074-68e791f803a6/scratchpad/lanes/fix-664f/ContainerNursery/tests/testServerCliHelpAndInvalidOptions.kts" name="containernursery.testServerCliHelpAndInvalidOptions" success="true" time="8890"/>
<test file="/tmp/claude-1000/-code/28a83ccc-c521-474c-b074-68e791f803a6/scratchpad/lanes/fix-664f/ContainerNursery/tests/testUrlFacade_startPersistsOsAssignedPortInPersistentConfig.kts" name="containernursery.testUrlFacade_startPersistsOsAssignedPortInPersistentConfig" success="true" time="9453"/>
<test file="/tmp/claude-1000/-code/28a83ccc-c521-474c-b074-68e791f803a6/scratchpad/lanes/fix-664f/ContainerNursery/tests/testUrlFacadeRpc_probePassesForConformantUrlBindBackend.kts" name="containernursery.testUrlFacadeRpc_probePassesForConformantUrlBindBackend" success="true" time="12261"/>
<test file="/tmp/claude-1000/-code/28a83ccc-c521-474c-b074-68e791f803a6/scratchpad/lanes/fix-664f/ContainerNursery/tests/testUrlFacade_startCrashesWhenPersistentPortIsOccupiedAndNoMappings.kts" name="containernursery.testUrlFacade_startCrashesWhenPersistentPortIsOccupiedAndNoMappings" success="true" time="11478"/>
<test file="/tmp/claude-1000/-code/28a83ccc-c521-474c-b074-68e791f803a6/scratchpad/lanes/fix-664f/ContainerNursery/tests/testUrlFacade_providerCreatedResolverPersistsInvalidPort.kts" name="containernursery.testUrlFacade_providerCreatedResolverPersistsInvalidPort" success="true" time="12219"/>
<test file="/tmp/claude-1000/-code/28a83ccc-c521-474c-b074-68e791f803a6/scratchpad/lanes/fix-664f/ContainerNursery/tests/testUrlFacade_startSucceedsWithInvalidPortInPersistentConfig.kts" name="containernursery.testUrlFacade_startSucceedsWithInvalidPortInPersistentConfig" success="true" time="12476"/>
<test file="/tmp/claude-1000/-code/28a83ccc-c521-474c-b074-68e791f803a6/scratchpad/lanes/fix-664f/ContainerNursery/tests/testUrlFacadeRpc_isolatedForwardingAvoidsPublicAddressDiscovery.kts" name="containernursery.testUrlFacadeRpc_isolatedForwardingAvoidsPublicAddressDiscovery" success="true" time="23302"/>
<test file="/tmp/claude-1000/-code/28a83ccc-c521-474c-b074-68e791f803a6/scratchpad/lanes/fix-664f/ContainerNursery/tests/testServerIsolatedUrlNetworkIgnoresPublicAddressDiscovery.kts" name="containernursery.testServerIsolatedUrlNetworkIgnoresPublicAddressDiscovery" success="true" time="33614"/>
<test file="/tmp/claude-1000/-code/28a83ccc-c521-474c-b074-68e791f803a6/scratchpad/lanes/fix-664f/ContainerNursery/tests/testUrlFacade_portBindingRetryOnOccupiedPort.kts" name="containernursery.testUrlFacade_portBindingRetryOnOccupiedPort" success="true" time="15239"/>
<test file="/tmp/claude-1000/-code/28a83ccc-c521-474c-b074-68e791f803a6/scratchpad/lanes/fix-664f/ContainerNursery/tests/testUrlFacade_startCrashesWhenPersistentPortIsOccupied.kts" name="containernursery.testUrlFacade_startCrashesWhenPersistentPortIsOccupied" success="true" time="9721"/>
<test file="/tmp/claude-1000/-code/28a83ccc-c521-474c-b074-68e791f803a6/scratchpad/lanes/fix-664f/ContainerNursery/tests/testAdoptOrDrain_restartAdoptsRunningChildInsteadOfKilling.kts" name="containernursery.testAdoptOrDrain_restartAdoptsRunningChildInsteadOfKilling" success="true" time="65344"/>
</tests>
Independent review
Independent review: backend RPC isolation and tests
Review target: https://github.com/CodexCoder21Organization/ContainerNursery/pull/664
Checkout: ContainerNursery, HEAD af1f228857c1574873f7e3a7d8430e34ce6bfe86; compared with origin/main.
Review checklist
- [x] Read repository instructions and overview.
- [x] Read all of the linked testing and engineering documents.
- [x] Trace every use of the private backend
urlProtocol, including the nonmatching URL branch and invalid port values.
- [x] Review the forwarding test, CLI guard, restart test, and adjacent adoption tests as a separate test review.
- [x] Review commit
af1f2288 and the resolver isolation change against origin/main as a separate source review.
- [x] Record findings here.
This pass made no repository edits and ran no build or tests, as required by the review brief. I did not use reflection, mutate the public API, or create a production-only route to exercise private state.
Documents read
- Repository
AGENTS.md and README.md.
- Full Testing Architecture document saved at
/tmp/fix664f-TESTING.md, from https://github.com/CodexCoder21Organization/DocumentationRepository/blob/main/architecture/TESTING.md.
- Full Engineering Philosophy document saved at
/tmp/fix664f-PHILOSOPHY.md, from https://github.com/CodexCoder21Organization/DocumentationRepository/blob/main/PHILOSOPHY.md.
The testing guidance relevant here calls for real dependencies and public behavior tests, resource cleanup, and no reflection or test-only public access. The review follows those constraints.
Test review
Verdict: the tests cover the observable isolation behavior under review; no actionable coverage gap found for this change.
tests/testUrlFacadeRpc_isolatedForwardingAvoidsPublicAddressDiscovery.kts:23-122 exercises a real client-to-facade RPC request and a real TCP backend. It checks loopback-only advertised addresses, the exact reply, one container creation, one backend request, zero public-address discovery calls, and observed socket read/write addresses. Its nested finally closes the caller, facade, nursery, and backend, restores the process-wide discovery callback, closes the recording, and removes the trace. It uses public APIs and a real TcpServer, without mocks or reflection.
tests/testServerIsolatedUrlNetworkIgnoresPublicAddressDiscovery.kts:10-99 launches the production CLI in a child JVM, checks the discovery count at readiness, clears PUBLIC_IP and inherited JVM options, and stops the child and removes its temporary directory in cleanup. This covers isolated startup; the forwarding scenario covers service traffic. These are complementary public-path checks.
The forwarding scenario intentionally covers a valid, positive backend port. The negative-port edge is reachable through an RPC URL mapping configured with the built-in admin container: AdminContainer.hostPort is -1 (AdminContainerProvider.kt:186-206), and the route shape is accepted by the public config path (tests/testAdminRoutesDecodeUtf16AndPersistAcrossRestart.kts:81-85). That edge does not start public or LAN networking in the private protocol instance; the source review below closes that concern. It therefore does not leave the isolation claim untested.
The test does not record connect-only socket events; it pairs the read/write audit with the public-address callback and loopback address assertions. The source path and private registry audit show there is no remote peer candidate or network-start call on this route. No extra instrumentation is needed for this change.
The changed restart scenario at tests/testAdoptOrDrain_restartAdoptsRunningChildInsteadOfKilling.kts:186-305,335-429 now reads the full HTTP response and validates either exact Content-Length framing or complete chunked framing before accepting a numeric PID. It tests valid and malformed framing over real loopback sockets, checks the same child PID before and after restarting the real CLI, inspects the persisted child record, audits socket events, and cleans up both nursery processes, descendants, recorded PIDs, and the temporary directory. Adjacent URL adoption tests (tests/testAdoptSurvivingChildren_adoptsUrlRouteChildWhenConfigCarriesPort.kts:45-150 and tests/testAdoptSurvivingChildren_migratesLegacyUrlPidRouteKey.kts:28-123) cover current and legacy route keys with real child processes/listeners and cleanup.
Existing lane records show the forwarding scenario passed both on the fixed source and with only the private backend constructor changed to UrlProtocol() (1/1 each). That is expected: the test verifies real public forwarding and no discovery, not sensitivity to an unused backend network setting. The mutation result supports the source-level conclusion and should not be described as a failing regression control for the removed constructor configuration.
Adversarial source review
Verdict: the isolation reduction is sound for the current backend forwarding path. I found no regression in commit af1f2288 or the retained resolver isolation change.
At UrlFacadeProvider.kt:453, the private backend protocol is constructed as UrlProtocol(). Its only operational use is openStream(containerUrl) at UrlFacadeProvider.kt:1954; the only other use is close() at :2138. The caller builds containerUrl from the selected container's integer port as url://localhost.tcp:$hostPort/ at :1929. No public mapping value or request field can replace that host with a remote service name.
For the normal case, protocol 0.0.1420's bytecode checks LOCALHOST_TCP_PATTERN and calls connectViaTcp("localhost", port, ...) before peer lookup (/tmp/fix664f-protocol-bytecode.txt:3415-3484). The pinned pattern is localhost\\.tcp:(\\d+) (:12734-12736). The direct branch invokes TcpRpcStream.connect; it does not start the libp2p network or LAN discovery. The earlier stream-open check only rejects a closed protocol. The default protocol constructor allocates an in-memory PeerRegistry and a network provider, but does not start that provider.
I checked the negative-port edge explicitly. The readiness probe does not reject it: probeRpcProtocolConformance returns CONNECT_FAILED for hostPort <= 0 (UrlFacadeProvider.kt:2542-2544), and runRpcReadinessProbe accepts that result and continues (:1233-1237). Therefore an admin-backed RPC route can reach forwardViaRpc; localhost.tcp:-1 does not match the digits-only pattern and does enter findAuthorizedRouterForService.
That fallback still cannot discover or contact a public peer in this private instance. The default-parameter constructor used by UrlProtocol() creates PeerRegistry.inMemory(clock) and a fresh default network provider (pinned bytecode at /tmp/fix664f-protocol-bytecode.txt:253-260,275-353). Its default LanDiscoveryConfig has its enabled flag set to false (:298-321). This constructor has no bootstrap-peer list parameter and does not seed the registry. Its default NameResolutionConfig creates a fresh SignedDelegationResolver from empty descriptor lists. The resolver's resolve() parses the service identifier and reads its local TLD/delegation maps (/tmp/fix664f-signed-resolver-bytecode.txt:160-289,208-403); it has no ambient DNS or network resolver. findAuthorizedRouterForService consults that resolver and the private registry (/tmp/fix664f-protocol-bytecode.txt:4005-4100); it has no call to ensureNetworkListening. With the registry empty there is no peer to ping or route to, so the invalid-port case ends with the no-peer error before any network or LAN startup.
The isolation that does affect public behavior remains in createUrlProtocol2: when bootstrap peers are explicitly empty, it passes a Libp2pNetworkProvider bound to 127.0.0.1 (UrlFacadeProvider.kt:952-975). The server flag selects that mode only when requested, while the default still constructs UrlFacadeProvider() (ContainerNurseryServer.kt:599-620,680-686). The current commit removes only the unused backend protocol's special constructor configuration. Its default network provider may be created, but no current backend call starts it.
Review limits
This review used source, the pinned 0.0.1420 protocol bytecode, the pinned resolver bytecode, and the existing lane records. It did not execute code or build the project. The new public-forwarding test covers the valid backend-port path; the negative-port conclusion is from the public route construction and the pinned protocol's default resolver/registry behavior. The broader PR's unrelated HTTP response implementation was outside this review's requested source scope.