Skip to content

test(ui-server): join the journal's startup release before counting native sockets - #3552

Merged
schlessera merged 1 commit into
mainfrom
test/3550-dirty-mount-seventh-socket
Nov 4, 2026
Merged

schlessera merged 1 commit into
mainfrom
test/3550-dirty-mount-seventh-socket

Conversation

@schlessera

@schlessera schlessera commented Nov 4, 2026 •

Copy link
Copy Markdown
Owner

Closes #3550

What changed

The "dirty failed mount" case in packages/ui-server/tests/http-supported-mounts.test.ts now waits until the fresh app's turn journal has settled before it opens its six native.ready() calls and counts native-manager sockets. It awaits the journal's ready, then its startup maintenance pass (maintain()), then releasesSettled(). No assertion changed: the case still requires exactly six sockets, six re-armed idle deadlines and zero pending timers after native shutdown.

Why this shape

The seventh socket's caller, captured from failing runs with an env-gated stack log on the native client's call and on the observer's release (instrumentation not committed):

OBSRELEASE  at releaseNext (src/brain/native-journal-observer.ts:319)
            at deferRelease (src/brain/native-journal-observer.ts:346)
            at commit (src/brain/native-journal-observer.ts:461)
            at observe (src/brain/turn-journal.ts:445)
            at <maintain pass> (src/brain/turn-journal.ts:344)
NATIVECALL release ... releaseOwned (src/brain/native-brain-leases.ts:114)   <- in flight
FRESH-READY / SIX-START / SIX-END                                           <- six ready() connect here
error: Expected length: 6 / Received length: 7

