S1 tracer: server core (HTTP, WebSocket, sessions, embedded agent spawn, self-install) #19
Closed
opened 2026-10-06 11:48:14 +00:00 by kayg
·
58 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#19
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?
Part of #9. Base
dev7174d6b. The server half of the tracer bullet: axum on HTTPS or plain HTTP behind a proxy, the auth routes from #17 mounted, the leg-A WebSocket with the wire codec, sessions per user, spawning and supervising the embedded agent (agent --fd 3), routing terminal streams, andinfraternal server install.Starting server core lane on branch t-server, based on dev
7174d6bf6f.Contract gaps found at
7174d6b: contracts/wire.md defines WebSocket frames but not the embedded Unix socketpair channel's trust/mux behavior or terminal stream allocation; contracts/plugin.md has no terminal.open schema; contracts/keywrap.md requires forwarded client IPs to be trusted only from configured proxy peers, while the requested CLI has only --behind-proxy. Proposal for this lane: (1) terminal.open on control stream 0 uses JSON {cols:u16,rows:u16,px_w:u16,px_h:u16} and returns {session_id:<32 lowercase hex>}; (2) socketpair fd 3 uses the existing length-delimited wire frames wrapped in leg-B mux envelopes, with stream 0 control and server-opened even terminal streams; the inherited local socketpair is the trust boundary, so no mTLS; (3) add repeatable --trusted-proxy <IP/CIDR>, require at least one when --behind-proxy is set, and only accept X-Forwarded-* from those peer addresses (public-origin remains pinned). I will implement these additive details in this lane and document the local IPC boundary.Baseline finding at
7174d6b: crates/infraternal-agent/src/lib.rs contains only its module doc and crates/infraternal/src/main.rs returns 'not implemented' for the agent command. The server lane cannot launch a real local agent against this base; per #19 I will keep the production contract pointed at agent --fd 3 and use a clearly marked contract-speaking stub only in the integration test until the agent lane is merged.Runtime storage test found a migration namespace collision: placing the server schema at crates/infraternal-server/migrations/server/0001_runtime.sql makes auth::Auth::new return Migration(VersionMismatch(1)); reproduced by runtime::tests::router_migrates_private_storage_and_serves_health. I am moving server migrations to an independent server-migrations directory so the existing #17 auth migration runner remains untouched.
Correction to the previous migration finding: the collision is the shared SQLite version namespace, not recursive discovery. The server's version-1 migration ran before Auth and Auth then rejected its different version-1 checksum. Fix: keep the server migration in at version 2 and run Auth::new first, then the server migration.
Correction to the previous migration finding: the collision is the shared SQLite _sqlx_migrations version namespace, not recursive discovery. The server version-1 migration ran before Auth and Auth then rejected its different version-1 checksum. Fix: keep the server migration in server-migrations at version 2 and run Auth::new first, then the server migration.
Second migration correction from the same startup test: using a separate migrator directory with version 2 returns Migration(VersionMissing(1)), because both migrators share one SQLite migration table and sqlx verifies the complete embedded history. The compatible approach is a contiguous 0002_runtime.sql in the existing migrations directory; Auth::new then applies the single ordered schema stream without changing auth behavior.
Protocol gap proposal: the current wire handshake has no unambiguous way to tell a new connection from one that will send Resume. I propose reserving Hello caps bit 0 as RESUME_NEXT: a new client sends Hello with bit 0 clear and receives Welcome; a resuming client sends Hello with bit 0 set, then Resume on stream 0, and receives a refreshed Welcome after the server validates the single-use resume token. The server rejects a Resume unless bit 0 was set. I am implementing this handshake and documenting the capability on the #19 branch.
Alignment note: the web client calls the user-facing action
ssh.session.open(contracts/plugin.md §3). The agent (#20) exposes the internal leg-B callterminal.openwith input{uid, cols, rows}and output{session_id}, and reports exit as Eventterminal.exited. The server maps one to the other (authorize and audit, then forward).Finding: wire.md §5 defines terminal offsets as u64, but SQLite INTEGER/BIGINT is signed and migration 0002 stores input_acked as INTEGER. An offset above 2^63-1 cannot be persisted exactly, so restart/resume accounting would fail at the upper half of the wire range. Proposal: add migration 0004 with an eight-byte little-endian BLOB input_acked_u64, use it as the authoritative offset, and retain 0002 input_acked for migration compatibility. I am implementing that representation.
Protocol gap proposal: the mux has no way to retire one terminal attachment without closing the socketpair, and Credit(0) means zero output credit rather than detach. I propose that Close on a nonzero mux stream retire only that stream (code 1000 means detached); Close on stream 0 continues to close the connection. I am documenting this and sending per-stream Close when a browser attachment drops, so the agent lane can match it.
Finding: the requested spawn command is
<self> agent --fd 3, butcrates/infraternal/src/main.rscurrently definesCommand::Agentwithout options, so Clap exits before the socketpair handshake. Proposal: reserve optionalagent --fd <u32>in the CLI now; the agent lane can consume that descriptor when its implementation lands, while the current stub continues returning exit 2. I am adding only this compatibility argument.Finding: Decision 26 requires printing the initial setup capability to logs, but the job contract forbids secrets in logs, files, and argv. Proposal: keep Auth bootstrap unchanged and deliver the newly generated capability only over a same-user Unix socket at /setup-token.sock (inside the private 0700 data directory, socket mode 0600); add an explicit
infraternal server setup-tokencommand that prints it only on operator request. Runtime holds the token in memory and never logs or persists it. This changes Decision 26 behavior and needs owner review; the decision file remains untouched.Finding: the user unit cannot start with public origin https://10.69.69.125:47361; startup fails with
rp_id is not an effective_domain of rp_origin. The WebAuthn RP-ID rule requires a domain-form host, so the listener IP cannot itself be the passkey RP ID. Proposal: keep the listener bound to 10.69.69.125:47361, and use https://10.69.69.125.nip.io:47361 as public origin with a self-signed certificate whose SAN covers both that DNS name and the IP. This preserves the NetBird listener while giving passkeys a valid RP ID; the owner can replace nip.io with managed DNS later.Finished on branch t-server, HEAD
6fa27a3a53.Commits:
a3bf1bfspecifies per-stream Close;0f71e50mounts the authenticated WebSocket and embedded-agent relay;6fa27a3adds the setup/echo/resume integration test.Live URL: https://10.69.69.125.nip.io:47361. The tracer-server user unit is enabled and active, bound to 10.69.69.125:47361; /healthz returned {"status":"ok"} with HTTP 200. TLS is self-signed and the certificate SAN covers both the NetBird IP and DNS origin. The DNS name resolves to 10.69.69.125 and gives WebAuthn a valid domain-form RP ID. Terminal calls await the t-agent lane; no production fake agent was added.
Final gate output (verbatim):
+---------------------------+--------------+
| Rust gate | Result |
+---------------------------+--------------+
| fmt | PASS |
| clippy | PASS |
| test | PASS |
| cargo-deny | PASS |
| static builds | PASS |
+---------------------------+--------------+
stream differential: 134 streams, 11439 schedules, zero divergences
7 pass
0 fail
34390 expect() calls
Ran 7 tests across 2 files. [5.73s]
Independent server review started. Read-only branch t-server, head
6fa27a3a53; dev/base7174d6bf6f. Reviewing dev..t-server against wire/plugin/keywrap and the requested security/resource/history checklist. Experiments and gates will run in a temporary checkout. docs/PROCESS.md is absent at this head.Independent review finding P1: logout/revocation does not remove live terminal authority. websocket.rs:537-538 checks Auth::session only for Frame::Call; AttachTerm/Input/Resize/Credit and output bypass it. Temporary real-WebSocket/software-passkey test review_revoked_socket_cannot_reattach_and_type logged out via /auth/logout (200), reattached stream 3, sent Input(seq=8, bytes=id\n), and received Ack(11). Test fails with: revoked session reattached and typed id: infraternal agent acknowledged byte offset 11. Fix live-socket invalidation including output and recheck before attachment/input.
Independent review finding P1: one stalled consumer blocks all embedded terminal traffic. embedded.rs:768-776 awaits attachment.output.send inside the sole actor; the same actor serves every command/stream. Temporary test review_full_output_queue_must_not_block_shared_actor fills a capacity-1 queue and submits a one-byte Output: process_incoming exceeds a 100ms timeout. Production queue capacity is 4; a fifth undrained event has the same behavior. Shared writer awaits are also unbounded. Isolate per-stream backpressure/catch-up from the shared actor and add a two-client slow-reader regression.
Independent review finding P1: restart loses browser output credit. embedded.rs:699-725 reattach_all emits AttachTerm only and tracks no remaining credit. Test review_reattach_must_restore_output_credit first forwards Credit(1024), replaces the socketpair connection, then records only AttachTerm on the replacement. A compliant infraternal agent starts with no credit, so an attached browser expecting output can hang until it sends additional credit. Reconcile outstanding credit on restart and test real shim resume.
Independent review finding P2: normal stream retirement can fail the entire embedded connection. embedded.rs:850-864 returns output.send(Closed).await.is_ok(); if the browser receiver has already dropped, process_incoming returns false and run_actor invokes connection_lost for all attachments. Temporary test review_stream_close_must_not_restart_all_sessions sends Close(stream=2, code=1000) to an attachment with a dropped receiver and fails. A disconnected recipient must be a local detach, not a global transport failure.
Independent review finding P2: setup-token retrieval is unrecoverable after a pre-enrollment server restart. runtime.rs:245-251 creates the local socket only when bootstrap newly inserts the hash; bootstrap never reannounces an existing setup row. Measured static binary: first setup-token exit=0, response length=44; after terminate/restart with same database: exit=1, response length=0, stderr=setup token unavailable: no active first-run token socket. Unlike the old log handoff there is no retained capability to retrieve. Recommend accepting the private local-socket concept rather than reverting to secret logs, but add an explicit safe operator reset/recovery flow, reconcile keywrap text and Decision 26 with owner approval; enrollment itself remains single-use in the auth transaction.
Independent review history finding: 13 commits, but
0f71e50adds embedded.rs (1035 lines), websocket.rs (918 lines), runtime/setup-socket changes and CLI handoff together (2207 insertions total), contrary to the requested each-module logical slices.b73276c,05d1d61,fa08403mix migrations/runtime/installer with tests, and four early commit messages contain literal backslash-n sequences; several omit alternatives. docs/PROCESS.md referenced by the job is absent. Rework history into gate-passing logical steps before merge.Independent review finding P1: per-user limits are bypassed before Hello, and no handshake/read/write timeout exists. Temporary real-WebSocket test review_half_open_sockets_must_obey_user_limit authenticates one cookie, upgrades 256 sockets without sending Hello, holds them >500ms, and confirms upgrade 257 returns 503. The intended per-user limit 16 is checked only in new_connection after Hello; all 256 process permits can be held indefinitely by one authenticated device. Reserve per-user capacity at upgrade and bound handshake/frame assembly/writer progress. websocket.rs:94-103, 142-158, 319-333, 851-885.
Independent review finding P1: multi-device input offsets collide. Test review_multi_device_inputs_must_not_share_byte_range creates two AttachmentState entries for the same session at ack=0 and routes Input(0,x) and Input(0,y) through process_command. Captured leg-B frames are distinct x and y, both at in_seq=0 (streams 2 and 4). A per-session shim deduplicating byte offsets must drop one legitimate keystroke. Input/ack tracking is attachment-local (embedded.rs:235-239, 570-594; websocket.rs:718-724), without a session-wide writer/offset allocation scheme. Reconcile multi-writer exactly-once semantics in wire.md before implementation; test two real devices against one real shim.
Deployed to the dogfood target (DECISIONS 48), at the owner's request:
kayg: the static binary~/.local/bin/infraternal(t-server6fa27a3, sha256dfdb0af4e58d06b6…), installed with its owninfraternal server install --user --listen 172.18.0.1:7011 --behind-proxy --trusted-proxy 172.18.0.0/16 --public-origin https://infraternal.kayg.org. Unitinfraternal-server.service(user, linger on). Data in~/.local/share/infraternal./opt/infra/traefik/dynamic/infraternal.yml, NetBird-only allowlist, Let's Encrypt cert issued (valid until 2027-01-02)./healthzreturns 200 through Traefik from NetBird.infraternal.kayg.orgresolves too1.kayg.org(144.24.150.105, probably the*.kayg.orgwildcard). It needs an explicit A record →10.69.69.50(DNS only).tracer-serverdisabled).devgets deployed here.Clarification to multi-device finding: contracts/shim.md gives the shim raw Input bytes without sequence numbers; deduplication must live in the infraternal agent/server, not the shim itself. The test proves two distinct payloads share offset 0 on leg B while persistence stores one session-wide input_acked_u64. A session-wide deduplicator would discard one, whereas stream-local deduplication cannot be reconciled with the single persisted session offset on reconnect. No real-engine data-loss result is claimed: the production infraternal agent is still a stub at this head.
Independent review finding P1 / integration blocker: at this head, main.rs:204-207 accepts agent --fd but always prints not implemented and exits 2. EmbeddedAgent starts immediately, retries even with zero clients, and open()/attach() await replies without a timeout while connect failures never drain/reject queued commands (embedded.rs:306-348). Concrete input: infraternal agent --fd 3 exits 2; authenticated terminal.open on the shipped server has no responding process and no result deadline. The integration test uses a deterministic transcript fixture, not a real PTY. Merge/integration must bring the real infraternal agent lane and a real shim test; also reject calls within a bounded time when supervision is unavailable. This is a cross-lane integration dependency, not a request to implement a production fake.
Independent review finding P2: migration 0004_u64_input_offsets.sql resets existing acknowledgement authority to zero. SQLite reproduction: apply migration 0002, insert one session with input_acked=8, apply migration 0004, SELECT input_acked,input_acked_u64 yields legacy=8 and new unsigned offset=0. owned_session_ack now reads only the new blob, so an existing acknowledged range becomes replayable. Backfill the eight-byte offset from every legacy value as part of migration and add an upgrade regression.
Independent review finding P1: terminal.open bypasses the plugin action/audit path. Concrete valid Call(7,terminal.open,{cols:80,rows:24,px_w:0,px_h:0}) succeeds in the integration test, but websocket.rs:558-585/761-788 calls EmbeddedAgent::open before inserting only terminal_sessions. No role/permission check, pending audit, result audit, or audit table exists in the server migrations. The new core-action contract exception only changes descriptor ownership; plugin.md sections 3-4 still require authorization and audit before side effects. Define an explicit core action policy and durable audit path, or obtain an owner-approved contract change before merge.
Independent review finding P1: resource budgets and user-owned PTY counts are unbounded. Concrete request sequence: repeat valid Call(id++, terminal.open, {cols:80,rows:24,px_w:0,px_h:0}) on one authenticated control stream. open_terminal has no per-user session-count check or rate admission (websocket.rs:761-788), and /v1/ws bypasses SourceAdmission entirely (runtime.rs:426-429). The 32-attachment bound does not bound sessions opened and left detached. Queue item counts also allow 4MiB payloads: an attachment output queue alone permits 16MiB, times 32 attachments/socket and 16 active sockets/user = 8GiB of configured payload slots, before input queues and shared channels (this is a bound calculation, not measured saturation). No at-scale session/RSS/echo/resume benchmark is supplied, and bench/integration gates only print not yet implemented. Set explicit byte and per-user/process session budgets, measure expected scale, and test flood/slow-client isolation.
Independent review last-typer size finding / cross-lane obligation: concrete sequence A Resize(80x24), B Resize(120x40), A Input(seq=0,x). embedded.rs:602-612 forwards both Resize frames immediately; Input carries no dimensions and the server retains no per-device dimensions or active-writer state. The final server trace therefore contains no restore to 80x24 when A types. Decision 74 must be implemented by the real infraternal agent using each leg-B stream identity; that lane is absent here and the single-stream transcript fixture cannot demonstrate it. Require a two-device size-switch integration test before merge.
Independent review Close contract finding P2: stream-0 Close(code=1000, reason=done) reaches websocket.rs:720 default Err because only odd nonzero Close is handled. run_socket then emits Close(code=1011, connection ended), whereas normal nonzero Close removes one attachment. The new wire semantics (connection Close vs stream detach) are reasonable, but clean control Close needs its own normal shutdown path; test both leg A and local mux. RESUME_NEXT ordering is unambiguous; keep it, with handshake deadlines and compatibility tests.
Bug found while deploying (DECISIONS 26): after any restart before setup is complete, the owner can no longer get a setup token. The token is created once at first start and stored only as a hash. After a restart, no new token socket is started, so
server setup-tokenanswers "no active first-run token socket" forever (the old socket file stays, stale). The owner hit this on monitor01, after I restarted the service to add --web-root.Expected: on each start, while no owner exists, mint a fresh single-use token, invalidate any older one, and serve it on a fresh socket (and remove a stale socket file on start). Add an integration test: start → restart → setup-token works → setup completes.
Workaround applied on monitor01: an empty data directory (no data existed) and a fresh start.
Independent review integration contract mismatch: the pre-existing #19 alignment comment says browser action ssh.session.open maps to internal terminal.open {uid,cols,rows}, with terminal.exited Event and authorize/audit before forwarding. This head instead accepts browser terminal.open, sends leg B {cols,rows,px_w,px_h} with no uid (embedded.rs:50-64, 513-522), and read_incoming rejects all Event frames (lines 871-903). The deterministic fixture uses the branch-specific shape, so the green fixture test cannot catch this documented #20 interface mismatch or exited-session persistence. Reconcile both lanes/contracts and test open + exit + restart using a real shim before integration merge.
History correction after checking each commit stat:
b73276cis a clean migration-only commit (one SQL file, 12 lines), and it should remain separate.05d1d61combines runtime with its inline tests,fa08403combines installer/CLI/dependency/tests, and0f71e50combines both major modules with unrelated setup-token handoff. Three early message bodies (796873d,b73276c,05d1d61), rather than four, contain literal backslash-n paragraph separators. The substantive history blocker is still the explicitly requested module/test/dependency separation.Starting server work on t-server at
6fa27a3: discoverable passkey authentication, public setup status, and restart-safe setup token/socket. Contract proposal: additive status action returning only setup_required; login-start accepts omitted user_id, empty allowCredentials and per-credential PRF salts; preserve existing identified path for regression tests. Decisions 23, 24, 26, 86. docs/PROCESS.md is absent in this worktree; will apply the supplied Definition of Done.Finding: auth/crypto.rs prf_salt is global; auth/setup.rs bootstrap ON CONFLICT DO NOTHING loses token access on restart. Proposed additive contract: new credentials use per-user PRF input, existing credentials retain legacy input via nullable migration metadata. Discoverable login returns no credential list; authenticated wraps adds prf_input so client can request a local credential-specific PRF evaluation after signed login. evalByCredential with empty allowCredentials is invalid WebAuthn, so reject that alternative. No identity/credential enumeration before authentication.
Independent review performance evidence: canonical head x86 static binary is 14,617,568 bytes; final gate aarch64 is 12,239,072 bytes. cargo bloat --crates at head vs dev
7174d6b: .text ~9.3 vs ~6.2 MiB; openssl_sys 1.9 vs 1.4 MiB, std 1005.8 vs 598.0 KiB, server 385.1 vs 172.9 KiB, h2 +503.5 KiB, hyper +159 KiB, regex machinery +277.4 KiB, tracing_subscriber +71.4 KiB. Profiler attribution is approximate under LTO; unknown/C and rodata are not fully attributed. The profiled base stripped with GNU strip measured 9,612,760 bytes, rather than reproducing the historical 9.3 MB exactly; measured head growth is 5,004,808 bytes (~52.1%). Measure whether h2/EnvFilter are worth their size; keep the owner-approved OpenSSL verifier (Decision 86). Zero-client RSS samples were 10,712 and 8,556 KiB (separate fresh starts), 12 threads, 0 CPU ticks over 10 seconds in both samples. Repeat load was 6.93/9.09/9.67 -> 7.70/9.18/9.70, on eight i9-9900K KVM vCPUs, Linux 6.12.111, Rust 1.99.0, Zig 0.15.2, with concurrent builds. Not a pinned-VM latency or at-scale benchmark.Independent server-core review finished: verdict REWORK at HEAD
6fa27a3a53(base7174d6bf6f). Blocking findings: live-socket revocation bypass; shared-actor slow-client denial of service; pre-Hello per-user admission bypass; multi-device input-offset authority; lost restart credit; missing authorization/audit path and session/byte budgets; bounded supervision failure responses; unresolved real-infraternal-agent/exit/last-typer integration. Also fix legacy acknowledgement backfill, Close failure scope, pre-enrollment setup-token restart/lifecycle and requested commit granularity. Recommend accepting the private setup socket concept with fixes and owner decision/contract reconciliation. Six independent invariant probes failed as documented; the submitted fixture gate does not prove a real PTY. Findings, exact probes, size/RSS/load measurements and full final gate output appended to /home/kayg/.local/state/codex-jobs/infraternal/review-server/findings.md. Original worktree remains clean; 156 tracked files matched the pristine experimental checkout. Final scripts/verify fast exit 0; complete output verbatim follows:Concurrency note at final cleanup: the reviewed worktree advanced externally to HEAD
6ba20aa42b; git status now reports:M crates/infraternal-server/src/auth/setup.rs
M crates/infraternal-server/src/auth/tests.rs
This review was fixed to requested head
6fa27a3a53; the pristine gate and experiments used that snapshot (156 files matched before the concurrent update). I made no source edits or commits. Later commits/uncommitted fixes were not reviewed, so the REWORK verdict and findings must not be represented as a review of the new head. Temporary checkout and all three failed-probe data directories have been removed.Red test evidence: target/passkey-job/restart-red.log, setup_restart_rotation_expiry_and_parallel_redemption failed with restart must issue a fresh setup capability (0 passed, 1 failed). Bootstrap now replaces the hash atomically; test verifies old-token denial, expiry and parallel single-use enrollment. Full fast gate is running including both static architectures.
Finding: target/passkey-job/prf-red.log reproduces the global input bug with two distinct setup UUIDs: PRF input must be per-user (0 passed, 1 failed). Migration 0005_passkey_prf_input.sql is additive and preserves NULL as the legacy derivation. New registration persists the exact user-bound input; identified login supports mixed legacy/new credentials through a nonempty-list evalByCredential. Auth tests pass; corrected full gate running after extracting credential persistence to satisfy the 100-line Clippy limit.
Discoverable ceremony module committed
efac7d6with full fast gate. Direct software-authenticator regression covers empty allowCredentials, no pre-auth PRF/credential enumeration, missing/15-byte/unknown handles, unknown credential, replay and stale counter. HTTP omitted-ID baseline is 400 (target/passkey-job/discoverable-red.log). Performance check plan before final measurements: one owner with up to two passkeys, pending-state maximum 1024; local release targets challenge p99 below 1 ms and signed finish p99 below 10 ms. These auth measurements are separate from decision 41 echo/phone budgets. No new library or engine selected.Finding: public status returns exactly one boolean and no-store; omitted user_id now routes to signed resident authentication. Native real-router browser gate passes 4 isolated Chromium 149 scenarios, including non-UV assertion rejection and DEK preservation (target/passkey-job/native-browser-final.log). Production t-web/dist paired with the actual Rust router also passes setup, storage-cleared discoverable login and key unwrap (target/passkey-job/paired-browser.log). Restart integration now reopens one file SQLite database, replaces the stale private socket, retrieves the fresh CLI-protocol token and completes enrollment. Full gate running.
Server lane finished: t-server
7948ff00fb. Full fast gate output verbatim, part 1/3:Server lane finished: t-server
7948ff00fb. Full fast gate output verbatim, part 2/3:Server lane finished: t-server
7948ff00fb. Full fast gate output verbatim, part 3/3:Final server head
7948ff00fb; client head639ac27. Atomic commits on separate branches, clean worktrees, no push/deploy/merge. Implemented discoverable login, exact public setup status, fresh ownerless restart token, stored per-user PRF input with legacy preservation, and status-first one-form client screens. PRF requires a second local prompt after signed identity resolution. Self-review and reproducible evidence are docs/verification/discoverable.md and apps/tracer/README.md. Screenshots are client apps/tracer/test-results/terminal-first-run-passkey-29742-esume-and-the-latency-probe/{setup,signin}-{light,dark}.png at 390x844; inherited CSS is dark-only. No physical iPhone, independent visual/security approval or perf-VM claim.Actual process restart output verbatim:
Paired production browser output verbatim:
Native browser output verbatim:
Release scale output verbatim (200 samples per operation, 2 passkeys, pending limit 1024; load and memory caveats in docs):
Independent read-only audit started: server branch t-server, base
6fa27a3, head7948ff00fb; client branch t-web, base1b4c907, head639ac27684. Experiments will use disposable /tmp worktrees. Auditing discoverable login, setup-token secrecy/concurrency, PRF compatibility and admission claims.Finding F1 (P1): successful discoverable login + corrupted wrap + logout 429 -> expected revoked session; actual /auth/session 200 and production reload opens terminal. apps/tracer/src/auth.ts:151 suppresses logout rejection; main.ts:31 trusts remaining cookie on reload. Failing Chromium 149 real-router/SQLite test saved at /tmp/review-auth-browser-test.mjs, output /tmp/review-auth-unwrap-red.log. Logout 429 injected; real admission reachability being checked. Decisions 97/98 are absent from both supplied snapshots (DECISIONS ends at 86).
auth-ux finished: server
7948ff0, client639ac27. Audit running as review-auth-ux, tracked on #29. One-prompt sign-in (D108) is tracked on #30. The fix-server round is #28.F2 — P2: retained public identified login discloses credential IDs and account existence
Input: unauthenticated POST /auth/login-start {user_id:known UUID} versus unknown UUID, pinned Origin → expected: no caller can list credentials and no identity oracle → actual: known UUID returns 200 with 1 allowed credential (and PRF input), unknown UUID returns 401.
Evidence: auth/http.rs:219–224, auth/webauthn.rs:188–222. Failing test audit_no_public_credential_listing in /tmp/review-auth-cases.rs; output /tmp/review-auth-server-red.log. Inherited identified endpoint deliberately retained by this change; default discoverable response itself correctly returns an empty list. UUIDs are high entropy, so this is disclosure given a known/leaked UUID, not practical brute-force enumeration of every user. Client stores UUID nonsecretly. Remove the public fallback or bind its credential disclosure to authenticated authorization; direct Auth::login_start tests do not require publishing it over HTTP.
F3 — P2: login challenge is not bound to the initiating browser/session
Input: browser A starts discoverable login; a correctly signed proof and ceremony are finished in cookie-free browser B → expected per checklist: reject B (401), bind ceremony to A → actual: 200 and a fresh session in B; start issues no binding cookie.
Evidence: auth/http.rs:219–237 never supplies a browser binding; auth/mod.rs:83–86 Login holds only state and expiry; auth/webauthn.rs:279–335 keys state only by the bearer ceremony. Failing test audit_ceremony_browser_binding in /tmp/review-auth-cases.rs; output /tmp/review-auth-server-red.log. Signature/challenge/RP/exact-origin/UV remain enforced; this is a missing browser transaction-binding property, not demonstrated forgery or cross-origin JavaScript exploit. Same-Origin policy blocks ordinary cross-site browser requests; stolen or relayed ceremony/proof remains redeemable in another client. Also inherited for identified login. Add an unguessable browser transaction binding or have the owner explicitly resolve the checklist requirement; this audit does not change the contract.
F1 real admission reproduction: 120 public status actions then authenticated logout -> expected revoked; actual 429 and live_session=true. audit_logout_survives_admission_exhaustion fails. Independent parallel equal-counter regression passes (exactly one success); wrong origin, no UV, expiry denied. Evidence /tmp/review-auth-cases.rs and /tmp/review-auth-server-red.log.
Independent audit finish evidence: server scripts/verify fast (exit 0). Server head
7948ff00fb; client head639ac27684. Original branches t-server/t-web remain clean; no commits, push or deploy. Disposable /tmp worktrees removed.Verbatim output:
Independent audit finish evidence: client scripts/verify fast (exit 0). Server head
7948ff00fb; client head639ac27684. Original branches t-server/t-web remain clean; no commits, push or deploy. Disposable /tmp worktrees removed.Verbatim output:
Independent audit FINAL VERDICT: REWORK. Server head
7948ff00fb; client head639ac27684. Original branches t-server/t-web remain clean; no commits, push or deploy. Disposable /tmp worktrees removed.Independent audit of #19 / #21
Server 6fa27a3..7948ff0; client 1b4c907..639ac27. Original worktrees read-only.
F1 — P1: failed unwrap can retain a usable session
Input: successful discoverable login, corrupted envelope nonce, logout returns 429 → expected: new session revoked and reload remains signed out → actual: client attempts logout, suppresses its failure; real Rust /auth/session returns 200 and production reload opens terminal.
Evidence: apps/tracer/src/auth.ts:152; main.ts:30 accepts the cookie on startup without unwrap. Failing executable browser test: /tmp/review-auth-browser-test.mjs (copy into scripts/browser-auth/check-audit.mjs in disposable server tree and run with production client dist); /tmp/review-auth-unwrap-red.log. Chromium 149.0.7827.55, real Rust router/SQLite/WebAuthn/PRF/GCM; only logout 429 injected. Real admission reachability tested separately below. Requested fix: make incomplete unlock sessions unusable or ensure authenticated revocation cannot be denied by admission; never silently treat revocation failure as complete.
Audit baseline gap
DECISIONS.md in both exact snapshots ends at 86; decisions 97 and 98 are unavailable. Decision 26 still specifies a log banner, while new contract tail specifies private socket and its opening still authorizes a secret banner. No owner decision was changed by this audit. This is a documentation/baseline gap, not a runtime test failure.
Verified evidence so far
Owner design note: constant PRF input (item 4; no decision changed)
A stable, domain-separated public constant with prf.eval and empty allowCredentials preserves credential separation: WebAuthn PRF is per credential and UV-backed; different passkeys do not produce a shared secret just because their input is identical. HKDF salt plus unchanged info/AAD infraternal/keywrap/v1||passkey still separates users and authenticates the envelope context. Swapping envelopes between users still fails GCM, and RP/origin/challenge/signature/userHandle/counter checks do not depend on PRF input. The signed assertion can supply identity and the PRF output in one prompt, with output removed before transmission. Source: https://www.w3.org/TR/webauthn-3/#prf-extension .
The property lost is input-level separation when the SAME credential/PRF secret is reused across logical accounts or deployments sharing an RP: one exported PRF result at the constant is reusable for those envelopes (with their public HKDF salts/AAD), whereas distinct per-user inputs require distinct PRF evaluations. Current credential primary-key ownership prohibits multiple accounts per credential in one database. The v2 input is derived from a UUID, not a secret: the assertion exposes that UUID as userHandle, so it is not strong defense against code already able to request same-RP WebAuthn assertions. Constant input does not introduce credential enumeration and is not evidence of weaker signature auth or cross-credential key isolation under the present schema.
Existing v2 ciphertext cannot be relabeled to a constant: keep stored input routing and the second-prompt fallback for incompatible envelopes, or rewrap the same DEK while both inputs are available and atomically update envelope/input. Existing NULL/v1 envelopes already use a constant. A per-install constant must survive restarts/restores; rotating it strands wraps. This is concrete reasoning for the owner, not a decision or recommendation to change settled contracts.
F2 — P2: retained public identified login discloses credential IDs and account existence
Input: unauthenticated POST /auth/login-start {user_id:known UUID} versus unknown UUID, pinned Origin → expected: no caller can list credentials and no identity oracle → actual: known UUID returns 200 with 1 allowed credential (and PRF input), unknown UUID returns 401.
Evidence: auth/http.rs:199–204, auth/webauthn.rs:188–222. Failing test audit_no_public_credential_listing in /tmp/review-auth-cases.rs; output /tmp/review-auth-server-red.log. Inherited identified endpoint deliberately retained by this change; default discoverable response itself correctly returns an empty list. UUIDs are high entropy, so this is disclosure given a known/leaked UUID, not practical brute-force enumeration of every user. Client stores UUID nonsecretly. Remove the public fallback or bind its credential disclosure to authenticated authorization; direct Auth::login_start tests do not require publishing it over HTTP.
F3 — P2: login challenge is not bound to the initiating browser/session
Input: browser A starts discoverable login; a correctly signed proof and ceremony are finished in cookie-free browser B → expected per checklist: reject B (401), bind ceremony to A → actual: 200 and a fresh session in B; start issues no binding cookie.
Evidence: auth/http.rs:199–217 never supplies a browser binding; auth/mod.rs:82–85 Login holds only state and expiry; auth/webauthn.rs:279–335 keys state only by the bearer ceremony. Failing test audit_ceremony_browser_binding in /tmp/review-auth-cases.rs; output /tmp/review-auth-server-red.log. Signature/challenge/RP/exact-origin/UV remain enforced; this is a missing browser transaction-binding property, not demonstrated forgery or cross-origin JavaScript exploit. Same-Origin policy blocks ordinary cross-site browser requests; stolen or relayed ceremony/proof remains redeemable in another client. Also inherited for identified login. Add an unguessable browser transaction binding or have the owner explicitly resolve the checklist requirement; this audit does not change the contract.
F1 real admission reachability confirmed: audit_logout_survives_admission_exhaustion sends 120 public status actions, then authenticated logout → expected revoked session → actual 429 and live_session=true. This is not only an injected-response scenario. /tmp/review-auth-server-red.log.
Independent defensive regression audit_parallel_discoverable_counters_and_fail_closed_inputs passes: parallel proofs carrying the same nonzero counter permit exactly one finish; wrong origin, UV=false and expired discoverable assertions all deny. The source gates also exercise handle mismatch/unknown/missing/replay, concurrent setup authority and legacy ciphertext.
Re-measurement on this box (release, n=200 per row)
/tmp/review-auth-scale.log: one owner, two ES256 credentials, 1024 pending cap; load 5.52 / 8.52 / 7.05, other compilation active. Identified challenge p50/p99: 58/194 us at 0..200 pending; 58/198 us at 824..1024. Discoverable: 3/20 us at 0..200; 5/48 us at 824..1024. Signed discoverable finish at 1024: 229/2719 us. Entry 1025 denied. Inline Login 112 bytes, 114688 bytes for 1024, excluding heap/hash-table allocations. Whole-process baseline RSS 12976 KiB, full identified capacity 14312 KiB (+1336 KiB); discovery after reuse stays 14312 KiB. This reproduces the claim with its limitations; it is not an isolated memory measurement, disk benchmark or phone/network latency measurement. A final timing rerun without this audit's compilation is recorded below.
Final measurements and verdict
Final release rerun after this audit's builds finished (/tmp/review-auth-scale-final.log): n=200; load 5.10 / 7.89 / 7.05. Identified challenge p50/p99 85/265 us at 0..200, 66/135 us at 824..1024. Discoverable 1/3 us at 0..200, 2/3 us at 824..1024. Signed finish at 1024 pending: 325/622 us. 1025 denied; inline payload 112 bytes × 1024 = 114688 bytes. Whole-process RSS 12780 → 14096 KiB (+1316 KiB); discovery phase reuses allocated storage. This is a fresh run of the independently rebuilt release test binary. No exact heap, physical iPhone or pinned perf-VM claim.
Normal failed-unwrap control passes: real session returns 401 and reload stays signed out when logout succeeds (/tmp/review-auth-revokes.log).
Verdict: REWORK, ranked:
All three have failing tests; F1 has both production-browser and real admission reproductions. Signature/Origin/UV, single-use/expiry, handle ownership, latest counters, concurrent owner authority, rotation/private socket/stale path, legacy migration/AAD and normal revocation checks pass within stated evidence. Browser binding/identified privacy are inherited properties deliberately retained or extended by this lane; they are findings against the requested audit checklist, not all newly introduced regressions.
Both snapshot fast gates pass. Server test gate: 22 passed, 1 ignored; client snapshot Rust server tests: see complete verbatim log. Rust fmt/clippy/test/cargo-deny/static builds PASS for each; TypeScript protocol 7 pass, 0 fail, 34390 assertions, 134 streams/11439 schedules/zero divergences. Client tsc/build pass; real native browser gate 4 scenarios pass. Independent adversarial tests: 1 pass/3 fail, plus actual legacy migration 1 pass; browser unwrap/logout-429 test fails as expected.
Verbatim fast output preserved at /tmp/review-auth-server-fast.log and /tmp/review-auth-web-fast.log and posted on #19. No source edits or commits in original worktrees. Temporary worktrees are removed after preserving tests/logs/screenshots. DECISIONS 97/98 remain unauditable because absent from the supplied snapshots; decision 26/log contract contradictions need owner reconciliation.
Final release measurement output, verbatim:
Independent failing server tests, verbatim:
Production-browser failing test, verbatim:
Closed by the orchestrator during the 2026-10-28 handover (#254), on the owner's request to close finished issues. Evidence: Server core merged and deployed (t-server 7551ff6/f1b996c and integration
acf83c7are in dev and prod); audit findings fixed under #28/#29. Reopen if anything is still missing.kayg referenced this issue2026-10-28 17:53:54 +00:00