Developer ReferenceBehavioral Specs

Capsule daemon — Behavioral Spec

Behavioral invariant contract for the in-container capsule daemon — primarily crates/jackin-capsule/src/daemon.rs and its daemon/ subsystems. This is the oracle for the Phase 2 daemon decomposition: any split into client registry / session supervisor / active-client policy / status / clipboard / git-PR watch / usage / control routing must preserve every INV below.

Scope

Starts where the capsule process starts (run_daemon after launch has handed off the container). Does not cover host-side launch (see runtime/launch.rs) or the attach wire framing schema (jackin-protocol). Ends at cleanup classification and host-visible status publication.

Subsystems

Roadmap-named ownership boundaries used to group invariants:

  1. Client registry / active-client policy
  2. Session supervisor
  3. Input routing (keyboard / mouse)
  4. Status model / publication
  5. Clipboard transfers
  6. Git / PR watch
  7. Usage totals
  8. Control-protocol routing + cleanup

Behavioral invariants

Client registry / active-client policy

INVDescriptionVerify byTests
INV-D1A second attach client displaces the previous client — only one interactive attach stream is liveModule docs + Multiplexer::client.attach replace path; Hello frame displaces previous client (daemon.rs header)jackin_capsule::daemon::tests::reattach_updates_capabilities_without_resetting_model_palette
INV-D2Reattach updates client capabilities without wiping the model color palettesession_terminal_carries_the_attached_client_palette / reattach capability mergejackin_capsule::daemon::tests::reattach_updates_capabilities_without_resetting_model_palette jackin_capsule::daemon::tests::session_terminal_carries_the_attached_client_palette
INV-D3First-attach wipe policy erases the screen only on first attach and resize, not on every reattachwipe_policy_erases_only_on_first_attach_and_resizejackin_capsule::daemon::tests::wipe_policy_erases_only_on_first_attach_and_resize
INV-D4Drain-and-exit delivers a shutdown frame to the attach client before closing the socketdrain_and_exit_delivers_shutdown_before_closing_attach_socketjackin_capsule::daemon::tests::drain_and_exit_delivers_shutdown_before_closing_attach_socket

Session supervisor

INVDescriptionVerify byTests
INV-D5Spawn rejects agents outside the allowlist before opening a PTYspawn_request_rejects_agent_outside_allowlist_before_pty_spawnjackin_capsule::daemon::tests::spawn_request_rejects_agent_outside_allowlist_before_pty_spawn
INV-D6Spawn-failure dialog stays open until the operator dismisses itspawn_failure_popup_stays_open_until_dismissed / open_spawn_failure_dialogjackin_capsule::daemon::tests::spawn_failure_popup_stays_open_until_dismissed
INV-D7Zoom state is independent per tab; killing a zoomed pane clears only that tab's zoomzoom helpers in multiplexer session lifecyclejackin_capsule::daemon::tests::zoom_state_is_independent_per_tab jackin_capsule::daemon::tests::killing_zoomed_pane_clears_only_owning_tab_zoom
INV-D8Removing an exited session retires its codename and updates tab labels without dropping other tabsremove_exited_session / mark_agent_session_exitedjackin_capsule::daemon::tests::remove_exited_session_retires_codename_inv_d8

Input routing

INVDescriptionVerify byTests
INV-D9Modified clicks in mouse-enabled panes forward to the PTY; URL hover does not forwardmouse_input dispatch pathsjackin_capsule::daemon::tests::modified_click_in_mouse_enabled_pane_forwards_to_pty jackin_capsule::daemon::tests::modified_url_hover_renders_visible_target_without_forwarding_to_pty
INV-D10Status-tick select arm stays above PTY output so status refresh cannot starve on busy PTY streamsselect-loop arm order in run_daemonjackin_capsule::daemon::tests::status_tick_select_arm_stays_above_pty_output jackin_capsule::daemon::tests::ready_status_tick_wins_over_ready_pty_output
INV-D11PTY OSC 8 hyperlinks emit from frame metadata; unsafe/untrusted hyperlinks do notcompositor hyperlink emissionjackin_capsule::daemon::tests::pty_osc8_hyperlink_emits_from_frame_metadata jackin_capsule::daemon::tests::unsafe_pty_osc8_hyperlink_is_not_emitted_from_frame_metadata

