Scheduled ping closes a healthy host connection when the control queue is full #234
Closed
opened 2026-10-17 06:13:43 +00:00 by kayg
·
23 comments
No Branch/Tag specified
main
j470b-server-mesh-rejoin
mesh-compat-382
j444b-d381-key-wins-flood
j461b-macos-utun-reader-thread
j475b-remove-host-motion
fix-reconnect-new-link
j481-d380
dev
j479-mesh-soak
fix/j476-host-facts
j475-motion-pill-split
j470-mac-node-join
root-upgrade-d376
j474-review-merge
latency-targets-120hz
nightly-suite-shards
gotatun-pin-j469
macos-utun-reader-thread
fix-flood-echo-stall
wip/j416
motion-fast-ease-out
mesh-addhost-j259
takeover-close-terminals-d368
kbd-instant-followups
fix-trust-key-double-click
nightly-suite-partial-report
appwin-icons-j207
rusqlite-slice1
swipe-content-first-d374
mesh-mtu-381
addhost-instant-open
glass-contrast-d365-mock
glass-contrast-d365
signout-followups-442
fix-startup-toast-red
research-gotatun-matrix-j317
themes-sync-j304
fix-load-flakes
fix-nix-zig-patch
fix-compute01-browser-baseline
ratchet-size-baseline-419
predict-auto-d130
revoke-coverage-d367
nightly-suite-queue-timeout
nightly-browser-suite
fix-rustsec-2026-0459
fonts-ioskeley-obel
log-retention-d375
fix-kbd-no-anim-427
fix-dev-red-specs-2
takeover-proof-305-j205
wip/j410-server-pump
ui-consistency-d372
fix-osc52-clipboard
libghostty-vt-1.0
forget-retired-host-d369
j404-merge-426
fix-setup-lock-race
fix-touch-select-426
revoke-access-report-ui
fix-predictive-echo-specs
fix-selinux-agent-exec
wip/j395-checkpoint2
wip/j395
j391-review-scratch
ui-consistency-j195-deferred-rebased
fix-rustc-paths-421
decisions-wording-341
fix-install-suite-red
fix-dev-rtt-specs
fix-pending-removal-align
fix-privileged-skip
fix-install-suite-404
review-j368
mesh-dns-j254
revoke-access-report-d356b
revoke-privilege-round5
fix-removal-confirm
review-j360
gotatun-rotation-367
deps-refresh-340
review-j354
review-J351
review-j350
addhost-takeover-d359
revoke-report-items-347
revoke-end-sessions-d362
review-j348
size-mesh-ssh-346
size-trim-337
revoke-access-diff-d356
j259-pre-rebase-b06c5a6
j305-ended-at-start-spec
addhost-sudo-consent
fix-layout-real-specs-411
fix-shell-open-411
fix-console-hosts-373
fix-shell-exit
fuzz-host-name-353
fix-host-details-410
macos-filter-j197
research-gotatun-tricks
revoke-p3-j216l
stale-state-pill
revoke-followups-j216k
revoke-progress-j216e
backup/macos01-20261106/lume/j197
backup/macos01-20261106/lume/appwin-icons-j207-stash0
backup/macos01-20261106/claude/stash-0
backup/macos01-20261106/claude/stash-1
backup/macos01-20261106/claude/J80-macos-s1
backup/macos01-20261106/claude/J80
backup/macos01-20261106/claude/J80-slice1-20261102
backup/macos01-20261106/claude/J16b-appwin-on-dev
backup/macos01-20261106/claude/appwin-launch-idle-146
backup/macos01-20261106/claude/J80-s1-run
wip/j259
trust-285-p3c
runtime-lock-j216f
mesh-ssh-p3-j175
tun-proof-p3b
trust-285-p3
terminal-polish-j271
tun-proof-p3
ssh-install-flakes-j216i
fuzz-fixes-j264
trust-285-a
trust-285-b
restart-oversized-fixture-j198
backup/macos01-20261106/lume/appwin-icons-j207
restart-bound-j216h
up-preflight-p3-j198
backup/macos01-20261106/lume/j251
up-preflight-j198
wip/up-preflight-j198-j245-p2
wip/up-preflight-j198-j245-p1
agent-fsync-offloop-j196
wip/up-preflight-j198-j245
settings-offscreen-j231
wip/up-preflight-j198-j226-inspection
wip/j103f
themes-j206
wip/up-preflight-j198-j226
flakes-j216c
backup/macos01-20261106/claude/j197
flakes-j216b
backup/macos01-20261106/claude/j197-red
wip/up-preflight-j198-review
flakes-j216
backup/macos01-20261106/claude/appwin-icons-j207
engineering-j208b
appwin-mock-j209
toast-stack-j165
engineering-j208
decisions-d351
decisions-d350
decisions-d349
appearance-presets-mock-j129
decisions-d348
docs-audit-spec
doc-coverage-baseline-raise
decisions-d347
decisions-d346b
decisions-d346
j88h-profile
ui-consistency-j195-deferred
ui-consistency-j195
engineering-practices-j194
preferred-login-ui-j166
signin-pools-263-j174
macos-node-265-s1
decisions-d345
decisions-d344
proxy-cgroup-j164
verify-quick-fuzz-329
proxy-start-io-j168
timing-flakes-j163
up-263-s1
mcp-events-j147
verify-quick-fuzz-j158
proxy-p3-j159
agent-resource-j157-detect
wip/j103d-final
remote-cancel-hang-j155
proxy-traefik-281-j62d
j149-verify-quick
proxy-traefik-281-j62b
j150-drop-unnamed
wip/j79d-red-r4
wip/j103d-d332
rootless-toast-j142
wip/j103d-head
wip/j103d
j125c-mcp
macos-build-j136
backup/macos01-20261106/claude/j136
wip/j79d-red-cli
wip/j79d-red-server
j121-link-liveness
j118c-tracer-failures
wip/j79c-head
release-desktop-j131
j124c-signed-macos
wip/j79c-red-p3
wip/j79c-red-p23
wip/j79c-red-p22
merge-j128-check
perf-deferred-j128
sidebar-toggle-260
j127-shortcut-changed-dot
j125-mcp
j118-tracer-failures
j126-account-summary
settings-sections-mock-j92
j120-pinch-lock
sidebar-292
backup/macos01-20261106/claude/j120
taste-2026-11-03
row-actions-242-215
backup/macos01-20261106/claude/stash-0-untracked
backup/macos01-20261106/claude/J80-265-s1-run
backup/macos01-20261106/claude/J80-macos-s1-20261102
j112-canvas-commands
ui-no-overshoot-scroll
fix-recovery-restart
backup/macos01-20261106/claude/stash-1-untracked
taste-2026-11-02
size-baseline-ratchet
machine-cards-structure-j108
j109-boot-copy-mockups
fix-dev-red
wip/up-263-s1
j95b-settings-rows
proxy-traefik-281
j95-option-rows
docs-human-j91
docs-keys-palette
fix-flood-1013-270-j38d
mesh-ssh-s1
mesh-ssh-s0
addhost-d291-tunnel
test-stability-load
mesh-relay-220
repro-296
shim-thp-off-24
mesh-pins-258-v2
mesh-dns
signin-d311-merged
docs-pages
ui-size-283
fix-ui-batch-specs
wip/j12b-edge-update
gate-on-arm
fix-rename-sync-293
trust-285-j60b
fix-focus-ring-clip
integ-ui-1101
edge-update-v2
signin-d311
taste-2026-11-01
layout-per-tab-250
ui-approved-splitopt-224
ui-approved-splitopt-224-on-dev
icon-text-centre-282
ui-approved-189-212-216
research-reverse-proxy
transfer-budget-320
review-mesh-pins-258-r2
sidebar-pill-259
review-mesh-relay-220
wip/j12-edge-update-v2
nix-package
status-crash-loop-237
fix-dev-test-failures
fix-fixture-socket-path
wip/mesh-relay-220-pre-dev-advance-rebase
fix-canvas-side-clip
research-head-to-head
frosted-real-256
review-mesh-pins-258
wip/mesh-relay-220-pre-final-rebase
review-layout-per-tab-250-r2
broad-suite-triage
nix-devshell
review-status-crash-loop-237-r3
wip/nix-devshell-pre-p3
taste-frosted-real
wip/code-serve-demo-2026-10-30
wip/code-serve-glass-2026-10-30
wip/code-serve-clusters-2026-10-30
wip/code-merge-release94-dev-2026-10-30
wip/rproxy-delta-scratch-2026-10-30
wip/stale-note-257-staged-2026-10-30
wip/d241-size-staged-2026-10-30
review-broad-suite-207
wip/mesh-pins-258-pre-split
wip/mesh-relay-220-pre-rebase
mesh-measure-220
plan-211-update
review-edge-autoupdate-r3
perf24-reconnect
review-stale-note-r2
release-prod-live
d241-shell-marker
review-mesh-pins-server-258
review-revocation-r3
review-setup-token-r2
review-sync-journal-r3
wip/review-mesh-pins-258-prune-review-1995fee46
frosted-scroll-256
sync-journal-r3
revocation-per-account-wip
review-tokio-asyncfd-0386
review-sync-p3s
wip/stash-8by64-stash0-2026-10-30
mesh-drag-r2
setup-token-data-wip
release-9-4
auth-busy-1013
mesh-view-overlap
sync-p3s
review-auth-busy-wake
review-readd-r8-wip
mesh-drag-bounds
audit-auth-93
app-windows-stream-start
review-authority-cap-r3
app-windows-r7-measure
readd-minimal-r6
readd-minimal-r5
app-windows-r7-soak
readd-minimal-r4
app-windows-r7
app-windows-r6
readd-minimal-r3
readd-minimal-r2
readd-minimal
merge-release8-dev
review-busy-page-liveness-r2
review-busy-page-liveness
mesh-view-trim
review-authority-read-retry-r2
review-remove-uninstall-r2
release-retry-reattach
release-retry
review-remove-uninstall
review-authority-read-retry
app-windows-ship
mesh-autoswitch-p2
mesh-ssh
mesh-relay
mesh-macos-node
screen-and-control
session-labels
review-real-logins-r5
review-real-logins-r4
review-real-logins-r3
shim-memory-r2
review-real-logins-r2
shim-memory
shadow-addhost-instant
shadow-real-logins
shadow-same-session-flood
shadow-prod-perf-fix
hosts-from-server
app-windows-real
app-windows-transport
release-prod-6w
mesh-policy-verify
quiet-close-toast
dev-merge-r4
review-mesh-agent-transport
review-app-windows-transport
settings-roundtrip
review-release-pipeline-r4
mesh-agent-transport
ended-strip-fix
review-audit-ca-update-d244
settings-rows
phone-width
signout-clear
fix-restart-tests
dev-merge-rp
signer-hardening
review-audit-ca-update
terminal-fidelity
review-audit-ca-update-wip
mesh-integrate
review-mesh-agent-transport-515d3712
review-mesh-dataplane-feec1988
mesh-dataplane
signout
research-canvas-node
wip/review-renderer-cost
wip/review-terminal-fidelity
padlock-glyph
review-release-pipeline-2e95b689
phone-polish
sidebar-polish
inbox-generic
review-mesh-dataplane-378d4055
prod-batch
ui-inbox
ui-ca-unlock
ui-machine-sections
review-mesh-dataplane-a5f744fb
tls-session-prod
session-bounds-prod
fix-dead-session
session-bounds
tls-session
palette-polish
shortcuts-d222
research-appmeta
phone-gutter
ui-centre-button
wip/ui-centre-button
wip/tls-session
review-addhost-breakit
ui-platform
renderer-cost
remote-agents
mesh-api
ssh-bootstrap
prod-hotfix2
prod-fixes
review-remote-agents
review-mesh-dataplane
session-life
research-browser
review-mesh-api
research-capture
review-mesh-api-round3
review-mesh-api-round1
gate-stability
review-gate-stability
review-relay-direct
review-mesh-api-findings
proto-sessions
font-picker
proto-addhost
proto-windows
mesh-core
latency-gate
research-bsd
research-discovery
keytray-mods
mock-core
ui-tweaks
fix-motion-sel
fix-motion
integrate2
fix-robust
t-web
t-server
vt-ghostty
fix-snap-echo
t-web-auth
review-vt2
t-agent
lane-protocol
lane-shim
lane-auth
s1-base
s1-contracts
s1-workspace
No results found.
No labels
launch-qa
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set
Reference
kayg/infraternal#234
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Scheduled Ping admission treats a temporarily full native control FIFO as a dead transport, closing a healthy host primary connection. RLR-13 regression is already committed as
54d24be15on ping-fifo-fix.Track the fix, audit of server/host control sends, regression tests, base-versus-branch terminal-flood measurements at LAN and 150 ms RTT, final fast gate and D243 size comparison here.
Related: #172 (RLR-13), #227 (connection indicator flicker), #201 (production reliability). No merge or deploy.
Start: branch ping-fifo-fix, HEAD
54d24be15, base2a5302cc7(origin/dev at branch creation). Read #234 (no comments), all #227 and #172 comments, and repository contracts. Continuing RLR-13 failing regression. Scope: queue-admission fix, control-send audit and individual regressions, perf-VM base/branch LAN and 150 ms RTT flood, fast gate and D243 sizes. Push ping-fifo-fix only; no merge/deploy.Control-send audit: embedded.rs scheduled Ping bypasses writes.can_admit(); process_incoming Ping also propagates Pong enqueue refusal as primary failure. daemon.rs reply() maps Full to WouldBlock and dispatch propagates it for Pongs, action results, plugin events, terminal closes and the census/open-result fence. Existing daemon_readiness_tests even require retirement on 32 coalesced Pings. Added individual regressions for scheduled Ping (existing cherry-pick), server Pong, host Ping replies, and host reliable action replies.
Implementation choice: skip scheduled probes before issuing their correlation when capacity is unavailable; keep checking outstanding reply deadlines. On the host dispatcher, reserve room for the maximum two-message turn (census then open Result) before polling reply-producing sources, waking only on queue capacity. Preserve bounded FIFO/order and ongoing independent native terminal work. No new dependency, wire layout or resource limit. This replaces local-full retirement with producer backpressure; transport failure and reply expiry still retire connections.
Proposed additive clarification to contracts/wire.md route liveness (#234): local queue pressure is not evidence of a dead peer. Scheduled Ping may be skipped before its reply lease is issued; an already outstanding lease is never renewed by skipping/coalescing. Best-effort Pong and terminal-command probes may be omitted under local pressure, leaving the requester’s correlated timeout authoritative. Reliable control messages remain ordered in bounded producer queues and wait for admission capacity. No frame layouts, authority checks or timeout budgets change.
All five regression cases observed failing before their fixes on base implementation
54d24be15/ origin/dev2a5302cc7:Fixes are in the worktree; no commit yet. Existing primary-output saturation control passed. New host tests also require all 32 replies to drain in order when the same blocked writer is released; queue bounds and reply timeouts remain unchanged. The natural flood fixture uses two real PTYs and memory-only enrolled identity; no expected close count is fabricated. Baseline static build is running under the shared build slot.
Baseline static x86_64 artifact from origin/dev
2a5302cc76: 21,300,016 bytes, SHA-256 236e8496260f6de12d93ecd0e0ce1f920716e1029b6a3db0ecdf1bccfe055606. Recipe: CARGO_TARGET_DIR=$HOME/.cache/codex-scratch/ping-fifo-fix/target scripts/static-cargo build --locked --release --package infraternal --target x86_64-unknown-linux-musl. Same pinned toolchain/compiler wrappers/profile as final gate; no dependency changes. Build passed under build-slot. Post-fix fmt/Clippy/touched-crate library tests and matched optimized measurement images are queued. Perf-VM functional smoke check is waiting for flock -w 3600; owner currently process-per-user ppu-soak-d296-p2c, started 2026-10-17T06:36:06.712368+00:00. No flood results yet.Broader server audit found the same probe-only retirement at websocket_transport.rs:361 and :388: both transport Ping and wire Ping replies retire the browser socket when the 128-slot reply FIFO is full. Added one regression per reply type, covering preservation of the queued reply, continued admission after drain, and rejection of a closed writer. Extracted the unchanged try_send policy for direct regression coverage; behavior fix remains pending observation of both red tests. Plugin/action admission overflow in plugins.rs returns a scoped refusal; remote replacement/priming overflow refuses the candidate, preserving the healthy primary. Those are resource admission refusals, not probe-induced healthy-connection retirement.
Both browser reply regressions now observed red: 0 passed / 2 failed, each at queue-full admission. Fix applied: Full skips the optional reply while Closed remains fatal; the reader continues processing correlated Pongs. Receipt saved at $HOME/.cache/codex-scratch/ping-fifo-fix/evidence/red-browser.log; issue-attach is still running.
First host-library validation stopped on 15 NotFound fixture setup failures, including pre-existing relative ../../target paths; no timeout/baseline waiver claimed. The checkout had no target fixture parent because Rust outputs live in the mandated external CARGO_TARGET_DIR. Created a temporary, untracked target symlink to this job’s private scratch fixture directory and set INFRATERNAL_TEST_STATE_ROOT; corrected the new reply fixture to use an existing parent. All fixture/build artifacts remain under the job scratch directory. Rerunning scoped lint/tests before committing.
Takeover after interruption: branch ping-fifo-fix, base
2a5302cc7(origin/dev), previous head54d24be15. Kept the worktree fixes after review and committed them atomically (3ae395719..748343331, pushed): wire contract, server scheduled Ping/Pong, browser proof replies, host terminal Ping, host control-reply backpressure, ignored flood fixture. Fixture now counts retired terminals and relay drops and prints its result line before asserting, so the base run still reports numbers. Scoped host lib: 128 passed / 0 failed. Runningscripts/verify fastand building matched base/branch release test images for the perf VM now.Measurements and root cause (head
c179cdcff, perf VM 10.69.69.208, 4 vCPU, under /var/lock/perf-bench.lock)Short version: the queue fix in this branch is correct and has its tests, but it is not what drops connections under real load. Under load, the primary closes because the reply deadline is shorter than one QUIC loss recovery. One lost packet on the ordered control stream holds back every later Pong for about 2 round trips. At 150 ms that is about 300–450 ms, and the deadline is about 250 ms. Changing the deadline belongs to the owner (D265: "dead link detected within about one round trip"), so this branch does not change it. Experiment numbers are below.
Fixture
remote_runtime::tests::ping_flood::primary_ping_during_terminal_flood(ignored; release musl test image). It uses a real enrolled mTLS QUIC host client, real PTYs and a parked secondary, so the shipping bounded control writer is in use. Both the primary and the secondary go through a userspace UDP delay relay with optional random loss. Nothing else on the VM is touched; no host netem. Load: PTY 1 runswhile :; do head -c 32MiB /dev/zero | tr '\000' x; done. PTY 3 takes a continuous 4000-byte-line paste (server→host). PTY 2 echoes once a second (echo = input → output round trip). Images: base = origin/dev2a5302cc7+ fixture only; branch = this branch.10-minute rows, base origin/dev vs branch (fixture
d505e8b35)No terminal was retired in any row. Echo samples were 600/600 in every row except base 150 ms + 1 % (599/600).
Which code closed the primary (cfg(test) caller lines,
c179cdcff)liveness.expired()=true, never a full-queue Ping (probe_ok=falsenever fired).expired=true.expired=true. So the server's own control FIFO does not need to be full.expired=true. It brought nothing, so it was dropped.What the branch fixes (each red before, green after, all in the fast gate)
54d24be15regression). Now the probe is skipped and no lease is issued.These are real P1 gaps; the deterministic test reaches (1) in about 75 ms of saturation. But the realistic flood rows show that, under load, the deadline fires first.
Decision needed (owner; foundational, not decided in a job)
The liveness deadline at 150 ms is about 250 ms. One QUIC loss recovery on the ordered control stream costs about 2·RTT + ack delay. Options, measured or estimated:
The one close left in A (
actor_lines=[958]= tick/expiry) suggests a burst loss longer than one recovery. A + B together would cover it.Raw result lines:
$HOME/.cache/codex-scratch/ping-fifo-fix/evidence/vm-results.txt. Experiment tree:.../exp(liveness.rs only). Pong-bypass patch:.../evidence/pong-routing.patch.Also seen:
auth::tests::socket_tests::restart_open_liveness_review::responsive_socket_survives_a_held_terminal_open_with_an_existing_attachmentis flaky on base too (1 of 6 base runs fails after 90 s, "socket test stopped before its observed event"; 1 of 5 on branch). It existed before this branch.Finish: head
c179cdcff(pushed to ping-fifo-fix), scripts/verify fast verbatimStatic builds fail on size only. Origin/dev builds to exactly the cap (x86_64 21,300,016 B). This branch: x86_64 21,301,008 B (+992 B), aarch64 19,501,672 B (+1,296 B). That is real growth from the fix, not the registry-path noise. Caps were not raised. Needs a trim or an owner waiver (D243). No merge, no deploy.
Start D300 A+B (adaptive reply deadline + doubt-then-retire). Branch ping-fifo-fix rebased onto origin/release-prod-live
3048580c1(production fix); local headab514ecf2before D300 work. Conflicts resolved keeping production behaviour: remote_control_queue_tests (kept production's Reconnecting-skipping delivery loop, added the diagnostic line), wire.md (both paragraphs), daemon.rs (production's SESSION_ONLY guards combined with the branch'scontrol_readygate). Plan: failing tests first for (A) deadline from measured srtt/rttvar with floor/ceiling, (B) first miss → doubtful + browser reconnecting without teardown, second consecutive miss → retire, reply clears doubt; then perf-VM 10-min rows base vs branch and dead-host (SIGSTOP) detection time.D300 A+B implemented; pushed ping-fifo-fix
e77f5fb88(rebased on release-prod-live3048580c1).Red first (production behaviour + stub API), scoped server lib run:
The actor test fails at its check right after the first miss: the production line closes the QUIC link on the first missed reply. (The socket_tests restart_open_liveness_review failure in the same run is the pre-existing flake already recorded on this issue.)
Green: 100 passed / 0 failed in scoped liveness/control_queue/embedded/websocket; the real mTLS QUIC actor test prints
LIVENESS_DOUBT doubt_ms=269 retire_ms=524(LAN: doubt at the 250 ms floor, retire after the second allowance). clippy -D warnings and fmt clean.Design: host allowance clamp(2·srtt+4·rttvar+75 ms, 250 ms, 2 s) for primary + terminal routes, browser unchanged. Primary first miss → Reconnecting to settled windows (link kept); reply → new TerminalOutput::Recovered to exactly those windows → browser socket emits terminal.replay_complete at its own forwarded cursor (client already treats it as the readiness fence). Second consecutive miss → retire. Never-answered link retires at 2 s grace. Perf-VM rows and the dead-host timings follow.
Break-it review (Opus) started: branch
review-ping-fifo-fixfrom origin/ping-fifo-fix at4e7a8d2e3(base release-prod-live3048580c1). Focus: liveness allowance bounds, alternating misses, doubt scoping/clearing, Recovered ordering, agent FIFO wait deadlock, browser path.D300 perf-VM measurement: base vs branch (head
4e7a8d2e3)Venue: perf VM
infraternal-perf-test(4 vCPU, kernel 6.12.111+deb13-amd64), overnetbird ssh, every row underflock /var/lock/perf-bench.lockand attached to its own SSH session (nothing detached). Each row started only when VM load was below 16 and at least 40 GB was free. The highest VM load during any row was 5.43. Matched optimized fixture images (primary_ping_during_terminal_flood, real PTYs, one flood PTY + one echo PTY + one upload PTY, the same shim binary for both):release-prod-live3048580c1+ fixture:base-release-testssha256381a574c…5698591ping-fifo-fixb01f1e326(code is unchanged at head4e7a8d2e3):branch-release-testssha256f1a52e30…f3945334f4b15342…fce2cf210-minute rows (flood + upload, one 600 s run each)
Dead host (the host stops answering 15 s in, no flood; 5 trials each)
Time from the host going silent to the window being told ("notice") and to the primary being retired ("retire"):
All 26 runs exited 0, and no window was retired (
terminals_retired=0) in any run.Findings
Limits
kill -STOPwould stop the server too. The dead host is simulated by making the relay drop every datagram in both directions (RELAY_BLACKHOLE). From the server's side this matches a stopped agent process: no datagrams arrive.Row logs (VM):
/root/infraternal-bench/ping-fifo-fix/d300/; copied to the build host at~/.cache/codex-scratch/ping-fifo-fix/d300/vm-logs/d300/.Size ratchet and gate
4e7a8d2e3build: static caps set to the measured sizes (D243 16 KiB allowance, approved by the orchestrator): x86_64 21,090,448 → 21,093,520 B (+3,072), aarch64 19,316,264 → 19,319,768 B (+3,504); docs/PROCESS.md section 7 now matches.scripts/verify faston4e7a8d2e3(rc=0):Break-it review (Opus) of ping-fifo-fix
4e7a8d2e3: HOLD (P0 0, P1 1, P2 2, P3 3)Branch
review-ping-fifo-fixheada7b648166= target + failing tests incrates/infraternal-server/src/liveness_doubt_breakit_tests.rs. No fixes. The perf VM was not used.P1-1: a stale doubt confirms the replacement link mid-replay (failing test, fails 5 runs out of 5)
run_actor'sdoubtedlist is drained only bynotify_doubt(true). Nothing clears it onconnection_lost, on Activate/promotion, or on anyliveness = Liveness::host_link()reset. The sequence: doubt, second miss, retire, reattach. The new lease is "not doubtful", so the next tick sendsRecoveredto the window as soon as the reattach Ack lands. At that point the host is still replaying and itsterminal.replay_completehas not been sent. (settledchecks onlyawaiting_ack/proof_rebind_pending.)Observed:
notices so far: ["Ack { in_seq_acked: 0 }", "Recovered"].websocket.rs turns this into
terminal.replay_complete. The browser (terminal.ts:930/1395) treats it as the replay fence and stops suppressing terminal responses for the rest of the replay. Replayed terminal queries then get answered again into the user's PTY, "Catching up…" clears early, and unacked input is resent before the replay ends. This happens on every retire that follows a doubt and has a non-trivial replay, which is the dead-host path that D300 is about.Test:
retired_doubt_never_confirms_a_replacement_link_mid_replay.P2-1: live output during a doubt is handled as replay in the browser (code read)
Doubt sends
terminal.reconnecting, which setsattachmentConfirmed=falseandsuppressReplayResponses=true(terminal.ts:914-925). The link is kept and live output keeps flowing. Responses to live queries (DA/DSR/OSC from a program that starts during the blip) are dropped, and typing is held until Recovered. The browser path was left untouched, and that is what causes this.P2-2: retirement timing vs the D300 text (numbers; question for the owner)
Settled path, paused clock (
doubt_and_retire_times_per_rtt):D300 says "a dead host is noticed after about 0.4 s at 150 ms … LAN is unchanged". That holds only for the Reconnecting notice. The link retires, and the reconnect starts, at 0.75 s at 150 ms and at 0.5 s on LAN (it was 0.25 s). Worst-case retirement of a dead host is 4001 ms, two 2 s ceilings (
inflated_host_link_still_retires_a_dead_host_within_four_seconds, passes). Inflation limits itself: a reply later than 2× the allowance is rejected.P3-1: doubt flaps forever at the 2 s ceiling (failing test)
Take a proven link whose RTT climbs to 2–4 s because a deep uplink buffer fills. Every probe raises a doubt and its own reply clears it: 10 doubt episodes in 10 probes, and the link never retires. Each flip brings the P2-1 effects. This does not matter at 150 ms (the allowance is 375 ms once settled). Test:
queued_path_at_the_ceiling_does_not_flap_doubt_every_probe.P3-2: the doubt notice is never retried for a full window
The
notify_doubtdoc says a full window "is told on a later tick". Only the Recovered side retries.doubt_startedfires once per episode, so a window that is full at that tick never gets Reconnecting.P3-3: Recovered leaves websocket
awaiting_ack = trueThe
in_seq_acked > input_sent_endcheck stays relaxed until the next Ack.Checked, no finding
Gate:
scripts/verify fastona7b648166(the only failures are the 2 review tests)Fix round for the break-it review (Opus) started (Claude). Branch
ping-fifo-fix, base4e7a8d2e3; review testsb814e60e2+a7b648166cherry-picked first. Plan: one commit per finding (P1-1, P2-1, P3-1, P3-2, P3-3); P2-2 goes to the owner as a note, timings unchanged.Break-it fixes on
ping-fifo-fix(pushed, headf06512497; gate follows)Review tests cherry-picked first (
727255f09,7cce516c4). One commit per finding:cc657f35e:doubtedbelongs to its link. Every lease reset (fresh connect, Activate promotion, idle link) goes throughfresh_host_lease, which also clears the list, and the actor clears it whenever no link is installed. A replacement link never getsRecoveredfrom an earlier episode; replay completion is left to the replacement's own fence.retired_doubt_never_confirms_a_replacement_link_mid_replaypasses.dbc82eb9b: ceiling hysteresis. If a doubt recovers while the allowance is at the 2 s ceiling, the link is pinned: later first misses raise no doubt, and only retirement (still 2 × 2 s) reports them. The first reply that brings the allowance back below the ceiling unpins it. Below the ceiling nothing changes, so 150 ms paths (~375 ms allowance) keep early notice after a single loss.queued_path_at_the_ceiling_does_not_flap_doubt_every_probepasses, plus 2 new liveness tests.9c648cc08: a doubt is now its own state. Actor →TerminalOutput::LinkDoubt, socket →terminal.link_doubt, recovery →terminal.link_recovered. The viewer shows "Reconnecting…" in the window status and changes nothing else: it stays confirmed, keeps answering live terminal queries and keeps sending input.link_recoveredclears only that exact text and is never a replay fence. Neither notice sets the socket'sawaiting_ack, which removes the cause of P3-3. Older viewers treat unknown notices as a protocol failure, so these notices go only to viewers that set the new browser Hello caps bit 1 (LINK_DOUBT_NOTICES). New viewers set bit 1 and ignore notice types they do not know. Tests: websocketlink_notice_tests, tracertest/link-doubt.test.ts(now in the ts gate). contracts/wire.md is updated.f06512497:report_doubtoffers the doubt notice again on every tick of the episode, so a window whose queue was full is told once it has room. Test:a_window_full_at_doubt_start_is_told_on_a_later_tick.No timings changed, so the 10-minute perf rows were not re-run. Settled-path doubt and retirement times are the same as in the review table.
Owner note (P2-2, wording only; timings unchanged)
Measured on a settled path with a paused clock (
doubt_and_retire_times_per_rtt):D300's "a dead host is noticed after about 0.4 s at 150 ms … LAN is unchanged" is true only for the window indicator. The link itself retires, and the reconnect starts, at about twice that: 0.75 s at 150 ms, and 0.5 s on LAN where it was 0.25 s. In the worst case (allowance at the ceiling) a dead host retires after 4 s. Question for kayg: keep these timings and correct the D300 wording, or change the retire rule? Nothing was changed here.
Break-it fixes done: head
8f3945801(pushedping-fifo-fix)Two commits since the earlier note:
b5ed41406makes the new P3-2 test pass pedantic clippy (assert_is_empty).8f3945801ratchets the size caps. Both binaries shrank: x86_64 21,093,520 → 21,091,504 B (−2,016), aarch64 19,319,768 → 19,318,840 B (−928). docs/PROCESS.md §7 matches.Flake, not from this round: the second gate run had one failure,
infraternal-agent daemon::readiness_tests::uninstall_handoff_runs_only_after_a_successful_real_socket_write(daemon_readiness_tests.rs:350,left: true right: false). The fix round did not touchcrates/infraternal-agent. The test passes 10 out of 10 runs on its own, and the third full run below is green. A likely cause from reading the code (not proven):Socket::inherited(writer.as_raw_fd())shares one fd withwriter, so the fd is closed twice and can race with other tests in the same process.scripts/verify faston8f3945801(rc=0):The perf VM was not used: no timing changed. The owner question on P2-2 is still open (see the previous comment).
Break-it delta review r2 (Opus) started. Branch
review-ping-fifo-fix-r2, base origin/ping-fifo-fix8f3945801. Scope: prior P1-1/P2-1/P3-1/P3-2/P3-3, caps-bit negotiation, indicator text, retry bounds, ceiling pin vs retirement, agent flakeuninstall_handoff_runs_only_after_a_successful_real_socket_write.Break-it review r2 (Opus) — #234, origin/ping-fifo-fix
8f3945801Review branch:
review-ping-fifo-fix-r2@fda0c95f7(tests only; two commits on top of8f3945801).Prior findings
retired_doubt_never_confirms_a_replacement_link_mid_replaypasses. Every lease reset goes throughfresh_host_lease(fresh connect, Activate in both select arms, idle link). The loop top clearsdoubtedwhileconnection.is_none(). I checked everyconnection.take()site: each one passes throughNonebefore a new link is installed, or throughfresh_host_lease.LinkDoubt→terminal.link_doubt. It sets noawaiting_ack, the window stays confirmed, and replies and input keep flowing (tracerlink-doubt.test.tspasses).queued_path_at_the_ceiling_does_not_flap_doubt_every_probepasses. The pin is cleared by any reply that brings the allowance below 2 s.a_window_full_at_doubt_start_is_told_on_a_later_tickpasses).awaiting_ack(link_noticereturns false for both doubt notices).Checks that passed (new tests in liveness_doubt_breakit_r2_tests.rs)
link_notice(.., false) == None). The old viewer would throw on an unknown notice, and it never gets one.4e7a8d2e3test onlycaps & RESUME_NEXT, so bit 1 is ignored. dev has no doubt path at all, so nothing changes for it.REMOTE_WRITER_BINDING) is a separate agent↔server channel and does not collide with the browser bit.a_window_that_reconnects_mid_doubt_is_told_and_recoveredpasses. The new attachment is told once it settles and later gets its recovery. The departed id is dropped and not retried.doubt_retry_sends_one_notice_per_window_per_episodepasses. 8 windows, half of them full at the start, over 80+ ticks: each gets exactly 1 LinkDoubt and 0 Recovered.a_pinned_dead_host_retires_within_two_ceilings_without_doubtpasses. The link retires ≤ 4 s + 1 tick after the last probe, with no doubt notice.retired()ignorespinned.link_recoveredclears only the exact text "Reconnecting…". No other code sets that exact string (the connection layer uses "Connection lost. Reconnecting…").New findings
P3-4: the doubt indicator is invisible on the canvas (failing test)
On the canvas, a terminal's status element is the detached relay element (
canvas/status.ts). That relay drops routine states that end in "…" or mention "reconnect", and leaves them to the window's status icon. The status icon follows the terminal hooks (onDisconnected/onAttached), andlink_doubtcalls none of them. So in the product UI a doubted link shows nothing. "Reconnecting…" is visible only on the non-canvas core screen.This is safe, because nothing is held or suppressed. But the "browser shows Reconnecting…" claim does not hold where people actually work.
Test:
apps/tracer/test/link-doubt-canvas.test.ts(fails: no toast, no hook).Fix direction for the owner or the fix job: a hook (for example a doubted connection state) that the window status icon reads.
Not a deploy blocker.
Flaky agent test: REFUTED double close; the root cause is a fork race in the test (failing tests)
Socketnever closes its fd (Descriptorhas no Drop; transport.rs module doc says so). The test dropssocket(which deregisters the fd) beforewriter(which closes it). So there is no double close.The recorded failure was "left true, right false": the unhealthy write succeeded. The test assumes that
drop(reader)closes the peer. But any child that a concurrent test forks holds a copy of every fd until it calls exec (CLOEXEC acts only at exec). While that copy exists the socket stays open and the write lands in its buffer.Evidence:
the_unhealthy_handoff_case_races_concurrent_spawns:rounds=20000 spawned=428 write_succeeded=62(gate rerun: spawned=351 write_succeeded=84) (fails).a_dropped_peer_still_accepts_the_write_while_a_forked_copy_holds_itreproduces it deterministically with a held copy (fails).Fix: call
reader.shutdown(Shutdown::Both)before the drop. Shutdown acts on the socket itself, not on one descriptor. This is a test-only issue, not a product bug.Verdict
MERGE: P0 0 / P1 0 / P2 0 / P3 1 (P3-4), plus 1 test-only flake with its root cause found.
Gate (
scripts/verify fastatfda0c95f7, verbatim)The test step fails only on the two intended failing agent tests (
test result: FAILED. 138 passed; 2 failed; 20 ignored). Cargo stops at that binary, so the TS step did not run in this gate. The TS failing test isbun test test/link-doubt-canvas.test.ts(1 fail; link-doubt.test.ts 4 pass). An earlier gate run at52231b0c7also failedsession_exit_events(idle canary: 1 wakeup in 2.2 s, under load). It is unrelated to this delta.release-2 started (production line + #234 D300 ping/FIFO fix + #189 already-added "Open a terminal").
Branch
release-2, base origin/release-prod-lived5a4188d3(Retry + faster add-host + #241).Merging origin/ping-fifo-fix
8f3945801with review tests origin/review-ping-fifo-fix-r2fda0c95f7(agent flake fixed withreader.shutdown(Shutdown::Both)), then origin/already-added-opende1928b27. Then caps, tests, gate, artifacts, staging proof on the perf VM, Docker sshd add-host. No deploy.release-2 finished. Head
103815e70(pushed torelease-2), base origin/release-prod-lived5a4188d3. No deploy; monitor01 untouched.Merged (each resolution explained in its merge commit)
5317c3b20: #234 D300 ping-fifo-fix8f3945801+ r2 review testsfda0c95f7. The flaky agent test now callsreader.shutdown(Shutdown::Both)before the drop. The review's two fork-race tests are now regression tests and pass (a_shut_down_peer_refuses_the_write_while_a_forked_copy_holds_it,the_unhealthy_handoff_case_survives_concurrent_spawns; 3/3 with the original).c4a814da7: link-doubt-canvase6387508e(P3-4, web only). link-doubt-canvas.test.ts passes.4d70ec609: already-added-opende1928b27(#189). Conflicts were only in the web app (AddHostSink keeps prepareTerminal, enrolled(hosts,status), setupInPill, openHost/hasHost; mock sink; spec watchdog + activeWindow). Rust auto-merged: the duplicate lookup runs before register(), so Retry's lock/refresh and the instant path are untouched.87676178a: remove-uninstall27a0a1e65(#235). Checked against Retry: runtime admission takes the shared parent flock only while admitting a start, so uninstall's bounded exclusive setup_guard cannot deadlock a running infraternal agent.6c3c5f02f: authority-read-retrya9db5526b(#240). websocket.rs merged cleanly (different functions from D300). The r2 failing P3 test review_authority_cap.rs was left out.d0422100d+103815e70: live harnessBREAKIT2_READD=1re-adds the host, checks already_added + host_id, then opens a terminal on that id.7cdbfc9fd: caps.Size (same target dir; release-prod-live reproduces its caps exactly)
d5a4188d3Total -704 / +6,288 B (≤ +16 KiB).
Artifacts (
/home/kayg/.cache/codex-scratch/release-2/artifacts, built at7cdbfc9fd; the later commit is test-only)3614720Staging
103815e70and rerun above.Tests
INFRATERNAL_TEST_STATE_ROOT=$HOME/.cache/r2and the static musl helper. With$HOME/.cache/r2rel,review_live_session_socket_in_newline_home_blocks_cleanupfails, because the socket path exceeds sun_path. That is the environment, not the code; the one ssh-plugin failure was the same test.cargo test -p infraternal-plugin-remotealone does not compile (tokio rt-multi-thread is only enabled through workspace feature unification). This is pre-existing; the workspace gate runs it.Gate (
scripts/verify fastat103815e70, RUST_TEST_THREADS=2, INFRATERNAL_TEST_STATE_ROOT=$HOME/.cache/r2, start load 3.9, 73 GB free; exit 0)x86_64 21080144 cap 21080144 +0; aarch64 19322648 cap 19322648 +0. TS protocol 37/0.
Evidence: instant-readd.log full-readd.log full-clean.log full-retry.log remove-rootless.log remove-rootless-readd.log remove-root.log remove-root-readd.log remove-root-mesh.log remove-root-mesh-readd.log staging-proof.log authority-stall-soak.log MANIFEST.txt addhost-wizard-playwright.log suites.txt verify-103815e70.log
Release 2 deployed to production at 14:08 IST: release-prod-live
103815e70, server sha256 8608623e…, MainPID 86026, /healthz ok. Backup /var/backups/infraternal/20261018-083808-103815e70…. CA is locked after the restart until the owner unlocks it.Closed by the orchestrator during the 2026-10-28 handover (#254), on the owner's request to close finished issues. Evidence: Ping/control-queue fix and link-doubt indicator released in
103815e70(ancestor of dev and prod, release 2) with measurements. Reopen if anything is still missing.kayg referenced this issue2026-10-28 17:53:55 +00:00