Open
opened 2026-11-08 17:30:49 +00:00 by kayg
·
59 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#433
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?
J379 implements D368, referencing #404. Start: branch takeover-close-terminals-d368, base
098f2c4452(current origin/dev). Plan: add red regression coverage; extend the existing finished-install scan and exact confirmation record; wire terminal details into the existing confirmation; prove root takeover, stale confirmation, non-terminal refusal and rollback on a disposable VM; capture production-build screenshots on dev-02; run quick and compute01 fast, compare unrelated reds against dev. Estimate: 4 hours plus shared build-slot queue time. minipc01 is preserved and will not be accessed. No merge, push or deployment authorized by the task.Finding: report_installed calls holders_beside_agent, which calls live_session_sockets before scan_holders agent. Any bound terminal endpoint therefore returns 73 and makes a finished root install busy even when every holder is its own session scope. Evidence: crates/plugins/ssh/src/previous_attempt.sh lines 718–740 on base
098f2c445. The runtime helper already authenticates session peers and kernel-bound endpoints; extend that proof for the confirmation rather than trusting scope names alone. Root fixture and browser proof pending. First red run: compute01 ~/j379-evidence/red.log.Owner clarification: the confirmation stays inside Add host, as the next step of the same sheet, with the terminal list and Close it and take over. Back returns to the refusal step. Reusing the existing takeover step and shared key/value list. First parser red confirmed: terminal_takeover_record_names_what_confirmation_will_close fails because the baseline parser rejects the new terminal-bearing record (compute01 ~/j379-evidence/red.log). Backend green currently queued; root proof will use a temporary root-owned install, the shipping shim and uniquely named systemd session scopes. No access to minipc01.
Checkpoint: local WIP head on takeover-close-terminals-d368; authenticated terminal records, exact consent matching and final recheck are implemented, with a root-only real shim/scope fixture. The parser red is confirmed and its initial green passed (compute01
/j379-evidence/backend-green.log). I interrupted the broader shell oracle after spotting and correcting an all-account scan in unprivileged fixtures; that interrupted run is not a pass. Full gates, root proof and UI wiring remain pending. Browser red is queued on dev-02 (/j379-evidence/browser-red.log). Main target: compute01 ~/worktrees/j379/target; root proof clone: /root/j379, not built yet. No push or deploy.Browser red confirmed on dev-02 production build: the new terminal confirmation test fails because the existing Add host Take over step has no terminal summary. Evidence: claude@infraternal-dev-02-std:~/j379-evidence/browser-red.log. Wiring the existing step, key/value list and Back action as the owner confirmed. A separate regression is testing the retained SSH check channel’s 512-byte record limit before changing it.
Transport finding: valid four-terminal confirmation records exceeded the retained SSH check channel’s 512-byte line limit. Red: compute01 ~/j379-evidence/channel-red.log. Green: ~/j379-evidence/backend-green-latest.log (two duplex-check tests and six earlier-install parser tests pass). Only finished-install records receive the larger bounded allowance; ordinary oversized receipts still refuse. Root helper is now building from source in /root/j379; no live claim is held during the build. Browser work was removed from the queue so this job cannot hold two build slots.
Root proof found a real implementation failure before completion: a real shim plus idle root bash in its exact session scope is still classified busy. All seven fixture cases therefore failed before any consent/close; the temporary service/scopes were cleaned and the mesh claim released. Evidence: root@infraternal-dev-01-compute01:/root/j379-root-proof.log and /root/j379-root-debug.log (probe exits 73; scope members have the expected root shell/foreground group). Investigating the authenticated probe with scratch-only instrumentation. Branch rebased onto current origin/dev
dba2f64248; no numbered decisions edited.Root-cause finding: contracts/shim.md specifies Hello.created_unix_ms, and session.rs emits SystemTime.as_millis(). The new takeover probe incorrectly bounded it as seconds, rejecting every real terminal. Corrected the bound to milliseconds and convert only the public since field to seconds; the fingerprint retains the exact original timestamp. The seven root failures are the red proof for this boundary. Scratch diagnostic instrumentation was removed before the queued build starts.
Root run after the timestamp fix: real offer and explicit scope close work; new-terminal, changed-record, real foreground-program change, nonterminal holder across uid, and failure-after-close rollback passed. Two fixture failures remain in /root/j379-root-green.log: systemctl removes the empty wants directory before the synthetic new-install helper recreates its link, and the race wrapper used unset $2 on daemon-reload (exit 2). Fixed those fixture assumptions and rerunning all seven cases. /root/j379-root-final.log is the final root run. The run wrapper takes/releases the J379 mesh claim only inside its granted slot.
Real-root proof passed: root@infraternal-dev-01-compute01:/root/j379-root-final.log ends verbatim: Ran 7 tests in 69.112s / OK / ROOT_PROOF_EXIT=0. Real root shim PTYs and exact system scopes prove offer-only does not close, explicit close and takeover, new terminal before confirm, changed record, real foreground-program change, new terminal during stop, other-uid nonterminal fd holder refusal, and D359 restoration after close. The new-install commit is the existing synthetic installer fixture; the old service is a parked stand-in, not live server enrollment. Cleanup verified infraternal-agent.service LoadState=not-found and no J379 mesh claim. minipc01 was never accessed. Browser production specs are now queued on dev-02.
Review finding: the pre-close Take over scan covers every account when root, but the inherited post-close scan_holders all still filters to processes owned by root. A descriptor passed to another uid after scope close could therefore escape the final refusal. Added a root fixture that pauses after disable, introduces a nobody-owned holder, and expects refusal plus D359 restoration. Its red run is pending the current quick slot; no production change for this boundary yet.
Quick pre-check reached clippy and failed on this branch’s wildcard import in takeover_terminals.rs; replaced it with explicit imports. Evidence: compute01 ~/j379-evidence/quick.log. No baseline claim for this author-owned red. Further transport finding: the production server backend has enrollment_origin=Some, so its initial check takes the ordinary 4096-byte command path rather than check_stream. Added a 40-terminal enrollment-origin regression. The queued short run will execute the final-holder red, release its live claim, then run this reader red in the same slot. Latest local checkpoint 0dc4535cf; screenshots and final gates remain pending.
Queue checkpoint: the short root-holder red plus enrollment-reader red has waited about 25 minutes and is now first in compute01’s queue (own waiter PID 4001485). No live claim held while waiting. Logs: /root/j379-holder-red.log; then /home/claude/j379-evidence/check-reader-red.log. The earlier complete seven-case root proof is /root/j379-root-final.log and copied to code:~/.cache/codex-scratch/j379/evidence/root-final.log. One remaining build target: /home/claude/worktrees/j379/target; /root/j379/target was removed after retaining the root-built helper at /root/j379-recovery-helper. Final quick/fast and browser evidence remain pending.
D368 real-root fixture evidence, seven original scenarios. The attached log is verbatim and ends with
OKandROOT_PROOF_EXIT=0.These use the shipping shim, real PTYs and exact system session scopes on compute01, from the root-owned /root/j379 clone and its root-built helper. The old service is a parked stand-in, and the new-install commit uses the existing synthetic installer fixture. This proves the root takeover/close and rollback paths, not live server enrollment. The later final-holder regression is still queued; final gates and screenshots are pending.
Two final boundary findings reproduced and fixed. Root post-close holder red: /root/j379-holder-red.log, expected 73 but actual 0, proving an other-uid fd holder escaped the inherited final scan. Added takeover-all for that scan only, preserving other cleanup modes. Enrollment-origin reader red: ~/j379-evidence/check-reader-red-unit.log, expected Some(40) terminal records but actual None. Green: ~/j379-evidence/check-reader-green-unit.log (1 passed). Both enrollment paths now use the existing bounded check reader; only early publication retains its staging channel, and ordinary command limits stay unchanged. The mock reader test ran under the common brief’s single-quick-unit exception (no extra slot). Rebased on origin/dev
8e664e6b7, including J376’s merged #432 helper fix. Full eight-case root rerun, focused green checks and quick are queued as a short run; the mesh claim is acquired only inside the slot and released before compilation. Latest local checkpoint 69ded4ca7.J379 verification status: the eight-scenario real-root rerun plus focused native checks and quick is queued on compute01 (own waiter PID 523484; /root/j379-root-eight.log). Eight queue entries are ahead as of this update; dev-02 is also busy. No live mesh claim is held while queued. Production browser confirmation checks are prepared on dev-02 and will follow without holding two slots. Current branch takeover-close-terminals-d368, head 69ded4ca7, base
8e664e6b7. No minipc01 access.Focused frontend record validation is green on dev-02: bun test test/provisioning-failure.test.ts reports 6 pass, 0 fail, 14 expect() calls. Artifact: /home/claude/j379-evidence/provisioning-unit.log (copied to control evidence directory). This was a single quick unit-test invocation, not a browser run or gate. The shared preview browser failed to open because the control host lacks browser libraries; production screenshots will use the required dev-02 Playwright runner. Compute01 root/quick run remains queued; no mesh claim is held.
Queue update: J379 has reached the front of the compute01 queue, but all four slots remain busy; own waiter 523484 has waited about 42 minutes. /root/j379-root-eight.log still contains only the launcher queue line. No root fixture, mesh claim, or own browser is running. Latest local changes are cargo-fmt output only in takeover_terminals.rs and takeover_check.rs; the feature head remains 69ded4ca7 until consolidation after verification. The final gate and four production screenshots remain outstanding.
D368 real-root proof is green on compute01: all eight scenarios passed with real shipping shim PTYs and systemd scopes. The old service is a parked fixture executable, and the new-install commit is synthetic; this does not claim live server enrollment. minipc01 remains untouched.
Cleanup was checked: temporary infraternal-agent.service and all ten exact fixture scopes report LoadState=not-found; J379 mesh claim is released.
The attached launcher log also records UNIT_EXIT=101: after the root proof, the separate native command omitted Tokio's rt-multi-thread feature and failed to compile existing multi-thread remote tests. The routing test (1 passed) and channel tests (2 passed) preceding it were green. This is an invocation correction, with no source change; quick did not start and remains outstanding.
J379 rebased cleanly to current origin/dev
753fcd6578; WIP head is now 1d98f1bf7f49fc71c811012a92d9f266e5ae1cb6. The real-root proof remains eight passed (attachment in previous comment). Corrected focused remote invocation --features agent,tokio/rt-multi-thread now passes its one outsider-endpoint test; artifact ~/j379-evidence/remote-unit-green.log. Required quick followed by fast is queued on compute01 with raw logs ~/j379-evidence/quick-gate.log and fast-gate.log; fast starts only after quick succeeds. The source snapshot and git metadata are synchronized. Browser source is updated on dev-02; browser run remains pending, with one-slot limit preserved.New consent finding before the final gates: a cached older tracer ignores the new terminal list but can still send the existing generic leftovers challenge. That generic confirmation must not authorize closing terminals the person did not see. I stopped only own queued gate waiter PID 1259876 (launcher exited 143) before changing source. Added a focused regression that attempts the generic challenge on a terminal-bearing offer; red run is ~/j379-evidence/terminal-consent-red.log. The fix will reuse the existing challenge mechanism with a terminal-close-specific challenge in the offer, while the generic challenge refuses; no new API action or foundational dependency is needed. Real-root shell proof is unchanged.
The cached-client consent regression is red/green. Before the fix, submitting the generic leftovers challenge on a 40-terminal offer resumed takeover (the regression failed). After the fix, that challenge refuses without consuming the pending observation, while the explicit close_challenge from the terminal-aware offer resumes it.
The frontend record tests remain 6 pass / 0 fail and now reject a terminal list without a valid nonzero close_challenge. The production browser fixture now exposes only the terminal-specific challenge as valid, so the browser journey will also detect a forgotten UI override.
WIP head a42e084b8 on base
753fcd657. Quick and fast have been requeued on compute01 after synchronizing the source and git metadata. Root shell behavior is unchanged; its eight-case proof and cleanup evidence remain valid. No mesh claim is held.Quick reached the slot and stopped on an author-owned Rust 1.99 lint: chunks_exact_to_as_chunks at previous_attempt.rs:253. Earlier rows (scope, migration names, Nix vendoring, fmt, doc ratchet, shape lints, diff docs, fixed waits) passed; fast did not start. Raw artifact: ~/j379-evidence/quick-gate.log, copied as control evidence/quick-chunks-red.log. Replaced chunks_exact(2) with as_chunks::<2>().0.iter(), retaining the existing even-length check. Also added the missing root-fixture lifecycle docstrings. WIP head 61e722356, source and metadata synchronized for the rerun. This is our lint failure, not an unrelated red.
Upstream #421 has merged. J379 rebased cleanly onto current origin/dev
0a5432d2c1; WIP head ea9ca9ec331413b85f7d4746fde00f62db1378de. The queued gate source and metadata were updated before the slot was granted (launcher still reports waiting). Own gate waiter PID 1944385; logs ~/j379-evidence/gate-launcher.log, quick-gate.log, fast-gate.log. No live mesh claim is held. This base includes both the J376 install-suite fix and the #421 path fix.Quick is green on compute01 at ea9ca9ec3, base
0a5432d2c. It passed all listed rows: scope guard, migration names, Nix vendoring, fmt, documentation ratchet, shape lints, diff docs, fixed waits, workspace clippy, changed-package unit tests, UI literals, and tracer typecheck.The attached file is the raw quick output. scripts/verify fast has now started in the same target, through the same build slot; its raw output is ~/j379-evidence/fast-gate.log. Browser checks remain pending on dev-02, with no second slot held.
First fast run reported two server test failures. The D359 rollback test at bootstrap_takeover_tests.rs:192 still asserted that the reviewed record appeared in argv; D368 intentionally moved it to stdin for long lists. Local WIP 099-placeholder replaces that obsolete assertion with exact stdin capture and also exercises a terminal-bearing offer with generic-consent refusal and explicit close consent against the real enrollment backend. Compute01 running source remains ea9ca9ec3 until this run completes. The other failure is breakit2_concurrent_same_destination_issues_only_one_invitation (2 identities versus expected 1); it is not yet classified as unrelated. I will prove it on unmodified dev before doing so. Full fast continues through remaining rows; artifact ~/j379-evidence/fast-gate.log.
Correction to the previous progress comment: the new test-only WIP is 3676201578b27741347ed275cf041cafbac06aa6 (the placeholder was accidental). The active compute01 gate remains on ea9ca9ec331413b85f7d4746fde00f62db1378de, based on unmodified dev
0a5432d2c1. Quick passed. Fast has exposed the obsolete argv-binding assertion, now updated locally to assert the exact stdin record and explicit close challenge, plus a concurrent destination reservation assertion whose baseline status still needs proof. No minipc01 access.First full fast result on ea9ca9ec331413b85f7d4746fde00f62db1378de: exit 1. Every Rust gate row passed except workspace tests (959 server tests passed, two failed). Both static builds and privileged integration passed. One failure is the obsolete argv-binding assertion, updated in 367620157 to cover explicit terminal consent and the exact stdin record. The concurrent-destination reservation assertion still needs unmodified-dev proof; it is not classified as unrelated yet. Full raw output attached. Browser evidence is next on dev-02; current origin/dev is still
0a5432d2c.Production browser checks are queued on dev-02 through the required launcher (J379 launcher PID 617746, ~/j379-evidence/browser-green.log). The launcher reports queue position 8; --status currently shows slot 1 held and slots 2/3 free behind older FIFO waiters. I am preserving queue fairness and the one-held-slot rule. No live-mesh claim is held. The next steps remain the browser evidence, baseline reservation proof, updated recovery test, then final quick/fast and atomic commit.
Browser queue did not advance: dev-02 status still shows the long-running slot-1 holder with free slots 2/3 behind the older FIFO waiter. I canceled only my queued PID 617746, without touching any other job. Baseline comparison is now queued on compute01 as PID 3372934 (~/j379-evidence/reservation-baseline.log). It checks unmodified
0a5432d2c, then restores 367620157 and runs the updated recovery regression. Browser evidence will be retried afterwards.Baseline proof: the concurrent destination assertion also fails on unmodified dev
0a5432d2c1. Withcargo test --locked -p infraternal-server --features remote --lib breakit2_concurrent_same_destination_issues_only_one_invitation -- --nocapture, attempts 1–3 selected and passed one test; attempt 4 selected one test and failed at bootstrap_remote.rs:3277 with the same assertion and values:left: 2,right: 1. Exit 101. Attached corrected-run output includes the exact base SHA. An earlier invocation omitted--features remoteand selected zero tests; it provides no proof and is excluded from this artifact. The same slot is now running the updated recovery regression on restored feature head 367620157.Updated server recovery regression passed on 3676201578b27741347ed275cf041cafbac06aa6 with
--features remote: exactly one test selected, one passed. It verifies the listed root/bash terminal, refusal of generic cleanup consent, acceptance of the terminal-close challenge, the exact reviewed stdin record, and the existing rollback token/restoration path. Output attached. Final quick/fast is queued on compute01 (PID 3538869, ~/j379-evidence/gate-launcher.log); no other J379 slot is held or queued.Browser blocker needing orchestrator inspection: dev-02's launcher FIFO has not advanced for roughly 20 minutes of observation. Read-only status shows slot 1 held by PID 421828 (the command begins
/usr/bin/python3 -c import sys; sys.stdout.buffer.write(b'INFRATERNAL_FUZZ_SLOT_), slots 2 and 3 free, and older waiter PID 471771 still first. I canceled my own queued browser PID 617746 and have not signaled or modified any other job. Screenshots remain outstanding and will be retried after this gate; please inspect the queue/holder through the orchestration process.Final quick pre-check passed on 3676201578b27741347ed275cf041cafbac06aa6, current dev base
0a5432d2c1: QUICK_EXIT=0. Full raw output attached. Fast is now running on compute01 in the same target. Dev-02's queue has advanced and slots 2/3 now run work; browser evidence will be retried after this slot releases, keeping the one-held-slot rule.Final required gate is green on compute01, feature snapshot 3676201578b27741347ed275cf041cafbac06aa6 (base
0a5432d2c1):QUICK_EXIT=0,FAST_EXIT=0. Full raw fast output attached; quick is attached in the previous comment. The server suite passed 961 tests with 0 failed and 57 ignored, including both the updated recovery regression and the previously reproduced baseline reservation test. Both static images passed: x86_64 28,213,072 bytes; aarch64 25,340,128 bytes. No baseline-red exception is needed for this final result.Production browser checks are queued again on dev-02 as PID 842618 (~/j379-evidence/browser-green.log), now that its FIFO is moving. Four required confirmation PNGs and their 2× inspection remain outstanding. Source is unchanged; I will consolidate the WIP history into the atomic feature commit after those checks.
J379 implementation finished on local branch
takeover-close-terminals-d368, atomic headee89ae90a3b1eb8b22f691c0029a7f69b3f4172a, based on dev0a5432d2c1347c57bf1a6c57e368877599d57587(D368, #404). No push, merge or deploy.The confirmation is a step in the existing Add host sheet. It says “1 open terminal. Take over closes it.”, lists “root, bash, since 17 Oct 2026”, and offers “Close it and take over”. Back returns to the refusal without sending consent. The scanner authenticates and binds every terminal record, then rechecks immediately before closing exactly those scopes. Other holders still refuse. D359 restoration preserves the install and service after a failure; the warning explains that closed terminals stay closed. Generic cleanup consent cannot close terminals, including from an older cached client.
Evidence:
scripts/verify quick: exit 0, raw output.scripts/verify faston compute01: exit 0, full raw output. Both static images pass: x86_64 28,213,072 bytes, aarch64 25,340,128 bytes. The gate reports deltas +5,794,816 and +4,886,848 bytes against its stored size baselines; these are not isolated feature deltas.The final commit changes history only from verified WIP
3676201578b27741347ed275cf041cafbac06aa6: both have tree1270052a4c04307d894bc163e18b40d61eb7861b, andgit diff 367620157 HEADis empty. Full evidence remains under/home/kayg/.cache/codex-scratch/j379/evidenceand~/j379-evidenceon the VMs. minipc01 was never accessed or changed.Verbatim Rust summary from
scripts/verify fast:Verbatim final frontend test block:
Launcher result, verbatim:
Cleanup complete for final head
ee89ae90a3. The owned compute01, dev-02 and root targets are absent. No mesh claim is held and the process sweep found no J379 launchers, servers or browsers. Evidence is preserved; cleanup log attached. Local worktree is clean. No push, merge or deploy; minipc01 untouched. The final gate summary, raw output links and four production screenshots are in the finish comment above. Issue remains open for owner review.J402 review start (independent correctness review of D368).
takeover-close-terminals-d368@ee89ae90a3b1eb8b22f691c0029a7f69b3f4172a(base0a5432d2c).~/Developer/Worktrees/infraternal-j402on code (detached at the target), scratch merge with origin/dev44e80ccc5for gates.J402 finding P1: Take over is still refused for a terminal opened by the current product. D368 covers only the legacy scope layout.
Real e2e on compute01, on a disposable overlay of the J311b AlmaLinux image (
/root/j402-vm/s1.qcow2; SELinux set to permissive at runtime on the overlay only, because #401's fix isn't on this branch). Both servers and the payload are built from the scratch mergefc57d1175=ee89ae90a+ origin/dev44e80ccc5.127.0.0.1:47402) addedroot@guestand opened a terminal (echo j402-s1-a1-19129 rootlanded in the guest file).:47406) ran Add host on the same guest. The result was not a Take over offer. The row shows"127.0.0.1 is set up elsewhere", fix"Install from the host", and server B logsfailure_kind="unrecognized-install" step="connect".Cause: the terminal that A's agent opened is a host-login terminal. Its shim (
/var/lib/infraternal/bin/live __shim --session be7f7cec…) runs in logind's/user.slice/user-0.slice/session-8.scope, beside theinfraternal-login-d75bed57…-0.serviceworker that runs-bash. Noinfraternal-session-*.scopeunit exists at all.takeover_terminals::scope()(takeover_terminals.rs:89–110) requires aninfraternal-session-<id>.scopepath component, so the helper refuses the whole offer. Running the shipped helper directly on the guest:In code:
daemon.rs:576sends every person-opened terminal throughsessions::launch_with_login.Manager::for_shim(…, login=true)givesDetached(sessions.rs:120), sosystemd-run --scope --unit infraternal-session-<id>is never used, and the shim is placed in the login's cgroup. Only the pre-host-login layout (minipc01's 17 Octinfraternal-session-d2f8649c….scope) matches. J379's root proof built its owninfraternal-session-*.scopeunits around a real shim, so it never went through this placement.Effect: minipc01 may well pass, but any install from a current build that has one terminal open stays "busy" with no way forward. That is the exact situation D368 was meant to end. Closing it needs a design for login terminals: authenticate the shim, then end exactly its logind session or the login worker unit, and keep refusing other members of that logind session. This is the kind of redesign PROCESS asks for before another patch round.
Evidence on compute01 under
~/.cache/codex-scratch/j402/evidence/s1/:run.txt,snap-after-A-stop.txt,helper-manual.txt,server-B.log,flow-B-takeover-b1.log. Guest snapshot excerpt attached.J402 finding P2: the busy refusal says things that aren't true when it appears. (TASTE "Messages say what happens, in plain words")
Real e2e rerun on a fresh overlay (s1c), same builds as the P1 above. Server B's Add host ends on the attached refusal (production web build, 1440×900 and 393×852):
TAKEOVER_BUSY(previous_attempt.rs:394) is used by every 73. That covers the read-only check, the 73 fromholders_beside_agentafter the agent stops but before any scope stops, and the post-close scan. Only that last case has closed terminals. The sentence about closed terminals belongs only on the post-close refusal.exit 75intake_over_finished_installhappens before$manager stop: the pre-stop record comparison and the post-agent-stop recomputation. So a 75 has never closed a terminal, yet the message says one stayed closed.Suggested fix: two messages for 73. Before any close, say what is in use and that nothing changed. After the close, say the terminals are closed and the install was put back. Drop the closed-terminals sentence from 75 entirely.
J402 progress. These are gate results on the scratch merge
fc57d1175, which isee89ae90amerged with origin/dev44e80ccc5. Quick's base was the local origin/dev6b60f1fb7.Every Rust gate row passed, and the TypeScript protocol tests show 50 pass, 0 fail. Logs: compute01
~/.cache/codex-scratch/j402/evidence/{quick,fast}.log.Next: a real e2e on the layout D368 targets. Server A comes from dev
432f2e788, the last dev commit before host-login terminals (947ce2870, 2026-11-01), so its terminals live ininfraternal-session-<id>.scopeas on minipc01. Server B comes from the branch merge. That legacy build is queued on compute01 at position 8.J402 review: REQUEST CHANGES
Reviewed SHA
ee89ae90a3b1eb8b22f691c0029a7f69b3f4172a. Gates and e2e ran on the scratch mergefc57d1175, which isee89ae90amerged with origin/dev44e80ccc5with no conflicts.Findings
daemon.rs:576→launch_with_login,Manager::for_shim→Detached). Its shim ends up in logind'ssession-N.scope, notinfraternal-session-<id>.scope.takeover_terminals::scope()therefore exits 73 and Take over stays "busy". This was reproduced for real twice (s1, s1c): the refusal appears with no offer. Details are in comment 88088. minipc01's pre-11-01infraternal-session-*scope may still pass. J379's root proof built its own scopes, so it never went through the product's placement. A fix needs a design for login terminals, not another patch.TAKEOVER_BUSYsays "terminals you confirmed closed stay closed" even on the check-time refusal, and it calls the person's own shell "a program … outside the terminal sessions it can safely identify". Everyexit 75happens before$manager stop, yet its message says closed terminals stay closed. Seen for real in s1c and s5-hybrid. Details are in comment 88100.nohupprocesses in the same scope are stopped without being listed. The fingerprint binds them, so this is a display gap, not a race.What holds (checked in code, and for real where the table says so)
close_challengeis separate from the generic leftovers challenge, which is set to nil for terminal offers. A terminal opened between offer and confirm is refused with 75, and nothing is closed (real run, s5-hybrid).sessionsdir, its Hello session id matches, and its cgroup path contains that exact scope name. The stop uses that canonical id. A same-named scope under another manager makes the stop fail, which leads to 73 and the install being put back.takeover/takeover-all). I checked this in code only. J379's root fixture covers it at runtime.put_back_taken_over. I checked this in code only. J379's root fixture covers it at runtime.Real e2e (compute01, J311b AlmaLinux overlay per run, SELinux set permissive at runtime on the overlay, servers on loopback)
"Hybrid" means: server A (branch build) finished its install and its host-login terminal was closed by hand. Then terminals were started the way the pre-host-login agent started them (
systemd-run --scope --unit infraternal-session-<id> /var/lib/infraternal/bin/live __shim …). A was stopped for good, and B is the real branch server driven through the browser. A real legacy server A from dev432f2e788(before host login) was tried. Its install fails on this guest (install step exits 73,server-A.log), so it can't produce a minipc01-shaped install.session-8.scope)42bf…→a5e9…432f2e788)Screenshots (production web build, attached): offer and confirmation at 1440×900 and 393×852 (s1-hybrid), the s5 refusal, and the 41-terminal confirmation at 393×852. Raw logs and snapshots are on code under
~/.cache/codex-scratch/j402/evidence/and on compute01 under~/.cache/codex-scratch/j402/evidence/.Gates (scratch merge
fc57d1175, compute01)All 24 Rust gate rows passed (table attached). Cleanup: guest overlays, servers, leftover shims, the 54G target and both worktrees are removed. No mesh claim was taken. minipc01 was not touched. Nothing was merged or deployed.
J379c start (Claude Opus 5.5, successor to J379 after Codex hit its limit)
Branch
takeover-close-terminals-d368, baseee89ae90a, merged origin/dev411b49aa8(brings the SELinux fix6b60f1fb7).Plan (est. ~5 h, mostly build-slot queue and the guest e2e):
infraternal-session-<id>.scope, and host-loginsession-N.scope), plus server-side record parser tests for the new fields.sessions/. The close boundary is either the old scope, or the logind session scope whose LEADER (from/run/systemd/sessions/N) is the infraternal login worker (exe in the root-owned login image dir,__shim --login-worker, started by the system manager, same scope). Every member of a login scope must descend from that worker or from one of this install's authenticated shims, and every terminal tty in it must map to one of those shims (via ptmxtty-index). Otherwise 73. An ssh or console login (leader sshd or login) never qualifies. Closing stops the reviewed scope units (session-N.scopeorinfraternal-session-<id>.scope).verify quick, thenverify faston compute01, quoted.Overlap between #305 (D337, J205c) and #433 (D368, J379c). Posted on both issues.
I compared
takeover-proof-305-j205(8d57c453c) withtakeover-close-terminals-d368(ee89ae90a) usinggit merge-tree. They conflict in 3 files:crates/plugins/remote/src/runtime_lock.rsLEVEL8 = the issuer pin inenrolledLEVEL8 =takeover-terminalsrecordscrates/plugins/ssh/src/bootstrap.rsPendingFresh { challenge, takeover, resume }, wheretakeoveris the D337 passkey-ticket proofPendingFresh { challenge, resume }, withchallengereplaced by the terminal-close confirmationapps/tracer/src/canvas/addhost/wizard.tsstartFresh(job, takeover?: TakeoverTicket)startFresh(job, confirmation?: string)Both also edit
previous_attempt.{rs,sh},install.rs,model.rsandprevious_attempt_shell.py. Those merge cleanly, but in the same area: the finished-install takeover set up by J315/D359.The scope overlap is real, but small. Both decisions handle "a finished install from another server still has open terminals".
Proposed split:
takeover-proof, the passkey ticket, the audit and session inheritance. #433 owns the close-after-confirmation path and itstakeover-terminalshelper mode.start-fresh: the second branch keeps both fields,PendingFresh { challenge, takeover, resume }, with D368's confirmation feedingchallenge. On the client it becomesstartFresh(job, { ticket?, confirmation? }). That's about 20 lines.session-N.scope. D337 counts terminals by their session sockets (sessions-count), not by scope, so it should not hit that P1. I'm proving that now on the guest rig with a host-login terminal, and I'll post the result here.#305 does not change D368's files beyond these three hunks, and I won't touch
takeover-close-terminals-d368.J379c progress (pushed
4ec183d4fontakeover-close-terminals-d368)Commits on top of the dev merge
e30c3ccf8(origin/dev411b49aa8):10c324830test: host-login terminals accepted, rows told apart (red: does not compile against the old helper or parser)caf2bb4e8P1: terminals identified by their shim. The close boundary is the oldinfraternal-session-<id>.scope, or the logindsession-N.scopewhose leader (from/run/systemd/sessions/N) is the infraternal login worker (pid-1 child,__shim --login-worker, root-owned image in/var/cache/infraternal-login-bin). Every process in a login session must map, through its PTY (the shim's ptmxtty-index), to one of this install's shims, otherwise 73. The record carries the close unit, the pts name and the count of other programs.33c4a375c/955ec2448P2: three truthful texts (busy before any close, busy after closing as new host status 74, changed with nothing closed).4ec183d4fP3: rows read "root, bash on pts/3 and 2 other programs, since 9 Nov 2026, 14:05".Running now:
verify quickqueued on dev-02 (~/.cache/codex-scratch/j379c/quick.log). The e2e build is queued on compute01 (position ~15,~/.cache/codex-scratch/j379c/build.log). The guest rig is/root/j379c-start-guest.sh(port 22379, SELinux enforcing, since dev has #401). Scenarios: L1 (product terminal plus background job → offer, Back, confirm, B terminal), L5 (second product terminal opened between offer and confirm → refusal), O1 (old-layout scope). Target dir: compute01~/.cache/codex-scratch/j379c/target.J205c (#305) follow-up to the overlap note above. Details and evidence are in #305 comment 88495.
StuckInstall::Reenroll if previous.in_use_elsewhere(),bootstrap.rs~1371). A finished install never reaches it. So #305 and #433 cover different host states, and the remaining overlap is only the 3 merge hunks listed above (helper level,start-freshfields,startFresh).session-7.scope,infraternal-login-…-0.service) is refused by B as "set up elsewhere" (unrecognized-install). That run islive1on #305; it used #305's branch, which doesn't change that path.kind=upgradejournals) nor D368 (only finished installs) handles it. A design is proposed on #305. If D368's "close and take over" should cover it instead of D337's "keep", that's the orchestrator's call.J205c → J379c:
legacy1, an e2e case for the shim-based terminal identity (D368)The orchestrator asked that #305's
legacy1repro go to #433. Here is what it holds and the exact path that refuses it.State. A real crashed first root install with a surviving terminal. AlmaLinux 10.2, SELinux enforcing.
/var/lib/infraternal/.bootstrap-lock:kind=fresh, withactivating,creating-alias,replacing-{binary,launcher,state,unit},stopping,transactionandtransfer-progress. Theleaseowner is dead (lease_alive=no).state/identity.jsonis enrolled with server A. A is gone for good, but its archive is kept:~/.cache/codex-scratch/j205c/archive-legacy1-A.tgzon compute01.infraternal-agent.serviceis active.5384 /var/lib/infraternal/bin/live __shim --session fe710269164440088a7513c35c6b41d8 --uid 0 --cols 80 --rows 24 --state /var/lib/infraternal/sessions. It runs in the old layout scopeinfraternal-session-fe710269….scope, with-bash(5386) under it, and servessessions/sessions/fe710269….sock. It was started with the pre-11-02 early-terminal command,exec <payload> __ssh-session --state $base/sessions(install.rs:704). Current servers never take that path becauseenrollment_origin()is alwaysSome. It survives losing its SSH channel (snap-legacy-ssh-gone.txt).Exit path (branch
8d57c453c,previous_attempt.shunchanged from devb68edbfff):inspect_previous: no receipt andkind=fresh, so it skipsinspect_upgrade.not_live(:432):flock -n 8on the journal succeeds, because the lease is dead.activating+fresh→not_live_scan=unit, andagent_main_pidis skipped.live_session_sockets(:446) → helper--bootstrap-runtime $base <dev:ino> sessionsonfe710269….sock→runtime_lock.rs:608Err(73).scan_holders unit(~:573) would also exit 73 on the shim'sexe→bin/live.classify(73)→SetupRunning: "Setup is already running on this host. Wait for it to finish, then try again." It refuses forever, and nothing is changed. That's #305's original report.This is not the setup guard's flock, so #439 (J395b,
flock -w 5) doesn't change it. With D368's identity (exe = the install's verified binary, socket under$base/sessions), this shim qualifies as the install's own terminal in the old-layout scope. What's left is routing akind=freshstuck journal into D368's "close and take over", where today it goes to the J257not_liverefusal.Using the overlay.
/root/j205c-vm/legacy1.qcow2on compute01 is read-only, so boot a child overlay on top of it (qemu-img create -f qcow2 -F qcow2 -b /root/j205c-vm/legacy1.qcow2 <run>.qcow2), and copy it if you want it kept independently. Its backing file is/root/j311b-vm/disk.qcow2. The disk does not keep processes, so after boot the shim is gone. Recreate it the way the runner does: hold an SSH channel open toexec /var/lib/infraternal/bin/live __ssh-session --state /var/lib/infraternal/sessions, then drop it. See theJ_LEGACY_SHELLblock in~/.cache/codex-scratch/j205c/e2e-stuck.sh. Re-run the whole repro from scratch withJ_CRASH_ON_AGENT=1 J_LEGACY_SHELL=1 bash e2e-stuck.sh <name>after/root/j205c-start-guest.sh <name>(port 22205). The crash watcher iswatch.sh. Evidence: the tarball on #305 comment 88495.J379c finding (L1 e2e, real product terminal): systemd's
(sd-pam)sits in the login's session scope. Fixed inc72d39905.The first real run (
4ec183d4f, guest overlay of J311b, SELinux enforcing) went like this: server A added root, opened a product terminal and ransleep 100000 &. The helper on the guest still exited 73. The real host-login layout,session-7.scope(/run/systemd/sessions/7:LEADER=2632,SCOPE=session-7.scope,UID=0):Everything matched the design except
(sd-pam), whichPAMName=leaves as a tty-less child of the worker. The helper now counts it as part of the login, but only when it is a direct child of the verified worker, has no tty, and is named(sd-pam). Anything on a terminal, or below one, still counts as that terminal's, whatever it calls itself. A red test was committed first (4e38c31a1). Evidence: compute01~/.cache/codex-scratch/j379c/evidence/L1/.J205c legacy1 (comment 88519): implemented, e2e pending.
537a3a02b(red) →d1bc84d80→7f5574233. For an activated fresh journal,not_livenow asks the helper for terminal records. When every live endpoint is this install's own shim, the STALE line carries them, and Start fresh lists them (UI: "Close it and start fresh") under a terminal-only challenge. That Start fresh never runs automatically. On confirm it re-checks the records after the activated agent stops (75 if they changed, nothing closed), then stops their units. A 73 after that becomes 74. Endpoints the helper can't attribute still refuse, as before. Runner:e2e-legacy.shon a child overlay of/root/j205c-vm/legacy1.qcow2.J379c e2e: both layouts, the current product, and J205c's legacy1. Final build
419862508(heade36832b31differs only in a test file). compute01 guest = a per-run overlay of the J311b AlmaLinux 10.2 image, SELinux enforcing (dev has #401), servers on loopback, real browser flows against a production web build.session-7.scope), plussleep 100000 &97ca56b9…→48838493…, old login unit gonepts/1, same login)1c72b36f…keptinfraternal-session-<id>.scopefrom the installed binarydf636a78…→a1583779…kind=fresh, activating)__ssh-sessionas on the field hosta8bafd22…→cb2820a0…How it got there (each a red then a fix, all on the branch):
(sd-pam)sits in the login scope (4e38c31a1/c72d39905).f0e578dd1/7376a61ce).eeb3b7efd).9bed4eda1).db68f6e2espec /419862508).Raw evidence on compute01:
~/.cache/codex-scratch/j379c/evidence/{L1,L5,O1,LG1}/(run.txt, guest snapshots with every boundary's processes, the helper's records, server and flow logs, screenshots). Failed intermediate runs are kept beside them (*-run*). Screenshots attached below.J379c progress: gate status
verify fast(compute01,e36832b31):FAST_EXIT=1. All rows passed except two:test:a_root_add_offers_take_over_and_a_failed_install_puts_the_old_one_backstill built the five-field terminal record. Fixed in259ee884d; with--features remote,sshthe 5 takeover tests pass.cargo-deny: RUSTSEC-2026-0459 (libsqlite3-sys). I didn't touchCargo.lockordeny.toml. Dev handled it after my merge base with a time-limited ignore (5acf28773, #473);cargo deny checkon origin/dev198c4a736passes.+102400 bytes accepted with delta.198c4a736→a93ba2779. One conflict,addhost-wizard.spec.ts(dev'sresetDouble+ this branch'sthemeparameter).verify quickate36832b31passed earlier. So did the touched bun tests (28 pass) and 7 wizard specs, including the new "leftovers with a terminal".verify faston compute01 (position 12) ata93ba2779; on dev-02, a merged x86 build, then quick and the specs. L1 and LG1 get rerun on the merged build.~/infraternalrefs/remotes/origin/devpointed at my mergea93ba2779, because of a badupdate-ref. It's restored to198c4a736, which matches the remote. Nothing else changed.J379c finish. Head
a93ba2779e6cbe8ccb359dc03b67f42c86a42fefontakeover-close-terminals-d368, pushed and confirmed withgit ls-remote. Not merged, not deployed; minipc01 not touched.Done
sessions/must have a root peer whoseHellomatches and whose exe is under the canonical base. The close boundary is either the oldinfraternal-session-<id>.scope, or a host login's logindsession-N.scope. A login scope counts only when logind's record names our login worker as the leader (pid-1 child,__shim --login-worker, root-owned image), never an ssh or console login. Inside a login scope every process must belong to one of this install's terminals by PTY, except systemd's(sd-pam); otherwise 73. A shim's parent is kept out of the fingerprint, because the agent stop reparents it. The helper level moves to 9.8fb355b9…→366b12b0…, B terminal echo) and LG1 PASS (legacy1, ownera8bafd22…→55512785…).e2e summary (all on a J311b overlay, SELinux enforcing; final build
419862508, L1 and LG1 also on mergeda93ba2779)session-N.scope)infraternal-session-<id>.scopekind=freshinstallGates
verify quick(dev-02,a93ba2779):QUICK_EXIT=0. Touched bun tests: 28 pass, 0 fail. Wizard specs (-g "Take over lists terminals|leftovers"): 7 passed.verify fast(compute01), verbatim:The only failure,
startup-toast.test.ts("a closing notice cannot hide its replacement"), is pre-existing on dev. It fails alone on unmodified origin/dev198c4a736(the merged base) and on the latest dev547a48885, and this branch doesn't touch that file orpackages/uitoast code. The firstfastrun (e36832b31) found one real stale fixture of mine (fixed in259ee884d) and the RUSTSEC-2026-0459 cargo-deny failure that dev fixed after my base. The static rows report the gate's stored baseline (+5,954,216 B x86, report-only); I did not measure a separate branch-vs-dev size delta.Not run: the root fixture
takeover_terminals_root.py(it needs the mesh claim). O1 and LG1 prove the old layout on real guests instead.Cleanup: guests stopped and overlays removed, compute01 and dev-02 targets deleted, no processes left. Evidence: compute01
~/.cache/codex-scratch/j379c/evidence/.J-review (D368 security review) start. Reviewing
takeover-close-terminals-d368ata93ba2779e6cbe8ccb359dc03b67f42c86a42fef(base origin/dev). Independent reviewer, Claude Opus 5.5. Plan (~2 h): readtakeover_terminals.rs+ the take-over / Start-fresh shell paths, then attack on a compute01 J311b guest overlay as root: false matches (ssh/console/tmux/screen/other install, socket spoofing, look-alike base, PID reuse, fake logind leader), TOCTOU (75), the(sd-pam)exception, partial close (74), legacy1 Start fresh. Then the takeover unit/integration tests and, if the mesh claim is free,takeover_terminals_root.py. Nothing merged, pushed or deployed; minipc01 untouched.D368 security review: REQUEST CHANGES
Reviewed SHA
a93ba2779e6cbe8ccb359dc03b67f42c86a42fef(takeover-close-terminals-d368). Independent reviewer (Claude Opus 5.5). Nothing was merged, pushed or deployed. minipc01 was not touched. No mesh claim was taken: every root step ran inside disposable J311b AlmaLinux overlays on compute01 (/root/j379c-start-guest.sh, SELinux enforcing unless noted). J379c'sa93ba2779build and flow drove a real two-server host-login install.The design holds against every false-match vector I could build. One exception still lets an unlisted process be closed: the
(sd-pam)match is by name.P2-1. The
(sd-pam)exception matches bycomm, so a terminal's own daemon can hide from the confirmation and is closed unlistedtakeover_terminals.rsattribute()→pam_helperaccepts any member whoseppid == leader,tty == 0andprogram == "(sd-pam)". The login worker is a child subreaper (infraternal-shim/src/login.rs:195), so every detached descendant of a terminal is reparented to it. A binary file named(sd-pam)gives exactly that comm.Repro (real product install from server A, host-login
session-45.scope; typed into the real terminal on/dev/pts/0):Server B then offered:
"root, bash on pts/0 and 1 other program". The one "other" is thesleep 100000 &job, so the spoof isn't counted. Confirming closedsession-45.scope, and/tmp/(sd-pam)died with it, never listed. That breaks D368's "what Take over closes is exactly what the confirmation lists". It also defeats the refusal (73) that exists precisely for tty-less processes the person can't see. Blast radius: only processes already inside the reviewed login scope, i.e. that account's own work, never another login's.Fix: match the real helper by identity, not by name. The real one is
exe=/usr/lib/systemd/systemd-executor(older systemd:/usr/lib/systemd/systemd), a root-owned file that isn't writable by others, and it starts with the login, before any shim. For example: requireexeto be the system manager's own executor (compare dev:ino with/proc/1/exe's sibling, or a root-owned path under/usr/lib/systemd/), birth ≤ the leader's first shim's birth, and at most one such helper. Add a unit test where a member named(sd-pam)with another exe is refused.P3-1. 74 after a failed unit stop says the wrong thing
Repro: one install with a host-login terminal (
session-54.scope) and an old-layout terminal (infraternal-session-c8a8….scope). I setRefuseManualStop=yeson the second via a/rundrop-in. Server C confirmed "Close them and take over". The host exited 74:session-54closed, the second stop refused. State afterwards is consistent: the old install is put back (ownera445c626…kept),infraternal-agent.serviceenabled and active, terminal 2 still running. The text (attached8-refusal-393x852.png) says: "Take over closed the terminals you confirmed, then found something else still using the old infraternal agent, such as a program that runs from its folder…". Neither claim is true here: one confirmed terminal is still open, and the cause was a unit that wouldn't stop. Suggest a separate status for "a close failed" (or neutral wording: "some of the terminals you confirmed were closed"), so the person isn't sent looking for a program that doesn't exist.P3-2. The root fixture
takeover_terminals_root.pyis stale and can't run as writtenI ran it inside a fresh guest overlay with a static x86
recovery-helperbuilt froma93ba2779, using the script's own runner (python3 takeover_terminals_root.py <tests>). Before it would run at all:/home/claude/mesh-vm.claimstarts withJ379, so nobody else can run it without forging that text. I wrote a guest-local file.python3 -m unittest takeover_terminals_rootalso runs every inheritedEarlierSetuptest against the real system manager, and they all fail. Only the__main__runner is valid; say so in the module doc./root(203/EXEC, admin_home_t). I set the guest permissive for this run only.Results (8 tests): 5 pass: new terminal between offer and confirm (75), changed record, foreground program changed, terminal opened during the stop, non-terminal holder (73). 3 fail:
test_other_account_holder_appearing_after_close_still_refusesandtest_failure_after_close_restores_the_old_install_and_serviceexpect 73, but the branch now (correctly) returns 74 once a terminal has been closed. Their restore assertions come after that line and never ran.test_offer_then_confirm_closes_only_the_reviewed_terminalfailed with 75 in 2 of 4 runs. When the offer is read while the new bash is still running its startup child (…,7074732f30,1,/,2,), the confirm sees a different set. The product's behaviour is safe (nothing closed), but the test races; wait untilothersis 0, or until the foreground is idle, before taking the record.Logs: compute01
~/.cache/codex-scratch/rv433/evidence/fixture-8tests.log,fixture-happy-x3.log.P3-3. A process's executable isn't bound into the fingerprint
Same pid, same comm, a different binary: a copy of bash at
/tmp/a/sleepexecs/tmp/b/sleepbetween two reads, and the record's fingerprint is byte-identical (…de4a21641dc0b3dd6de857f1both times). It stays inside the reviewed terminal, so the impact is low. Hashing theexedev:ino would close it cheaply, and it's the same identity P2-1 needs.P3-4 (hardening, code-only). The old-layout boundary trusts the scope name
boundary()acceptsinfraternal-session-<id>.scopeat any depth of the cgroup path, then the shell stops the system unit of that name.reviewed_terminal_processlikewise matches0::*/infraternal-session-*.scopeat any depth. If the shim sits in a nested cgroup of that name, the processes enumerated and the unit stopped can differ. Setting that up needs root, so this isn't exploitable by an account. Still: comparesystemctl show -p ControlGroup --value <unit>with the walked cgroup before stopping, or require the scope as a direct child ofsystem.slice.What holds (tried for real on the guest unless marked)
session-61.scope, leadersshd-session)/var/lib/infraternal-x/bin/live) serving a socket in the realsessions/dir, in its owninfraternal-session-*.scopestarts_with)sessions/sessionsreplaced by a symlinkkayg: socket squatting / writing the login-images dirPermission deniedon both (sessionsis root 0700, images dir root 0755, image 0555)session-Nscopessessions/start a boundarymydaemoncontrol above: 73LEADER=, which an account can't make exec the login image; exe dir +__shim --login-worker+ ppid 1 + membership are all requiredsu kayg -c sleepin the terminal, a setuid-root child, a new terminal (J379c L5, fixture)FreshMode::Confirm; the generic challenge is nil'd and the server compares challenges exactly (bootstrap.rs:1985); a terminal appearing after an automatic check changesinspect_previous→ 75. The row's quick link opens the list (wizard.ts:!failure.closes). I found no path that closes without the listtake_over()always goes throughawait_confirmationTests and gates (dev-02,
a93ba2779)cargo test -p infraternal-plugin-remote --features agent,tokio/rt-multi-thread,tokio/macros --lib -- takeover_terminals runtime_lock: 37 passed, 2 ignored (+1+1). All 11takeover_terminals::testsok. Without the tokio features the lib test target doesn't compile on its own; that's a feature-unification quirk, not this branch.--test takeover_check: 1 passed.--test install: 110 passed, 2 ignored. ssh libprevious_attempt: 10 passed.infraternal-server --features remote,ssh --lib -- takeover: 5 passed.VERIFY_QUICK_BASE=origin/dev scripts/verify quick:QUICK_EXIT=0("QUICK pre-check passed"). Pre-check only; I didn't rerunfast(reviewer rule).Evidence
compute01
~/.cache/codex-scratch/rv433/evidence/:P1/(spoof install, snapshots, B's offer/confirm screenshots and flow log),P74/(partial close, refusal screenshots), fixture logs. Setup:setup-P1.out,takeover-P1.out,takeover-P74.out. Screenshots of the 74 refusal are attached.Cleanup: guest overlays
rv433a/b/cstopped and deleted, servers stopped, server data dirs removed, dev-02 target and worktree deleted.Verdict: REQUEST CHANGES. P2-1 must be fixed: the owner asked that the
(sd-pam)match be by real identity, and today a name is enough. Also refresh the root fixture (P3-2) so it actually runs. P3-1, P3-3 and P3-4 can land with it or after.J379c round 2 (J462 review, comment 89347): fixes pushed,
verify fastrunning. Heade8e460e84.(sd-pam)by identity4c008e2d9(red) →f10c46278/tmp/(sd-pam)named copy, the executor started after the terminals, another uid, a second helper, and an empty executor list. Guest e2e SP (below).37ad6ed42(red) →77d193638e8e460e84/root, no existing unit.load_testscollects only its own 8 tests. On enforcing SELinux it skips with the 203/EXEC reason. It waits until the new shell's other-program count is 0 before reading the offer. 73→74 and 73→79 where the branch now answers that.f10c46278/proc/<pid>/exedev:ino is hashed. Unit: the same pid and name running another binary changes the digest.f10c46278/system.slice/infraternal-session-<id>.scope(with nesting below it), and the shell's holder-scan skip uses the same shape. Unit: the scope name underuser.slice, under another slice, or at the root is refused.What the real helper looks like (J311b guest, systemd 257, all four helpers present): exe
/usr/lib/systemd/systemd-executor(64516:8449214, root:root 0755, beside/proc/1/exe=/usr/lib/systemd/systemd); parent = the login worker; born 6 ticks after the worker and before any shim or shell; uid = the login's account (user@1001's helper runs as 1001, root logins as 0). The rule: the exe's dev:ino issystemd-executorbeside/proc/1/exe, or/proc/1/exeitself (older systemd), each root-owned and not group- or other-writable; it is a tty-less child of the worker with the worker's uid, named(sd-pam); it is born before every other process of the login except the worker; and it is the only such process.Guest proofs (build
e8e460e84, enforcing unless noted):cp /usr/bin/sleep '/tmp/(sd-pam)'; ( setsid '/tmp/(sd-pam)' 99992 … & ). The scope then held the real helper (2645 … exe=/usr/lib/systemd/systemd-executor) and the spoof (2674 ppid=2643 (sd-pam) exe=/tmp/(sd-pam)). Result:takeover_terminals_exit=73, and B was not offered Take over: "…something besides its own terminals is using it… No terminal was closed, and the old install was left as it was found…". Afterwards the spoof, the terminal and owner601b8aaf…were all still there.393e5af8…toe89a62b7…, and B's terminal echo landed.recovery-helperfrome8e460e84):python3 takeover_terminals_root.pygives Ran 8 tests … OK. That includes the 74 and 79 tests, whose restore assertions now run. The happy path passed 3 of 3 times.python3 -m unittest takeover_terminals_rootunder enforcing givesRan 8 tests … OK (skipped=8).verify quick(dev-02,e8e460e84, base198c4a736):QUICK_EXIT=0. All new tests ran and passed. The touched bun tests and the 7 wizard specs also pass.Evidence on compute01
~/.cache/codex-scratch/j379c/evidence/{SP,L1,FX}/. minipc01 untouched.J379c round 2 finish (J462 review, comment 89347). Head
e8e460e847a2d2b5b51fed1a6cbc82990536b60bontakeover-close-terminals-d368, pushed and confirmed withgit ls-remote. Not merged, not deployed; minipc01 not touched. The details and guest proofs are in comment 89398.Commits:
4c008e2d9(red) →f10c46278P2-1/P3-3/P3-4 ·37ad6ed42(red) →77d193638P3-1 ·e8e460e84P3-2 fixture.(sd-pam)is accepted only by identity. Its exe dev:ino must besystemd-executorbeside/proc/1/exe(or/proc/1/exe), root-owned and not group- or other-writable. It must be a tty-less child of the worker with the worker's uid, born before every other process of the login except the worker, and the only one. SP guest e2e: a detached/tmp/(sd-pam)started from a product terminal → 73, no offer, nothing closed. L1: the real helper is still accepted and Take over completes.Ran 8 tests … OKon a fresh guest (permissive); the happy path passed 3 of 3;unittestcollects only its 8 tests and skips them under enforcing.system.slice, in both the helper and the shell.Gates
verify quick(dev-02,e8e460e84, base198c4a736):QUICK_EXIT=0. All new unit tests ran and passed. Touched bun tests:BUN_EXIT=0. Wizard specs: 7 passed.verify fast(compute01,e8e460e84), second run, verbatim:Every Rust row passes. The one tracer failure,
startup-toast.test.ts"a closing notice cannot hide its replacement", is pre-existing on dev. It fails alone on unmodified origin/dev198c4a736(this branch's merge base) and547a48885. Dev fixed it after my base in4055a0590(#479); on the dev tipd7b2e4dc1it passes alone. This branch doesn't touch that file orpackages/ui.The first
fastrun at this head failed onlyinfraternal-agentsessions::tests::slow_hello_failure_still_closes_the_detached_session_it_started, withText file busy (os error 26)(an exec race under load). That crate is untouched by this branch; the test passed 3 of 3 alone, and the full rerun above passed thetestrow. Log:fast-3-etxtbsy.log.Cleanup: guests stopped and overlays deleted, compute01 and dev-02 targets removed, no processes left. Evidence: compute01
~/.cache/codex-scratch/j379c/evidence/{SP,L1,FX}/andfast.log.J474 fix-check (round 2) started. Reviewing
takeover-close-terminals-d368ate8e460e847a2d2b5b51fed1a6cbc82990536b60bagainst J462's review (89347) and the finish (89527). Merge with origin/devd7b2e4dc1= scratchb0bf93e83(branchj474-review-merge, deleted at the end; the reviewed branch is not pushed to).verify faston that merge is running on compute01 (~/.cache/codex-scratch/j474/evidence/fast.log). Plan: read the P2-1 identity check for exec, hard link, bind mount, PID reuse and namespace bypasses (≈45 min), then re-run guest proofs SP and L1 on a fresh J311b overlay on compute01 (≈60 min). minipc01 not touched.J474b fix-check started. Review target:
takeover-close-terminals-d368ate8e460e847a2d2b5b51fed1a6cbc82990536b60b; scratch merge base:origin/dev d7b2e4dc1(j474-merge b0bf93e83). Plan: review the privileged helper match and kill boundary (including exec, links/mounts, PID reuse and namespaces); verify and clean the prior J474 guest; reproduce SP and L1 on a fresh guest; runscripts/verify faston compute01. No push; minipc01 untouched. Estimate: about 2 hours plus build-slot wait.Guest cleanup before my rerun: compute01 still had J474's L1 QEMU guest (PID 3483325, forwarded port 22379) with open files
/root/j379c-vm/j474L1.qcow2andj474L1.console.log. I verified the open files against the live process, stopped it through a pidfd, removed only those J474 files and the matching pidfile, and left the J311b seed/base untouched. The guest process and J474 overlay are absent; no host-side processes reference the J474 scratch path. Two other QEMU guests use unrelated ports and have no J474 argv/open files, so I left them running. Receipt:~/.cache/codex-scratch/j474b/evidence/guest-cleanup.log.Update: after fetching, current
origin/devis4889dfaf16a95203882f745187bcb99368ca1a54, newer than J474's d7 scratch merge. I will run the gate on a fresh scratch merge of review heade8e460e847a2d2b5b51fed1a6cbc82990536b60bwith that current dev head. J474's old scratch worktree stays untouched; no push.J474b progress: the local and compute01 scratch merges both contain review head
e8e460e84plus currentorigin/dev4889dfaf16a95203882f745187bcb99368ca1a54(merge tree9a88ba24e). I verified the prior J474 QEMU is stopped and its overlay is absent; J311b base/seed remain intact.scripts/verify quickhas acquired a compute01 build slot and is still compiling; raw log:~/.cache/codex-scratch/j474b/evidence/quick.log.scripts/verify fastfollows in the same slot. Next: fresh J311b-derived guest runs for SP and L1, then verdict. No push; minipc01 untouched.J474b progress (review tree 1073bcd2, target
e8e460e84+ origin/dev4889dfaf). The prior J474 guest is stopped and its overlay is absent; J311b base/seed remain intact. Compute01 fast is still running in its existing slot; I am not starting another gate.Findings from the merged source:
takeover_terminals.rs:405-422accepts the unique executor candidate when its/proc/PID/statstart tick equals the earliest member tick. It does not require a unique earliest member. On compute01, CLK_TCK=100 (10 ms/tick). A same-account process can execute the accepted systemd inode, setcomm=(sd-pam), and be adopted as a direct child by the login worker's subreaper. If the genuine helper is gone and the spoof ties the earliest remaining member's tick, it is the sole candidate and is omitted from the terminal row. The current unit test attakeover_terminals_tests.rs:304-350covers a different executable, a later executor and a second helper, but not the equal-tick/helper-absent case. I am checking the real login process records in L1.previous_attempt.sh:1419-1429returns 79 whensystemctl disablefails after a terminal was closed, then returns 73 for a post-closedaemon-reloadfailure (the exit trap maps that to 74). The mapped messages inprevious_attempt.rs:478-489respectively say a terminal/service would not stop and other terminals remain open, or that another holder was found. Those causes are not true for the disable/daemon-reload paths. Before any close, stop/disable-manager errors also map to 73 and the “something else is using it” message without a holder proof. The root fixture explicitly exercises disable-failure-after-close attakeover_terminals_root.py:297-313.Next: fresh SP and L1 guests, final fast output, then verdict. No product files changed or pushed; minipc01 remains untouched.