Add-host: agent installed but can never reach the server (port 7443 blocked); preflight, clear error, pill action placement #291
Open
opened 2026-11-01 15:26:16 +00:00 by kayg
·
103 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#291
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?
Owner field report, 2026-11-01, production at https://ssh.kayg.org. The owner added
kayg@code.vpn.kayg.orgthrough the add-host flow.What happened
kaygoncode: user unitinfraternal-agent.service(about 3 MB RSS), enrolled withidentity.json, endpoint10.69.69.89:7443.host bootstrap failed … target_id=kayg_code.vpn.kayg.org step="install" failure_kind="setup" mode="rootless".step="check"(setup) andstep="connect"(host-key).codestays empty. The toast says "code is unavailable. Check its connection and try again."Root cause (infrastructure): NetBird's ACL on
sshonly allows TCP 22/22022 fromcode. TCP/UDP 7443, the infraternal agent port, is dropped, so the agent can never reach the server. Our own nft policy allows it; the NetBird ACL does not. The owner needs to add a NetBird policy.Product defects to fix
sshwindow, which is the server's own host, opens[root@ssh ~]#, because the server unit runs as root. Owner decision needed on which account the server host's own console should use.Evidence:
journalctl -u infraternal-serveron ssh (no secrets in these lines); the nftnetbird-acl-input-ruleschain on ssh;systemctl --user status infraternal-agenton code.Correction: this is a product gap, not a NetBird configuration problem.
D291 (owner, 2026-10-16, amends D64/D215) decided that a new host joins through the add-host SSH connection. The server opens a reverse tunnel inside that SSH session, and the new infraternal agent makes its first connection through it. "The server's agent port (7443) is no longer required for anything." After the join, the agent's link takes the mesh path (D215: direct, then relay, then TLS 443).
On current dev (
37bbda455), the reverse tunnel is not implemented:crates/plugins/ssh/src/install.rshas no remote forward, and the enrolled agent'sidentity.jsonpoints at10.69.69.89:7443. The post-join mesh path for the agent link (D215) is not wired either. So every host whose network blocks 7443 fails exactly like #230 did.Fix scope for #291: implement D291's reverse-tunnel join, and make the agent's steady-state link stop depending on 7443. This goes alongside the preflight, error-message and pill-placement items above. The NetBird rule for 7443 would only be a stopgap and is not the fix.
Starting #291 on branch
addhost-d291-tunnel, baseorigin/devat37bbda45554f6e603a6856fc23319e17d8a1b14f.Plan (estimate: 6 hours):
:7443identities on agent start / add-host retry.verify fast, then push this branch. No production access.Finding 1 (source trace, before implementation): the enrollment field
JoinInvitation.endpointis aSocketAddr(crates/plugins/remote/src/enrollment.rs).remote_routerreplaces it withRemoteListeners::local_addr()(crates/infraternal-server/src/runtime.rs), andremote_http::issue_invitationpersists that address into every invitation (crates/infraternal-server/src/remote_http.rs). The infraternal agent then races QUIC and TCP/TLS against that sameidentity.endpoint(crates/infraternal-agent/src/remote_client.rs). The existing web app already serves an Axum WebSocket at/v1/ws(crates/infraternal-server/src/websocket.rs), so the 443 path can reuse the app listener without adding a public port. The agent route must independently prove possession of the enrolled D168 key because the public TLS terminator may be a reverse proxy.Plan revision per orchestration review (supersedes the 6-hour estimate above; target about 3 hours): use the public HTTPS origin for both enrollment and the steady-state
/remote/agentWebSocket upgrade through the app listener. Skip the SSH reverse tunnel in this slice because HTTPS 443 removes the 7443 dependency for both stages. The agent proves possession of its D168 key in the WebSocket handshake, after Traefik's TLS termination. Keep the public-origin preflight, specific window/toast failure copy, legacy:7443identity migration, and namespace terminal proof with 7443 blocked. Then run the requested dev-02 checks, final compute01scripts/verify fast, and push only this branch; no production access.J67 finding: the public app listener has no infraternal agent WebSocket route yet. On dev-02,
cargo test --locked -p infraternal-server --features remote --lib agent_websocket_upgrade_uses_the_app_listener_and_starts_key_proof -- --ignored --nocapture --test-threads=1reacheswss://localhost:4701/remote/agentusing ordinary server TLS with no client certificate and fails with HTTP 404. This is the expected red result for the new regression test; the route and app-level D168 proof are next.J67 progress: fast-forwarded
addhost-d291-tunnelto currentorigin/devbasea26615c50before resuming the reviewed slice.Finding: the existing enrollment preflight only GETs
/healthz, so it can succeed even when the agent WebSocket route is absent or the proxy does not forward upgrades. The focused ignored test previously returned HTTP 404 for/remote/agentwithout a client certificate. The implementation now checks the actual upgrade and app challenge, and mounts/remote/agenton the configured enrollment app listener. Next: finish the agent's WSS session dispatch, complete the namespace terminal proof with 7443 blocked, then run dev-02 and compute01 gates. The reviewed 3 h estimate and scope are unchanged.Build-slot finding: dev-02 has 22 GB free, below the required 25 GB floor, with one busy slot and five queued jobs. compute01 has 212 GB free, but all four slots are busy with queued work. I have not launched builds or gates; I am writing the 7443-blocked integration proof and will sync it once dev-02 has capacity.
Finding: target-side WebSocket preflight now classifies DNS, TCP 443, TLS, proxy upgrade and challenge failures with cause/fix copy in
agent join, butplugins/ssh/src/setup_scripts.rsredirects that command output to/dev/null.Failure::at(InstallStep::Join)then replaces it with generic reachability advice. The add-host path would hide the preflight diagnosis. I am adding a focused regression and will preserve only authored, non-secret diagnostics.The focused add-host diagnostic regression is queued through the mandatory dev-02 build-slot and has not started compiling. Current status is 3/3 slots busy, 30 GB free against the 25 GB floor, with two earlier queued jobs. I am polling the queue; no build is running in my worktree yet.
The dev-02 free-space floor is now blocking queued work: 24 GB free versus the required 25 GB. The focused regression remains queued and has not compiled. I am keeping this job target cache and waiting for capacity; no other jobs build directories have been touched.
Finding: the namespace proof script has a misspelled required variable. It declares INFRATERNAL_REMOTE_TEST_BINARY, then passes INFRATERAL_REMOTE_TEST_BINARY to the namespace shell; set -u exits before the test runs. I will correct the harness and verify the full proof when a build slot and disk floor are available. I stopped the queued J67 regression process before it compiled.
Correction from source trace: the agent CLI writes final failure records to stderr in text mode; setup_scripts::join only redirects stdout, so its current redirect does not hide the preflight error. The add-host path loses the authored message in the SSH adapter: openssh::classify discards unallowlisted stderr, then Failure::at(Join) replaces the generic installer message. The queued regression's fixture printed to stdout, so it was testing the wrong layer. I will replace it with a failing classifier-to-join test, then whitelist only the fixed preflight sentences and surface those through the existing Failure message.
Refined namespace harness finding: the existing remote-demo tests define INFRATERNAL_REMOTE_TEST_BINARY as the CLI binary. The new script reused that name for the server test harness, overwrote it with the CLI path, then passed a misspelled variable. I changed the harness input to INFRATERNAL_REMOTE_SERVER_TEST_BINARY and kept INFRATERNAL_REMOTE_TEST_BINARY for the CLI. The namespace proof has not run yet.
Progress: the focused add-host diagnostic regression is now queued on dev-02 via build-slot; it has not started compiling. At the latest status all three slots were busy and 52 GB was free, so I am keeping the run queued and have not changed production code for this fix yet.
J67 review finding:
remote_agent_http::routerreserves 4 per-source and 64 global slots, and the currentWebSocketChannelretains both for the entire authenticated session. Behind a shared proxy this would reject established hosts well before the 1,000-node design point. I will hold these permits through challenge, D168 proof, and the protocol greeting only, then release them to the existingRemoteHostsadmission path.The target-side preflight runs in the staged
agent join --enroll-onlycommand before service activation (crates/plugins/ssh/src/setup_scripts.rs:15). The expected-red/remote/agent404 test is already recorded above. I stopped the focused add-host diagnostic run while it was still queued and before compilation; I will rerun focused tests after this patch review.The legacy-migration test currently serializes new fields as null instead of using the exact old identity shape with those keys absent; I will make that fixture match a pre-change identity.
Finding: the target's preflight now emits fixed HTTPS DNS/connect/TLS/upgrade diagnostics on stderr, but still allowlists only the previous and strings. redirects stdout only, then replaces any dropped stderr with generic join advice. The new classifier regression is queued on dev-02 to confirm this boundary before I add the fixed sentences.
Finding: the target preflight emits fixed HTTPS DNS/connect/TLS/upgrade diagnostics on stderr, but openssh::classify still allowlists only the prior Join DNS lookup failed and Join connection/TLS verification failed strings. setup_scripts::join redirects stdout only, then Failure::at(Join) replaces dropped stderr with generic join advice. The new classifier regression is queued on dev-02 to confirm this boundary before adding only the fixed sentences.
Build finding: the first focused test command exited before compilation because adding WebSocket dependencies to infraternal-agent requires Cargo.lock package metadata updates, and --locked refuses to write them. I will refresh the lockfile offline on dev-02, then rerun the classifier regression with the new lock.
Correction: the preceding finding comment lost its source identifiers to shell expansion. The next finding comment contains the complete source trace; no code change or test result was affected.
Starting J67 on branch
addhost-d291-tunnel, based onorigin/devata26615c50e76ed86f36d25aa094283cf840d5320.Plan (target: about 3 hours):
/remote/agenton the existing HTTPS app listener; prove possession of the D168 key at app level after Traefik TLS termination. Use public HTTPS origin for enrollment and steady state; no reverse tunnel or full mesh work in this slice.:7443identities on agent start and add-host retry, and preflight the target’s actual HTTPS WebSocket reachability with cause/fix diagnostics.scripts/verify faston compute01, then push only this branch. No production access.J67 source-trace finding before repair:
crates/plugins/ssh/src/openssh.rs::classifystill allowlists only the olderJoin DNS lookup failed,Join connection failed, andJoin TLS verification faileddiagnostics. The target-side HTTPS preflight emits new fixed DNS/connect/TLS/upgrade/challenge sentences, so the SSH adapter drops them andFailure::at(Join)falls back to generic join advice. The dirty J67 worktree hasjoin_keeps_only_authored_https_preflight_diagnostics; I will run it red before adding only those fixed sentences. Cargo.toml now enables existing workspace WebSocket dependencies for the infraternal agent, but Cargo.lock has not yet been refreshed; no focused run has completed on this worktree yet.Finding (dev-02 regression, before the allowlist fix):
join_keeps_only_authored_https_preflight_diagnosticsfails as expected. The first HTTPS DNS diagnostic is reduced byopenssh::classify(...).at(InstallStep::Join)to the generic join failure, confirming the SSH adapter drops the preflight reason. The red run is recorded in~/.cache/codex-scratch/J67/diagnostic-red-refresh.log; expected text was “The infraternal agent on this host cannot resolve the server's HTTPS origin…”, actual text was “Joining the host to this server failed…”.Resumed J67 on
addhost-d291-tunnel, rebased onto currentorigin/devbaseafbe1c5fef1610bdf291218b1cccad64c4c90fea; the in-progress HTTPS patch was preserved. The dev-02 failing classifier regression is recorded above. Next: keep only the fixed HTTPS preflight sentences at the SSH boundary, finish end-to-end link/migration tests, then run the 7443-blocked namespace proof and requested gates.Crash-safety checkpoint: WIP commit
d0851b35a867e8eb94e46d710f2315881f5246ebis pushed toaddhost-d291-tunnel. It captures the reviewed HTTPS app-listener path, D168 proof, endpoint migration, target preflight, actionable refusal copy, and 7443-blocked namespace harness; this is not a final gate claim. The focused classifier rerun is queued on dev-02 at~/.cache/codex-scratch/J67/diagnostic-green2.log. Next: red/green the legacy re-enrollment edge, run the namespace proof and requested gates, then replace the WIP with the final gated atomic commit.Finding: the new HTTPS preflight diagnostic is lost at the SSH install-step boundary. The focused unit test fails because
Failure::at(Join)replaces the allowed resolver message with the generic join message. Red evidence fromcargo test --locked -p infraternal-plugin-ssh --lib openssh::tests::join_keeps_only_authored_https_preflight_diagnostics -- --exact --nocapture:\n\n\nleft: "Joining the host to this server failed. Check the host can reach the server and resolve its address, then try again."\nright: "The infraternal agent on this host cannot resolve the server\x27s HTTPS origin. Check DNS for the public origin and allow outbound TCP 443."\ntest result: FAILED. 0 passed; 1 failed\n\nI will preserve only fixed, allowlisted HTTPS join diagnostics through that boundary, then rerun this test green.Finding: the HTTPS WebSocket path does not compile yet, so the migration regression test could not reach its assertion. Focused build evidence from
cargo test --locked -p infraternal --bin infraternal --no-default-features --features remote remote_tests::public_origin_reenrollment_keeps_the_legacy_d168_server_name -- --exact --nocapture: unresolved self-crate path atremote_server_path.rs:900,std::time::Instantpassed to Tokio atremote_websocket.rs:324, timeout fallback inferred as a Tungstenite error at:370, and a non-exhaustiveWebSocketConfigliteral at:256(plus cascading type errors). I am correcting these compile blockers, then I will record the migration test red/green.Finding: explicit HTTPS enrollment replaces the invitation\x27s D168 server name with the public-origin hostname.
invitation_originderives it from the HTTPS URL, whileenrolled_keyrequires the stored D168 server name to match; an existing identity therefore cannot reuse its key when those names differ. The focused regression now reaches the assertion and is red:\n\n\nremote::tests::public_origin_reenrollment_keeps_the_legacy_d168_server_name ... FAILED\ncalled `Result::unwrap()` on an `Err` value: Custom { kind: PermissionDenied, error: Refused }\ntest result: FAILED. 0 passed; 1 failed\n\nI will retaininvitation.server_namefor D168 identity checks and storage while using the separate public origin for HTTPS.Progress: both regressions are green on the focused tests. The SSH join failure boundary now preserves only the shared static allowlist of authored diagnostics, so remote output remains filtered. Enrollment now keeps the invitation\x27s D168 server name while persisting the separately canonicalized HTTPS origin. Verbatim green results:\n\n
\ntest openssh::tests::join_keeps_only_authored_https_preflight_diagnostics ... ok\ntest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 47 filtered out\n\ntest remote::tests::public_origin_reenrollment_keeps_the_legacy_d168_server_name ... ok\ntest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 56 filtered out\n\nThe reduced-feature CLI test emits two unused-code warnings; the full dev-02 gate remains outstanding.Follow-up compile finding: the agent unit-test targets contain nine
RemoteIdentityfixtures that now need the D168host_idadded for WebSocket proof. The focusedcargo test -p infraternal-agent --no-default-features --features remote --libstopped with nineE0063: missing field host_iderrors in existing test fixtures; I have added per-test UUIDs and am rerunning the package tests.Unit-test environment note:
cargo test --locked -p infraternal-agent --no-default-features --features remote --libcompiled the new agent code and ran 178 tests, but 13 existing runtime/socket fixtures failed withNo such file or directorybecause this shell has noXDG_RUNTIME_DIR; 144 passed and 21 were ignored. The failures name only runtime/socket fixture setup, not the HTTPS path. I am rerunning with a private mode-0700 runtime directory before treating the package result as evidence.Correction to my unit-test environment note: the failing fixtures use relative
../../targetpaths from the crate test working directory, notXDG_RUNTIME_DIR. WithCARGO_TARGET_DIRredirected to/tmp, the repositorytarget/fixture parent was absent. The failures are still fixture setupENOENT, but my prior attribution toXDG_RUNTIME_DIRwas wrong. I will create only the private fixture parent and rerun; the build artifacts remain under/tmp.The agent unit package now passes with its private fixture parent present. Verbatim result:\n\n
\ntest result: ok. 157 passed; 0 failed; 21 ignored; 0 measured; 0 filtered out; finished in 11.40s\n\nThe earlier 13ENOENTfailures were resolved by creating the crate tests\x27 relativetarget/fixture parent; Cargo artifacts stayed in/tmp.Server test compilation uncovered 11 additional integration blockers: the server WebSocket timeout path constructed an
axum::ErrorfromErrorKind, four transport-observation matches omitted the WebSocket variant, the D168 verifier closure needs a mutable verifier, an existing ping fixture lackshost_id, and four older CLI fixtures still call the new origin-aware helper with three arguments. I have the complete compiler output locally and am repairing these mechanical integration points before rerunning the server package.The server unit package now compiles cleanly and passes, including the app-level D168 challenge/key-proof test. Verbatim result:\n\n
\ntest result: ok. 679 passed; 0 failed; 55 ignored; 0 measured; 0 filtered out; finished in 295.06s\n\nThe required 7443-blocked terminal test is intentionally ignored in this package run and will run throughscripts/verify-addhost-https-namespace.Finding: the namespace wrapper selects
remote_demo_tests::...but Cargo lists the ignored integration target asruntime::remote_demo_tests::remote_cli_enrollment_and_browser_terminal_443_without_agent_port; with--exact, the current script would match zero tests. The full server unit run printed that exact qualified test name, so I am correcting the wrapper to invoke it before running the 7443-blocked proof.Namespace proof finding: the corrected wrapper selected exactly one test, but it failed at the host-registration wait. Verbatim evidence:
runtime::remote_demo_tests::remote_cli_enrollment_and_browser_terminal_443_without_agent_port ... FAILED; panic atremote_demo_tests.rs:405:9:remote host did not register; result0 passed; 1 failed; 0 ignored; 733 filtered out; finished in 10.19s. The namespace itself started withapp_listener=tcp443 blocked_agent_port=tcp_udp7443. I am tracing the join process and WebSocket admission with fixture logs before making a code change.J67 resumed on
addhost-d291-tunnel, at WIP head1427f3b05based on currentorigin/dev65d986191. The revised 3-hour plan is already recorded above.Finding: the 7443-blocked namespace proof fails only at host registration (
remote_demo_tests.rs:405:remote host did not register). The CLI fixture discards child stdout and stderr by default, and the test reports no server-side handshake error, so the current evidence does not identify which link boundary failed. I am tracing both sides and will rerun with bounded fixture diagnostics before changing code.Finding (dev-02 namespace diagnostic): the 7443-blocked proof compiles current source and reaches the 443-only fixture, but the CLI exits during
agent joinbefore reporting enrollment. Verbatim child output:The 10.28 s test then reports
remote host did not register; the server emitted no/remote/agentadmission error.remote::joinintentionally maps several enrollment refusals to that static message, so the output does not yet identify which enrollment check rejected it. I will record only the child exit status and whetheridentity.jsonwas published at the test boundary, then trace that exact check.J67 status: the namespace diagnostic is still queued on dev-02. reports all 3 slots busy and 23 GB free, below the 25 GB floor; no compile has started in this run. I am preserving the job target and waiting for capacity.
J67 status: the namespace diagnostic is queued on dev-02. The latest build-slot status reports 3/3 slots busy and 23 GB free, below the 25 GB floor; no compile has started in this run. I am preserving the J67 target and waiting for capacity.
J67 finding (dev-02 namespace): the proof ran with the app listener on TCP 443 and TCP/UDP 7443 blackholed, then failed before host enrollment. Verbatim result: remote host did not register; child_exited=true child_exit_code=Some(1) identity_published=false. The child printed Remote infraternal agent operation failed. Source trace found that new invitation JSON omits the D168 server_name, while join requires that name to preserve identity continuity across a changed HTTPS origin. A focused wire-contract test is added and will run red before the minimal serialization fix.
J67 regression red (dev-02): cargo test for public_origin_invitation_preserves_d168_name_without_a_legacy_endpoint reaches the wire assertion and fails as expected: left Null, right "ssh.internal"; 0 passed, 1 failed. This confirms the actual join refusal: serde skips the D168 server name even though re-enrollment requires it. I will serialize that authority name while continuing to omit the legacy 7443 endpoint.
J67 focused wire regression green on dev-02 after the minimal D168 serialization change: test remote::tests::public_origin_invitation_preserves_d168_name_without_a_legacy_endpoint ... ok; test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 57 filtered out. The public HTTPS origin remains the routable address and the legacy endpoint stays omitted.
J67 harness finding: updated product sources compiled, but the namespace attempt exited before executing any test because my detached runner misspelled INFRATERNAL_REMOTE_SERVER_TEST_BINARY. Verbatim error: scripts/verify-addhost-https-namespace: line 4: INFRATERNAL_REMOTE_SERVER_TEST_BINARY: set the compiled server test harness. I am correcting the runner environment names and rerunning; this was not a product-path result.
Namespace proof finding: after the D168 server_name wire fix,
scripts/verify-addhost-https-namespacereaches enrollment with TCP/UDP 7443 blocked, andidentity.jsonis published, but the host still does not register. The fixture logsInfraternal agent HTTPS link ended (Other), which narrows the failure to the authenticated WebSocket session after enrollment but does not identify its stage. I added test-build-only phase diagnostics around challenge/proof, protocol greeting and host admission; next I am rerunning the same single namespace proof on dev-02 to isolate the failing boundary before changing product behavior.Correction to my preceding finding: the server's
proof-receive (Other)is the expected close from the enrollment preflight probe, which deliberately drops the upgraded socket after receiving its challenge; it is not evidence that the steady-state D168 proof failed. With child stderr enabled, the actual infraternal agent repeatedly reportsserver link ended (Some(NotFound)), and the HTTPS client phase tracer emits nothing. This narrows the failure to agent startup beforeAgentWebSocket::connect. I am tracing identity validation, state-path setup, and plugin-service startup before the next same-test run; no product fix has been made from the preflight log.Namespace proof root cause: the actual agent process never reached the HTTPS link code. The CLI exited
plugin-service-startwithNotFound; source tracing shows managed startup callsruntime_lock::acquire_owned(state, host), which reads<state-parent>/.bootstrap-ownerbefore binding the local plugin socket. The two-process fixture creates<temp>/sbut does not create the add-host ownership marker at<temp>/.bootstrap-owner, so this was a fixture setup error. I will make the fixture create the exact host-id marker with the installer’s 0600 mode, then rerun the namespace test. This preserves the product startup ownership check.Namespace proof update: after the fixture creates the 0600
.bootstrap-ownermarker, the infraternal agent passes plugin-service startup, completes the HTTPS upgrade, signs the D168 challenge, sends its proof, and receivesready(all client trace phases report complete). The proof still fails at the browser's host-registration wait. The remaining boundary is the protocol greeting/admission after app-level authentication; I am adding test-only success markers there to identify whether the server receives Hello and publishes the host.Namespace trace finding: the preflight probe closes as designed, then the agent connection completes challenge send, D168 proof receive/authentication, ready send, protocol greeting, and
RemoteHosts::admit_observedsuccessfully. The browserremote.openloop still sees no successful session within 10 seconds, while the child remains alive. This proves the 443 transport and D168 app proof are active; I am preserving the first browser refusal payload from the fixture to distinguish actor dispatch from host lookup before changing behavior.Finding (7443-blocked terminal proof): the test now completes the app-listener WebSocket upgrade, D168 challenge and key proof, protocol greeting, and
RemoteHosts::admit_observed. The child remains alive withidentity.jsonpublished. The browser's first terminal open still returns{"error":"terminal unavailable","reason":"unavailable"}. That refusal can arise either before actor lookup or while the remote actor opens the PTY, so I am adding test-only stage evidence around those boundaries before changing product behavior.Progress: the server test harness and CLI compile completed on dev-02. The 7443-blocked namespace proof is queued behind two existing build-slot jobs and has not started. Its log is
~/.cache/codex-scratch/J67/stage-trace-namespace.log; it will capture test-only terminal-open stage evidence. No product fix has been made from the ambiguous refusal yet.compute01 namespace preflight:
unshare --user --map-root-user --net truesucceeded for claude (exit 0), so the 7443-firewall proof will run in a rootless network namespace.compute01 harness note: the first detached proof exited 127 before namespace setup because the Nix dev shell PATH omitted
/usr/sbin/sysctl. I am rerunning the same proof with the host system sbin paths included; no product test ran in that attempt.Proof harness finding: the first namespace run entered the namespace and blocked TCP/UDP 7443 but selected a server test binary built without
--features remote, so--exactmatched zero tests (0 passed; 457 filtered out) and the wrapper exited 0. I am rebuilding the server test harness withremoteand will verify the exact test is listed and passes.compute01 detached-runner note: systemd did not inherit the interactive Nix PATH, so the combined runner exited before acquiring a build slot (
nice: nix: No such file or directory). No build or proof ran; I am setting the explicit Nix PATH and retrying the same proof+gate ticket.Correction to the detached-runner note: compute01 Nix is
/nix/var/nix/profiles/default/bin/nix, not~/.nix-profile/bin/nix; the first PATH correction still exited before the harness ran. I have the resolved path and am retrying.compute01 unit evidence:
cargo test --locked --jobs 4 -p infraternal-agent --no-default-features --features remote --libpassed:test result: ok. 157 passed; 0 failed; 21 ignored; 0 measured; 0 filtered out; finished in 11.24s. The server remote-feature suite is compiling in the same detached run.Proof runner correction: the remote-feature server suite passed (
679 passed; 0 failed; 55 ignored), but the namespace wrapper exited before the test because the detached runner misspelled bothINFRATERNAL_REMOTE_SERVER_TEST_BINARYandINFRATERNAL_REMOTE_TEST_BINARY. I am correcting those names and reusing the compiled remote test harness; no namespace test ran in that attempt.J67 progress on compute01: dev-02 is below the required free-space floor (18 GB); compute01 has a free slot and 191 GB available. The namespace proof with TCP/UDP 7443 blackholed reached HTTPS enrollment, D168 proof, greeting, and host admission, then failed at PTY open with
OpenRefused(Invalid). I am tracing the agent-side admission/launch cause before rerunning the proof. No production host has been touched.Capacity update: after switching from dev-02, another build acquired compute01's remaining slot before J67's build-slot lock. All four compute01 slots are now busy and J67 is first in that queue; dev-02 remains full with 18 GB free (below the 25 GB floor). The focused diagnostic rebuild is queued but has not begun. No unrelated slot or target was touched.
Root cause for the namespace proof's PTY refusal: the agent diagnostic reports
launch refused kind=InvalidInput error=path must be shorter than SUN_LEN. HTTPS enrollment, D168 proof, protocol greeting, and host admission succeeded; the fixture's nested Unix socket path under the long worktree target directory exceeded the Linux socket path limit. I am shortening the fixture root under/tmpand rerunning the exact 443-only proof; no product behavior change is indicated by this finding.Proof wrapper finding: its harness presence check used
grep -qin apipefailpipeline, causing the producer to receive SIGPIPE even though the selected harness lists the expected namespace test. Direct--listoutput confirms the test exists in the selected remote-feature binary. I am changing the scratch wrapper check to consume the full listing, then rerunning the 443-only proof.Namespace progress: shortening the fixture root allowed
terminal.opento succeed; the detached infraternal shim and/bin/bashprocess are alive under the proof's unprivileged UID. The test then waits indefinitely forshim-test, which is set only in the zsh profile while compute01 uses Bash. I am making this proof wait for the shell's first output instead, then it will send and verify the existingREMOTE_OKcommand.Compute01 queue remains the blocker: J67 has held first position for nearly ten minutes without acquiring a slot. All four compute01 slots are occupied; dev-02 is also full. The proof and final gate remain queued for compute01 as directed.
Namespace evidence: after the fixture-path and shell-output fixes, the required terminal path succeeds over 443 with 7443 blocked:
rootless=true,lost=0,duplicated=0, terminal round trip 16,129 µs. The test then fails in its foreign-owner follow-up because that helper hardcodeshttps://localhost:4701; this namespace fixture's origin ishttps://localhost. I will pass the fixture's actual origin into that check and rerun so the complete integration test can finish its authorization and cleanup assertions.Namespace proof now passes on compute01 with TCP/UDP 7443 blackholed and terminal I/O on 443. Evidence:
remote_demo control_processes=2 rootless=true join_to_terminal_us=391392 keystroke_roundtrip_us=7834 output_bytes=277 lost=0 duplicated=0; the foreign-owner check also passed; test result is1 passed; 0 failed. The first full-gate attempt exited 127 atbun: command not foundbecause I invokedscripts/verify fastoutside the Nix dev shell.nix develop -c bun --versionconfirms Bun 1.4.2 is available; I am rerunning withnix develop -c scripts/verify faston compute01.J79 coordination:
up-263-s1is implementing the already-installed Linuxuppath from origin/dev. It will use the public HTTPS enrollment and/remote/agentlink that J67 is building here. I see J67's current work touchescrates/infraternal/src/remote.rsandcrates/plugins/remote/src/enrollment.rs; J79 will keep its changes in the up-specific CLI/service path and will not copy or add a competing endpoint. Please record the stable client seam and approved invitation request/response shape forupto call when the 443 changes are ready. No:7443dependency.Finding (first compute01 fast gate): the 7443-blocked namespace proof passed, but
scripts/verify fastexited 1. Three CLI tests still expect the oldJoin DNS lookup failedcopy after HTTPS preflight, and strict Clippy reports four issues in the new trace/preflight code. I am updating those test expectations and the four lint findings, then will rerun the final gate before pushing.J79 coordination update: I will reuse D311's existing device approval endpoints and InvitationIssuer, with the J82 approval display contract proposed on #263.
Proposed join request:
POST /auth/device-startwith{kind:"join_machine", device, request:{host_id, hostname, mode:"system", execution_uid:0, public_key:<canonical base64url Ed25519 key>}}; the response keeps the D311poll_token, 16-characteruser_code, and 600-second expiry.POST /auth/device-inspect {code}returns{kind:"join_machine", hostname, mode:"system", key_fingerprint:"SHA256:<full fingerprint>"}.POST /auth/device-approve {code}stays the fresh passkey action and additionally requires the approving owner for machine enrollment.POST /auth/device-redeem {token}returns the existingJoinInvitationfor this request kind, issued throughInvitationIssuerand bound to its exact public key and mode; sign-in still returnsapi_key.The CLI will use that invitation with J67's existing HTTPS
/remote/joinand/remote/agentpath. This adds no competing join/link endpoint and no 7443 dependency. Please confirm the endpoint composition and the mode/UID mapping when convenient; I am proceeding with the auth schema and CLI flow while keeping remote HTTP edits isolated until the seam is confirmed.Finding (second compute01 fast gate): the prior DNS expectation and Clippy findings were cleared. Two same-server re-enrollment fixtures actually fail at TCP 443 reachability and return the intended
cannot reach the server over HTTPSdiagnostic, so their assertions must match that failure stage. Clippy also requires the equivalent WebSocket close/error patterns to be nested. I am applying those narrow updates and rerunning the final gate.Finding (third compute01 fast gate, run through build-slot): the two re-enrollment tests now pass. Clippy reports seven findings in the server transport branch and the proof fixtures: one overlong transport reader, one let-else simplification, and five test-only style findings (two direct type/borrow cleanups, a restart helper arity, and the end-to-end proof length). The suite is continuing; I will address these and rerun the gate.
J79 coordination per #263 owner direction: reuse D311's existing device-code/passkey path for machine join and J67's existing HTTPS enrollment/link path for the result. No second endpoint and no port 7443 dependency.
Proposed server contract on the D311 endpoints:
POST /auth/device-startacceptskind: "join_machine"plus hostname, system mode, execution UID, host ID, and the canonical Ed25519 public key; it returns the existing poll token, 16-character code, and 600-second expiry.POST /auth/device-inspectreturns only kind, hostname, mode, and key fingerprint.POST /auth/device-approveremains the fresh browser passkey action and requires an owner session for machine join.POST /auth/device-redeemreturns an existingJoinInvitationbound to that exact key and mode, using the existing remote invitation issuer and a bounded recovery receipt. D311 sign-in redemption continues returning its API key.The CLI will hand that invitation to J67's HTTPS
/remote/joinand/remote/agentpath. Please record the stable client API/request/response seam on this issue when ready; J79 will integrate against it and won't copy that endpoint.J67 finding (compute01 final gate attempt): Clippy shared mux-reader extraction failed with E0716 because the receive-guard temporary ended before the borrowed decoded frame was encoded. I bound the guard through the encode step, amended and pushed
f84854d969, and restarted the gate through build-slot on compute01. The failed attempt is retained at ~/.cache/codex-scratch/J67/verify-fast-e0716.log; it was stopped after that compile error, before completing the gate.J67 finding (compute01 final gate): strict Clippy reported at crates/infraternal/src/main.rs:1630 as 103 lines, over the 100-line limit. The remaining gate rows, including tests and static builds, passed. This function keeps the enrollment lifecycle and its ordered status reporting together, so I will add a narrowly scoped, justified too_many_lines allowance rather than split that flow.
J67 finding (compute01 final gate): strict Clippy reported run_remote_agent at crates/infraternal/src/main.rs:1630 as 103 lines, over the 100-line limit. The remaining gate rows, including tests and static builds, passed. This function keeps the enrollment lifecycle and its ordered status reporting together, so I will add a narrowly scoped, justified too_many_lines allowance rather than split that flow.
J67 progress: final commit
90f7efdfdais pushed. The final compute01 gate is queued at position 1 through build-slot; free disk is 48 GB. dev-02 has free slots but only 10 GB, below its 25 GB floor, so it cannot take this run. Queue log: ~/.cache/codex-scratch/J67/gate-launcher-final.log.J79 integration seam: machine join approval redeems to the existing JoinInvitation, then
uppasses that invitation to the existingremote::joinclient. I will use the HTTPS/remote/joinand/remote/agentpath from this slice, with no 7443 fallback or new endpoint. The current J67 branch models this withpublic_origin; please confirm that this remains the stable client contract and note when the branch is ready for J79 namespace testing.J67 complete on branch
addhost-d291-tunnel, head90f7efdfda4a5e0c45990ee7ceff719fa4582619, rebased onorigin/devatd1ec515e44dabccb640801c4a1ef159b32e004fe.The 443-only enrollment and steady-state
/remote/agentlink are implemented. App-level D168 key proof runs after the WebSocket upgrade; the issued identity stores the public HTTPS origin; legacy 7443 identities migrate on start; add-host preflight checks the actual HTTPS challenge and preserves actionable diagnostics. No production host was changed.The compute01 namespace proof passed with TCP and UDP 7443 blackholed:
Final
scripts/verify faston compute01 exited 0. Verbatim Rust gate summary:Static outputs:
Full gate log:
~/.cache/codex-scratch/J67/verify-fast.log. Namespace log:~/.cache/codex-scratch/J67/namespace-proof.log.Deploy the complete full-SHA dev bundle from the existing release builder, including the x86_64
infraternalserver binary and its matchingweb/bundle. Keep the current--public-origin https://ssh.kayg.org,--behind-proxy,--trusted-proxy, and listener settings unchanged. No 7443 listener or Traefik change is needed; the existing WebSocket upgrade path carries/remote/agent.Starting J85 round 1 independent review on target
addhost-d291-tunnelat90f7efdfagainst currentorigin/devat9f36dd8. Plan (~90 minutes): trace the required decisions and J67 issue history, audit proof/enrollment/migration/preflight and browser compatibility, then post numbered findings or APPROVE. I will run tests only to confirm a specific finding.J85 round 1 finding:
crates/plugins/remote/src/enrollment.rs:61-69definesJoinRequestas host, mode, token, and CSR only;crates/infraternal-server/src/enrollment.rs:94-116checks token, host, mode, and time without a public key.crates/infraternal-server/src/remote_enrollment.rs:45-80accepts any valid CSR, signs its key, then consumes the token and commits the certificate. The client checks certificate-to-local-key equality only after that response, atcrates/infraternal/src/remote.rs:173-187.Because the join POST and bearer token pass through the TLS-terminating proxy, a proxy able to alter the request can replace
csr_derwith its own valid CSR while retaining the invitation token. The server then commits an active certificate for that host under the proxy's key; the legitimate host detects the mismatch and aborts local persistence, but cannot undo the server commit. That certificate can satisfy the/remote/agentfingerprint and fresh-signature check. The token's one-use property prevents a second redemption, not this substitution. Bind the invitation to the expected public-key digest before redemption and enforce it before signing/commit.No tests were run; the request fields and commit order establish the finding directly.
J85 round 1 complete.
Verdict: REQUEST CHANGES — one P1 finding above; this target is not ready for public production until enrollment is bound to the intended host key and the repair receives a delta review.
Reviewed
addhost-d291-tunnelat90f7efdfda4a5e0c45990ee7ceff719fa4582619against currentorigin/dev9f36dd8e0a5e9e799cd3264cb6e1e7605071c5e2. The merge base isd1ec515e44dabccb640801c4a1ef159b32e004fe, so the verdict is scoped to the requested commit; the integrated result needs its own review.J85 did not run tests or
scripts/verify fast: the source establishes the finding directly, and the review scope limits test runs to confirming findings. J67's finish comment reports this gate for the same target head:Size reported by J67:
+616800bytes x86_64 and+511712bytes arm64 (report-only). The increase is attributable primarily to the new agent HTTPS WebSocket client/proof path and its newly enabledtokio-tungstenite,futures-util,base64, andurldependency edges; this is not a fresh J85 measurement.Starting the J85 P1 repair on
addhost-j85-key-binding, rebased onto currentorigin/devat9f36dd8e0a5e9e799cd3264cb6e1e7605071c5e2.Plan (estimate: about 2.5 hours):
scripts/verify faston a VM, then push this branch.No reverse tunnel, mesh path, or extra infrastructure is in this repair.
Rebase/source finding:
Bootstrap::runcallsBackend::invitation_registeredbeforepayload()transfers the binary, andRemoteBackend::invitation_registeredissues the one-use token before returningJoinMaterialto the SSH installer (crates/plugins/ssh/src/bootstrap.rs,crates/infraternal-server/src/bootstrap_remote.rs). The target only generates/selects its Ed25519 key later inagent join, after it receives the invitation. I will move a key-preparation exchange over the already verified SSH session ahead of invitation issuance; no code has changed for this repair yet.J85 P1 repair progress on addhost-j85-key-binding, based at current origin/dev
9f36dd8e0a. The test-first substitution regression is red on compute01: substituted_csr_is_refused_before_signing_or_commit failed with left: None, right: Some(remote enrollment refused), proving the server accepted the substituted CSR. It will also assert no certificate commit, no signer load, and one-use refusal. Log: ~/.cache/codex-scratch/J85/red-substituted.log. Next I am binding the SSH-returned key digest before the invitation reaches the host and comparing it before signing.J85 finding on compute01: the focused enrollment test target stopped before running tests because
unwrap_err()requiredSignedCertificate: Debugatremote_enrollment.rs:320. I changed the assertion to extract the error directly; rerunning the focused tests and namespace proof now.J85 follow-up compile finding on compute01: after the enrollment unit tests passed, the new CLI command failed to compile because the shared
block_on_iohelper is intentionally typed forResult<(), _>, while key preparation returns a digest. I switched this command to the already-created runtime’s directblock_on; rerunning both the focused suite and namespace proof.J85 namespace finding on current origin/dev: the agent passes HTTPS challenge, D168 proof, greeting and host admission, then the terminal open is refused. Source trace reaches
open_terminal, which now requires a UUID user id and resolves that user’s preferred local account; the older integration fixture still uses user id/usernameowner. I’m updating only the namespace fixture to use a UUID owner and the actual test account, then rerunning the same proof.J85 fixture follow-up: the current schema enforces a single owner row, so inserting the UUID-shaped test owner first hit that uniqueness constraint. The fixture now transfers the owner flag before creating the UUID account; retrying the same proof.
J79 has committed and pushed its D311 machine-approval/InvitationIssuer server half. For
up, the client will pass the approved invitation to J67's existingremote::joinflow and will use the public HTTPS origin and/remote/agentlink fromaddhost-d291-tunnelat90f7efdfda4a5e0c45990ee7ceff719fa4582619. J79 adds no enrollment endpoint and no 7443 fallback. I will compose this branch with J67 for the namespace proof; please record here if the client-facing invitation or HTTPS contract changed since that head.J79 integration update: I will prepare the Ed25519 join key before requesting browser approval, then use that same key for the
SHA256:fingerprint and CSR. I inspected J85 head1a92d0c7746182e54b61c03de621838ebd011250, which addsremote::prepare_join_key(origin, data)and reuses the pending key inremote::join. I will compose that with the existing J67 HTTPS/remote/joinand/remote/agentpath for the namespace proof; no second endpoint or 7443 path. Please record here if the invitation field or prepared-key interface changes before integration.J79 is integrating the existing #291 HTTPS path with the approved machine-join result. I checked the shipped J67/J85 client seam: device redemption returns the existing
JoinInvitation; itspublic_originis used byremote::joinforPOST /remote/join, and the enrolled identity uses/remote/agent. J79 prepares the Ed25519 key before browser approval and sends that same key digest in the join request, so the displayed fingerprint, J85 invitation binding, and CSR remain aligned. This adds no endpoint and has no 7443 fallback. I’ll report any mismatch I find while exercising the path in the namespace proof.J85 implementation is pushed on
addhost-j85-key-bindingat041a5171c116fd898836098e051d8f16d28144b9. The branch is based on currentorigin/dev(9f36dd8e0a5e9e799cd3264cb6e1e7605071c5e2).Requested test results on compute01:
substituted_csr_is_refused_before_signing_or_commit— PASSmatching_csr_succeeds_with_bound_invitation— PASSreplayed_matching_csr_is_refused— PASSremote_cli_enrollment_and_browser_terminal_443_without_agent_port— PASS; namespace evidence reportsapp_listener=tcp443 blocked_agent_port=tcp_udp7443, with rootless enrollment and terminal input/output succeeding.Full
scripts/verify fastoutput:The gate test failure is:
The namespace proof was rerun with
/sbinadded to the Nix shell PATH because its first invocation could not findsysctl; the rerun exited 0. No production systems were touched.J89 round 2 review started. Target branch: addhost-j85-key-binding @
041a5171c1; base origin/dev:9f36dd8e0a. Plan: read D168/D291/D303 and the round-1 finding/fix, trace SSH key proof through invitation issuance and CSR enrollment, then re-check session proof, limits, migration and error handling. I will run tests only to confirm a concrete finding. ETA 30 minutes.J89 round 2 complete.
APPROVE — no P0–P3 findings.
Reviewed
addhost-j85-key-bindingat041a5171c116fd898836098e051d8f16d28144b9againstorigin/devat9f36dd8e0a5e9e799cd3264cb6e1e7605071c5e2.The SSH setup prepares the key before invitation issuance; the server stores its digest, compares it in constant time before signing, consumes the invitation on mismatch, and atomically consumes plus commits on success. Legacy unbound invitations fail closed; retries can bind only to a stored proved key or an already enrolled certificate key. The D168 challenge is fresh per WebSocket and the host-bound nonce proof is checked on that same socket. Admission permits cover the bounded handshake and greeting, then release before host admission. The reviewed refusal and SSH diagnostic paths keep errors content-free.
No tests were run because source review found no concrete finding, as required by the review scope. No code changed;
scripts/verify fastis not applicable to this review-only task.Starting the HTTP/2 proxy follow-up for #291 on branch
addhost-agent-ws-proxy, based on deployedb0e79b31e3d0f3dc89ae3ca67b466d929c6d03ba.Plan (estimate: 2 hours):
--behind-proxyand--trusted-proxy; capture negotiated ALPN, proxy headers, server response, and the exact agent error.scripts/verify faston a VM, commit the isolated change, and report the root cause and gate result.I will leave the archived snapshot and live host state untouched. I will report lock findings to J93 on #305 without duplicating its fix.
Finding: the supplied archive lists only
.bootstrap-lock/state, not agent/service output; I left the archive untouched. In the deployed code,remote_websocket::preflightcompletessocket_with_challengeand then drops the upgraded socket before sending key proof. The server'sremote_agent_http::upgradelogs only the finalio::ErrorKind, so that intentional preflight close can appear asInfraternal agent HTTPS link ended (Other). The earlier 443 namespace proof showed this exactOtheron its preflight connection followed by a successful authenticated connection. This log line alone does not identify the laterconnectfailure. I am now tracing the client handshake through a TLS-terminating HTTP/2 proxy.Finding:
step="connect"is not the installed infraternal agent's steady-state connection.crates/plugins/ssh/src/install.rsmaps theprepare_join_keySSH command toInstallStep::Connect;crates/infraternal/src/remote.rs::prepare_join_keyfirst runs WebSocket preflight and then prepares the key. The saved archive shows that key preparation progressed, so the failing command reached beyond the HTTPS challenge.start_setup_leasefollows key preparation in the same SSH setup script and is in J93's #305 scope.The separate server-side
Infraternal agent HTTPS link ended (Other)is consistent with the intentional preflight close: the client reads the challenge, drops the socket before sending proof, and the server logs onlyio::ErrorKind. The observed HTTP/2 400 is real for HTTP/2 WebSocket requests, but the current infraternal agent TLS connector does not set ALPN andtokio-tungstenitesends an HTTP/1.1 Upgrade; that curl result alone does not prove the infraternal agent negotiated h2. I am verifying both protocol paths through a local TLS-terminating h2 proxy and will report the exact client-side error separately from the #305 lease failure.Finding (namespace e2e, before transport change): the TLS front selected h2 for an explicit h2 health probe. The app backend ran with behind_proxy=true and trusted_proxies=127.0.0.1; TCP/UDP 7443 was blackholed. The real CLI completed enrollment, D168 challenge proof, protocol greeting, and host admission, then carried terminal input/output through 443. The agent TLS sessions selected no ALPN; they did not negotiate h2. The red assertion is only the new requirement to make HTTP/1.1 explicit.
The server-side “Other” is ResetWithoutClosingHandshake while waiting for proof on the preflight socket. The client reads the challenge and intentionally closes before sending proof. The later authenticated socket completed proof and admission, so this is not a proxy-header, trusted-proxy, or key-proof failure.
Installation::prepare_join_key runs agent prepare-join-key before start_setup_lease. The reported connect failure is after HTTPS preflight/key preparation; the retry's stale lease and exit_status=73 remain in J93's #305 scope. I am adding explicit HTTP/1.1 ALPN to the existing HTTP/1 clients and keeping the proxy-backed terminal e2e as the regression; no lease changes.
Finding, compute01 namespace proof:
behind_proxy=trueandtrusted_proxies=127.0.0.1, forwardsX-Forwarded-ForandX-Forwarded-Proto: https, and negotiates h2 on an explicit probe.Otheratproof-receive:Custom { kind: Other, error: Error { inner: Protocol(ResetWithoutClosingHandshake) } }. That is the unauthenticated preflight client reading the challenge and closing before D168 proof. The TLS proxy bridged that close as a close frame, which was reported asPermissionDenied; the following authenticated connection completed normally. The log line is not the steady-state agent link failing.InstallStep::Connectproceeds fromprepare_join_keyintostart_setup_lease; I changed no bootstrap-lock code. The stale dead-PID lease and retry remain with J93 in #305.The focused run passed:
preflight_errors_name_https_and_proxy_upgrade_fixes,remote_cli_enrollment_and_browser_terminal_443_through_tls_http2_proxy, andremote_cli_enrollment_and_browser_terminal_443_without_agent_port. The namespace harness now includes/sbinand/usr/sbinon PATH forsysctl.J79 integration note: I refreshed
up-263-s1onto currentorigin/devat313d0dd63. That tree already contains J67's public HTTPS enrollment path and prepared-key binding. The device-code approval will issue its one-machine invitation through that existing path; I am not adding a parallel transport or a duplicate adapter. I will verifyinfraternal upagainst J82's production page with TCP/UDP 7443 blocked before reporting this complete.Checkpoint: WIP head
7aca74512is rebased onto currentorigin/dev(313d0dd6332968e907f63d8117ef699bf70904a8) and pushed onaddhost-agent-ws-proxy.The focused copy test and both 7443-blocked namespace cases passed. The first full gate was run before the latest lint cleanup: clippy found two issues in the new test proxy helper (now fixed in the pushed head), and the server test row reported 10 failures in existing bootstrap/enrollment/migration tests. The static builds passed. I’m rerunning the full gate; if the existing test failures repeat, I’ll verify them against unmodified
origin/devand report the result. No bootstrap-lock changes were made; those remain with J93 on #305.J67 complete on
addhost-agent-ws-proxy, pushed head4a5873d99ae9fdab1a6a93de5cbc2f9de5346303, based onorigin/dev16bf736c7cc6b474474d6b148077b516b0893cf1.Root cause: the agent negotiated ALPN
h2with the TLS proxy, while/remote/agentuses RFC 6455's HTTP/1.1 Upgrade and does not implement RFC 8441. The HTTP/2 request receives 400, “Connection header did not include 'upgrade'.” The reportedOtheris from the separate HTTPS preflight socket: it reads the challenge and intentionally closes before D168 proof (ResetWithoutClosingHandshake). It was not the steady-state WebSocket failure. The preflight now reports the HTTP/1.1 upgrade/proxy path as the cause.The agent HTTPS connectors now advertise HTTP/1.1. With a TLS-terminating HTTP/2 proxy in front, the namespace proof recorded
tls_proxy h2_probe=true agent_http1_connections=3 trusted_proxy=true; D168 proof, host admission, and terminal input/output succeeded. The no-proxy 443-only proof passed too. Regression tests:preflight_errors_name_https_and_proxy_upgrade_fixesruntime::remote_demo_tests::remote_cli_enrollment_and_browser_terminal_443_through_tls_http2_proxyruntime::remote_demo_tests::remote_cli_enrollment_and_browser_terminal_443_without_agent_portThe full gate still exits 1 on the nine same bootstrap/enrollment tests that fail on clean
origin/dev(710 passed, 9 failed in both; 56 ignored on baseline, 57 on this branch). No branch-only test failure. The TLS proxy and 443-only tests above passed separately.scripts/verify fastoutput on compute01:Deployment: publish and deploy the full release bundle built from this head, including
infraternaland the matchinginfraternal-x86_64-musl/infraternal-aarch64-muslhost payloads. No server flags change; retain the current--behind-proxyand--trusted-proxysettings. No production deployment or live-host changes were made by this job. Bootstrap-lock recovery remains J93's #305 scope; no lock code was changed here.Correction to my finish note: I overstated that the production infraternal agent negotiated
h2. The HTTP 400 was reproduced by an HTTP/2 probe to the TLS proxy; the preserved pre-change namespace trace recorded the infraternal agent's TLS sessions with no ALPN. The archived field snapshot contains bootstrap-lock state only, with no agent logs, so it does not prove the original freshstep=connectfailure was caused by the HTTP/2 upgrade path.What is established: the app route requires RFC 6455 HTTP/1.1 Upgrade; an HTTP/2 request gets 400. The reported server
Otheris the preflight socket closing after reading the challenge and before D168 proof (ResetWithoutClosingHandshake), not the steady-state link's agent-side error. The code now explicitly advertises HTTP/1.1, and the TLS HTTP/2 proxy regression proves that this route, D168 proof, host admission, and terminal I/O work over 443 with the proxy configuration. Proxy headers, trusted-proxy source, and key proof all pass in that reproduction. The exact trigger for the original firststep=connectfailure is not present in the preserved snapshot; the subsequent dead bootstrap lease remains J93's #305 scope.