Status model / publication

INVDescriptionVerify byTests
INV-D12Control reply for runtime events applies to the named session and ACKs; unknown session still ACKscontrol subsystemjackin_capsule::daemon::tests::control_reply_report_runtime_event_applies_to_session_and_acks jackin_capsule::daemon::tests::control_reply_runtime_event_and_capture_for_unknown_session_still_ack
INV-D13Outer terminal title uses workspace + PR title, falls back to branch, and sanitizes control bytestitle composition helpersjackin_capsule::daemon::tests::outer_terminal_title_uses_workspace_and_pr_title jackin_capsule::daemon::tests::outer_terminal_title_falls_back_to_branch_without_pr jackin_capsule::daemon::tests::outer_terminal_title_sanitizes_control_bytes

Clipboard transfers

INVDescriptionVerify byTests
INV-D14Clipboard image errors use stable compact reason codes (not free-form strings)clipboard error mappingjackin_capsule::daemon::tests::clipboard_image_error_reason_uses_stable_compact_codes
INV-D15Clipboard transfer idle expiry is driven by an injected Clock (not wall-clock Instant::now)capsule clipboard expiry path + jackin_core::clockjackin_capsule::clipboard::tests::abort_idle_older_than_drops_transfer_past_timeout jackin_capsule::clipboard::tests::abort_idle_only_drops_idle_of_two_transfers jackin_capsule::clipboard::tests::abort_idle_boundary_idle_exactly_timeout_is_aborted

Git / PR watch

INVDescriptionVerify byTests
INV-D16Pending PR lookup can be armed and surfaces in outer title once resolvedPR watch helpers / arm_pending_pr_lookup test fixturejackin_capsule::daemon::tests::outer_terminal_title_uses_workspace_and_pr_title

Usage totals

INVDescriptionVerify byTests
INV-D17Opening the usage dialog queues a focused-provider refresh without replacing an already-open dialog body on refreshusage dialog actionsjackin_capsule::daemon::tests::apply_action_open_usage_queues_focused_provider_refresh jackin_capsule::daemon::tests::apply_dialog_action_refresh_usage_queues_refresh_without_replacing_dialog
INV-D18Control usage account list prefers the in-memory cache when warmcontrol usage list pathjackin_capsule::daemon::tests::control_usage_account_list_uses_in_memory_cache

Control-protocol routing + cleanup

INVDescriptionVerify byTests
INV-D19Last-session exit handler classifies whether the daemon should exithandle_last_session_exit / should_defer_last_session_exit + ports::PersistencePortjackin_capsule::daemon::tests::last_session_exit_defers_when_dialog_already_open_inv_d19 jackin_capsule::daemon::ports::tests::persistence_port_defers_when_dialog_open
INV-D20File export paths are categorized as jackin-run / jackin-owned / container-absolute / relative via container_pathsrequested_export_path_category in daemon/file_export.rsjackin_capsule::daemon::file_export::tests::export_path_category_matrix_inv_d20

Test seams

  • Echo-back PTY harnessVirtualClient / recorded PTY fixtures (recorded_pty_fixtures_keep_screen_equal_to_model); see TESTING.md render-conformance notes.
  • Attach client channelmux.client.attach(tx) + attach_drained_client for single-client frame observation.
  • Control-socket entry — control request/reply helpers in daemon/control.rs exercised by control_* tests.
  • Clockjackin_core::clock::{Clock, ManualClock} for clipboard expiry (plan 024).

Missing coverage (worklist, risk-ordered)

  1. INV-D1 full displace policy — reattach palette test exists; a second-client “stream B becomes active, stream A is closed” characterization is still thin (feeds plan 033).
  2. PTY failure recovery — writer-lock failure / closed channel / PTY EOF paths in session writer loops are largely unexercised (TEST-pty-recovery; plan 033).

INV-D8 / INV-D15 / INV-D19 / INV-D20 closed with Multiplexer / ManualClock characterization tests.

Undetermined behaviors

None recorded above the STOP threshold. Concurrent select-loop fairness beyond INV-D10 is intentionally left as characterization work, not a claimed invariant.

On this page