| D-M2-45-1 |
2026-10-20 |
OQ-03 (brief Design note 0d): path A (a token of claude setup-token in CLAUDE_CODE_OAUTH_TOKEN) and path B (/login with the login in a file of the home, on macOS in the keychain with the file as fallback) are documented, so the profile claude with personal is built (profiles.PersonalLoginDocumented); the quotes stand in 24 7.4. The question itself stays with the owner. docs/OPEN-QUESTIONS.md and docs/RISKS.md do not exist on this integration base, so OQ-03, RK-30 and RK-23 are updated by the port to main, not here. |
The brief builds personal only when one of the two paths is documented; the three pages were read on 2026-10-20 and contain no restriction to non-interactive use. The missing files are a known gap of this base (D-M2-42-20). |
| D-M2-45-2 |
2026-10-20 |
HostChannelHello carries clock_leeway_ms, idle_timeout_ms and detach_grace_ms (auth.clock_leeway, terminal.idle_timeout, terminal.detach_grace); the host service applies those of the data connection that carries the handshake (24 2.2 point 5, 5.3, 6.3), and every attach refreshes them. |
These are server values the host service must apply; hello already carries server values and comes on every data connection, and the server reads its configuration only at start, so no extra push is needed. |
| D-M2-45-3 |
2026-10-20 |
Brief Design note 2b was found merged: ready is sent again after a committed change of the declaration by ControlLinks.SendReady (SetLlmCredentials, afterConfirm), on the node that served the request. The log tail observer of the brief was not built. The case across nodes stays with M2-47. |
Nothing is built twice (brief "Boundaries"); the merged path already sends ready after the commit, and the routing of control links across nodes is M2-47's. |
| D-M2-45-4 |
2026-10-20 |
session.close and a close of the data connection by the server with 4100 end the PTY with its tree without session.ended; every other close of the data connection or its loss keeps the PTY detached until the next session.open or terminal.detach_grace; the host service never redials a data connection (24 6.2, 13). |
M2-44 closes the host side with 4100 and sends session.close for every end, so the session is closed already; one data connection per grant (D-M2-44-4) leaves no other way back. |
| D-M2-45-5 |
2026-10-20 |
internal/session/token.go is a second, smaller implementation of the ADR 0067 profile in the host module (compact JWS, alg exactly EdDSA, no crit, jku, x5u, x5c, zip or jwk, the key with the kid from the jwks of hello), tested with the Ed25519 example of RFC 8037 A.4 and the negative cases (TestVerifyRFC8037Vector, TestVerifyOrder, FuzzVerifySessionToken; 24 5.3, ADR 0068). |
The host module cannot import the server's internal/kernel/jose; the standard library covers the subset the token needs. |
| D-M2-45-6 |
2026-10-20 |
Check order of 24 5.3 with two added steps: step 8 (att: open needs a session without a live PTY, attach one with a live PTY of the same sub, osu and prf) and step 12 (wdi present exactly for claude with att = open, an index into the reported list of osu). The binding at step 11 is written under a lock before step 12 and stays when a later step fails; the jti is remembered at step 6 before the later steps; the jti memory lives only in memory. |
24 5.3 leaves open which session an attach may name and when wdi may be present; a mismatch can only come from the server (R-TM-16). A replay of a token that failed later must stay token_replayed; the server never reassigns a session user (T12); after a restart every earlier session is ended (host_restarted), so a memory on disk protects nothing more. |
| D-M2-45-7 |
2026-10-20 |
Close order after session.ended: the host service keeps the data connection open and closes it with 1000 only when the server has not closed it within the opening window (HostSessionOpen.expires_at minus the arrival of session.open; 24 6.1 step 7, 13). |
A close by the host right after the report would reach the relay before the end and give the device 4102 instead of the code of the end (M2-44). |
| D-M2-45-8 |
2026-10-20 |
A fault of the encrypted stream after the opening (first transport message not start, failed decryption, a frame frame.Decode refuses, a second start) ends the session with open_failed and handshake_failed (24 5.4, 10.3). |
Spec gap: 03 terminal_sessions.close_reason has no value for it and M2-43 accepts no other host reason; this keeps the manipulation signal 4104 for the device without a change of the core contract. |
| D-M2-45-9 |
2026-10-20 |
TERM is xterm-256color for every profile; on Windows the environment is the block of the helper's token without inheritance plus TERM; command starts the resolved path of program (24 11.1). |
The renderer of the clients is xterm (24 12); the token block is the Windows equivalent of a login environment; the resolved path is the one HD11 checked. |
| D-M2-45-10 |
2026-10-20 |
A login shell starts with argv[0] set to - plus the base name of the shell and must be an absolute path listed in /etc/shells, else pty_failed; on Windows the login shell is %SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe -NoLogo (24 7.4, 11.1). |
The login(1) convention works for every POSIX shell, fish and tcsh; /etc/shells is the system's list of valid login shells; Windows PowerShell is present on every Windows 10 and 11. |
| D-M2-45-11 |
2026-10-20 |
Session ops of the local channel: pty.start, pty.resize, pty.stop from the service, pty.started, pty.exited, pty.failed from the helper; a kind 0x01 body is the 16-byte session ID followed by one data frame. For claude at organization the API key is read from claude_api_key for each start and sent only in that pty.start. Addition: pty.start carries the declaration and, for claude, the path of the entry wdi names in the list after HD7; the helper starts only when that path is in the working directories of its last check.run and its identity equals the one it reported then (24 7.4, 16 4.4 point 5). |
The service computes the list after HD7 and holds the declaration; the identity check stays in the helper, which resolves as the session user. A key read per start never lives longer than one start in the service. |
| D-M2-45-12 |
2026-10-20 |
Per opening exactly one Light run that asks only the helper of osu (doctor.Runner.RunLightUser; the other helpers keep their last answer, whose age the periodic run of every host_service.credential_check_interval bounds) plus the checks of the service including HD11 for the places for all. Mapping: HD10 failed or a failed peer check of a session channel gives helper_untrusted (no new session, all end); HD6 failed gives subscription_login_detected (all end); HD11 failed at a place for all gives shared_location (all end); HD11 failed for one session user gives shared_location for that session user only; any other finding that refuses a start refuses new sessions with pty_failed without ending running ones (24 7.4). |
The open time would otherwise grow with the number of session users. The doctor run of the administrator ends the service for the other findings (M2-42), so the gate only has to refuse new sessions. |
| D-M2-45-13 |
2026-10-20 |
Every end kills the tree at once without a grace period. Linux uses the delegated cgroup subtree of the helper unit (Delegate=yes, one cgroup s-<session id> per session) instead of a systemd scope of the session user; macOS keeps a run file of the live sessions in <home>/.config/bothy-host/run (24 6.5, 16 4.4 point 2). |
Every end is the person's close or an enforcement that 8.3 wants immediately. A session user without a login session has no user manager that could hold a scope. launchd does not end process groups created with setsid when the helper exits. |
| D-M2-45-14 |
2026-10-20 |
Replay order: replay_begin, the scrollback as it was when start arrived, replay_end, then the output since start, then live output; a wrapped buffer is cut after its first line feed (24 6.2). |
No half line, half UTF-8 sequence or half escape sequence opens the replay; the snapshot at start keeps the output that arrives during the local confirmation apart from the replayed buffer. |
| D-M2-45-15 |
2026-10-20 |
Local confirmation: the agent bothy-host confirm-agent runs in the desktop session; the host service creates the endpoint; the peer rule refuses the service user, configured and former session users and every user who does not own the active console session; an attach is confirmed too. Deadline (brief Design note 15 d): on a host whose last report says require_local_confirm, the pairing of both sides meets the server deadline; the host bounds start by expires_at and the consent by host_service.local_confirm_timeout (24 6.4, 16 4.4 point 7). |
The service cannot show anything in another user's desktop; a long-running process of a session user must not approve PTYs; 24 6.4 names the person logged in at the machine, which a configured user could contradict; an attach hands the scrollback and the live stream to the device; the consent window may be longer than terminal.open_timeout. |
| D-M2-45-16 |
2026-10-20 |
A broken session channel to a helper (the helper ended its PTYs, D-W-90) ends its sessions with process_exited without an exit code (24 6.2, 10.3). |
Spec gap: M2-43 accepts no closer host reason, and 10.3 has no close_detail for it. |
| D-M2-45-17 |
2026-10-20 |
Base: no contract commit was needed; the brief and the English spec of origin/main were read; the chapters of this base are German and get English insertions like those of M2-44; the dependency M2-44 is merged into the branch (stacked build at 2c4bb2ed). |
The integration base carries the German chapters; the insertions keep the existing text and add the built behaviour in the language of the brief. |
| D-M2-45-18 |
2026-10-20 |
The platform suite and the unit tests of services/host run on throwaway GitHub-hosted runners through host-platform.yml (D-M2-42-19); TestSessionCgroup and the Linux PTY tests need a delegated cgroup, so they run there as root. |
No workstation may create users, services or cgroups for a test; the hosted runner is disposable and gives the rights. |
| D-M2-45-19 |
2026-10-20 |
The integration tests of internal/session run the production helper.Serve in a subprocess of the test binary over a real local channel with the unchanged peer check; the PTY supervisor of that subprocess uses ProcessGroupOnly (no delegated cgroup in an unprivileged test), which cmd/bothy-host never sets. |
The tests exercise the real helper and peer check without administrator rights; the cgroup path is proven by TestSessionCgroup on the platform runner. |
| D-M2-45-20 |
2026-10-20 |
HD11 checks the working directories of a session user only as far as HD7 keeps them: the helper reports one code per working directory (HelperResult.WorkdirCodes) besides the code of the home and the configuration folder, the service adds an HD11 entry only for a directory of the list after HD7, and the entry scan covers only that list. |
A folder that two session users list drops from both lists (24 2.2 point 6) and is then no place of either; checking it anyway blocked both session users with shared_location and broke TM-T29 (c), where the further directory of B must still open. Place: internal/doctor (checkPlaces, workdirPlaces, the scan request of Runner.RunFull). |
| D-M2-45-21 |
2026-10-20 |
An opening registers its session for the PTY only under the lock of the session manager and only while its data connection is still the current one of the session, the session was neither closed (session.close, the end of all sessions) nor ended, and the policy of the gate read under the same lock still admits its session user; a session that ended while the helper started its PTY gets a second pty.stop. Only a periodic run, which asks every helper, lifts a recorded peer_mismatch; the Light run of one session user answers the other helpers from the cache and lifts nothing. |
Review and red team found orphan or duplicate PTYs when an end, a newer session.open or a finding of a run came while an opening waited in the gate, the confirmation or pty.start. Place: internal/session (claimSession, EndAll, Gate.apply). |
| D-M2-45-22 |
2026-10-20 |
The PTY supervisor on Linux and macOS keeps its copy of the terminal side of a session until Kill; reading the PTY ends with Kill, which the helper calls at most exitDrain (a protocol constant of the helper) after the leader exited. |
macOS discards the unread output of a PTY when the last terminal descriptor closes, so a command profile that printed and exited at once lost its output on a hosted runner. Place: internal/pty/pty_unix.go. |
| D-M2-45-23 |
2026-10-22 |
Port onto main: the ticket was built on an integration branch whose spec chapters were still German; on main the English chapters 02, 16 and 24 and ADR 0068 carry the same insertions, the changelog entries and these rulings are fragments, the generated REST layer, Go client and SDK models are regenerated, OQ-03, RK-30 and RK-23 record the result of D-M2-45-1 and the tree end of D-M2-45-13, and doctor.Runner.RunFull keeps main's snapshot for the entry scan (the checks record the helper answers through recording, the scan reads the snapshot itself). |
The integration branch predates the English translation and the fragment rule (PL/24); the content of the rulings is unchanged. |