The journal's startup idle pass (turn-journal.ts:352, void maintain() once ready settles) commits an observation. The commit hands the previous root checkpoint to the observer's background release owner (#2872). maintain() settles when the observation commits, not when that release finishes, so the release's native request can still hold a socket while the case's six requests connect.

Not a product leak. The journal owns the release, serializes it, joins it in close() (native-journal-observer.ts close drains releasesSettled) and exposes releasesSettled() as its completion barrier. Releasing outside the observation's hold is the deliberate #2872 design. The race was in the test: it counted before the app had settled.

Why not maintain() alone, as #3342's 5e8939bce tried: it covers the pass but not the release that the pass defers (mutation A below). Why not count only the case's own sockets: after releasesSettled() resolves, the release's socket is free and gets reused, so the original oracle stays exact. Waiting on the completion barrier follows docs/process/test-waits.md (await the operation's acknowledgement, no sleeps).

Proof

Each loop below ran bun test --timeout 30000 tests/http-supported-mounts.test.ts -t 'dirty failed mount' in packages/ui-server, using a private TMPDIR.

Tree Runs Pass Fail
main @ 2fa67923d (before) 30 13 17, each Expected length: 6 / Received length: 7
this PR @ 35313bdfa (after) 30 30 0
Mutation A: remove await journal!.releasesSettled(), keep maintain() 20 11 9, each Received length: 7 at http-supported-mounts.test.ts:294 (toHaveLength(6))
Mutation B: finally { await fresh.t.close(); } instead of joining sockets in closeHttpMountFixture 5 0 5, each HTTP_MOUNT_PENDING_TIMERS {"phase":"fresh-native-sockets-closed","pending":6,...}: six 5000 ms socket idle deadlines still pending after native shutdown

Both mutations were restored, and the tree was checked against the committed file.

  • NODE_OPTIONS=--max-old-space-size=8192 bunx tsc --noEmit at the root exited 0.
  • The whole http-supported-mounts.test.ts file locally: 104 pass, 9 fail. All 9 are mounted supported handler cases (/api/brain/*, /api/schedules/proposals) failing with Agent worker boundary unavailable: unshare: write failed /proc/self/uid_map. This host has no unprivileged user namespaces, which the issue's Verification notes the rest of the file needs. The changed case passes there. The full ui-server suite was not completed locally on this host; the hosted unit jobs are the authoritative proof.
bun run check:pr --base origin/main

Rebased onto main 5d461c516 without conflicts (re-committed as 953c4599c, same tree, because a GitHub outage dropped the first rebased push's PR synchronisation), after #3557 fixed the #3553 flake that failed the first candidate (run 41780062866). Collisions at the rebased head: a textual overlap with #3342; the rest are closure overlaps. check:pr re-ran at the rebased head:

{"version":1,"kind":"brain-check-pr-receipt","head":"953c4599c5cfd7b577c4261c5d7c3496f72c0fc9","base":"origin/main","mode":"focused","startedAt":"2026-11-04T12:06:17.502Z","finishedAt":"2026-11-04T12:06:42.250Z","checks":[{"name":"all lint and leakage gates","categories":["changeset"],"outcome":"passed","elapsedMs":11649.769231},{"name":"environment documentation","categories":["changeset"],"outcome":"passed","elapsedMs":129.55273399999714},{"name":"pending changeset package names","categories":["changeset"],"outcome":"passed","elapsedMs":101.35281699999905},{"name":"contribution changeset","categories":["changeset"],"outcome":"passed","elapsedMs":16.226591000002372},{"name":"affected fast invariants","categories":["verify","unit"],"outcome":"passed","elapsedMs":12831.695647}]}

Collisions: bun scripts/collisions.ts --paths packages/ui-server/tests/http-supported-mounts.test.ts reports a path collision with #3342, the PR this unblocks. #3342 can drop its own maintain() await once this lands. All other reports are closure-level on ui-server.

Checklist

  • Focused local preflight/behavioral receipts and complete selected hosted proof passed; actual head/base/job checkout evidence is in Proof.
  • Acceptance criteria from the issue are all met, or the gap is named below.
  • The PR and completed/advanced issues have verified release milestones under docs/process/github.md.
  • A changeset is included, or this changes nothing a consumer can see.
  • No personal data anywhere in the diff — the leakage gate covers the whole tree.
  • The integration contract index or relevant component is updated in this same commit, or nothing in the machine surface moved.
  • Docs under docs/ reflect the change, or nothing documented moved.
  • Provider history stays append-only (docs/decisions/provider-history.md): the provider-history guard passes, or nothing on a turn's path changed.

Anything left open

  • The full ui-server suite needs a host with unprivileged user namespaces. Hosted unit is the proof of record for it.

🤖 Generated with Claude Code

@schlessera schlessera added this to the 0.45.0 milestone Nov 4, 2026
@schlessera schlessera added type: test Coverage, harness or proof work with no behaviour change. area: ui-server Hono app factory: turn coordinator, auth, session catalog, routes, cron. labels Nov 4, 2026
@schlessera

Copy link
Copy Markdown
Owner Author

@schlessera

Copy link
Copy Markdown
Owner Author

Candidate proof (run 41780062866) failed only on an unrelated test: ws-ask-turn-timeout.test.ts ask_user_form, waitFor timed out, in unit (2). The changed case passed. It is filed as #3553 and was not rerun for green. This PR waits on #3553 or a maintainer decision, then on a fresh confirm.

…ative sockets

The dirty failed mount case counted a seventh native-manager socket when the
turn journal's startup maintenance pass had committed its observation and
handed the previous checkpoint to its background release (#2872) while the
case's six ready() calls connected. Await the pass and releasesSettled()
before counting, so the case observes only its own six sockets.

Closes #3550

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@schlessera
schlessera force-pushed the test/3550-dirty-mount-seventh-socket branch from 00d204a to 953c459 Compare November 4, 2026 12:06
@schlessera
schlessera marked this pull request as draft November 4, 2026 12:07
@schlessera
schlessera marked this pull request as ready for review November 4, 2026 12:07
@schlessera
schlessera merged commit b6ddb64 into main Nov 4, 2026
43 of 55 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: ui-server Hono app factory: turn coordinator, auth, session catalog, routes, cron. type: test Coverage, harness or proof work with no behaviour change.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ui-server: dirty failed mount case intermittently counts a seventh native socket

1 participant