Skip to main content

rightkit_daemon/
lib.rs

1//! rightkit-daemon: the one discovery and authentication convention for local
2//! Right Suite services (the shared speech service, the in-app control server,
3//! `right-ctl`).
4//!
5//! * **Discovery** - `<suite>/runtime/<service>/service.json`, written
6//!   atomically after the endpoint is listening, removed on clean exit. A
7//!   record whose endpoint does not answer is stale and ignored.
8//! * **Ranking and takeover** - one host per service per suite. A would-be host
9//!   that outranks the incumbent (lower rank wins) writes `takeover.json`; the
10//!   incumbent stops admitting work, finishes or hands off in-flight requests
11//!   with the retryable [`ERROR_HOST_HANDOFF`] at their next boundary, and
12//!   releases. Clients re-read discovery and resubmit. The incumbent looks for a
13//!   request from another process once a second (a stat of `takeover.json`, no
14//!   thread polls faster), so a takeover starts up to about a second after the
15//!   request is written.
16//! * **Authentication** - no TCP and no bearer tokens. Unix: a socket in a 0700
17//!   directory, mode 0600, and the kernel-reported peer uid (`getpeereid` on macOS
18//!   and the BSDs, `SO_PEERCRED` on Linux) checked on every accept and by every
19//!   client (`verify_peer_is_current_user`); a platform that cannot check refuses
20//!   every peer. Windows: a named pipe whose DACL grants only the current user's SID
21//!   (`owner_only_security_attributes` on Windows), remote clients rejected, first-instance
22//!   flag set against name squatting, a bounded instance count
23//!   ([`HostOptions::max_pipe_instances`]), the client's process-token SID checked on
24//!   every accept, and clients opening with `SECURITY_IDENTIFICATION` and checking
25//!   the server's SID. See the README threat model.
26//! * **Verifying the host** - the transport proves a host runs as the current user, not which
27//!   program it is. [`Client::connect_expecting`] takes a [`HostExpectation`] (the executable the
28//!   host may be, for example a sibling inside the client's own app bundle), asks the kernel who
29//!   is on the listening end of the connected socket (macOS `LOCAL_PEERPID` and `proc_pidpath`,
30//!   Linux `SO_PEERCRED` and `/proc/<pid>/exe`), and refuses anyone else with a typed
31//!   [`HostIdentityError`] before the `hello` or any request is written. [`Client::connect`] stays
32//!   unauthenticated-host. [`ClientConfig::ignore_env_overrides`] makes the suite root come only
33//!   from the account database, so a launcher's environment cannot redirect a release client.
34//!   See the README, "Verifying the host".
35//! * **Per-app request allowlist** - each client names its app in `hello`; an
36//!   app may call only the methods listed for it (the generalisation of
37//!   HeardRight's `SERVICE_ALLOWED_SCHEMAS`). The app label is an allowlist key,
38//!   not a security boundary against code already running as the same user.
39//! * **Fail-closed roots** - [`Suite::resolve`] errors when no absolute
40//!   app-data root can be determined; it never falls back to the working
41//!   directory. [`Suite::per_run_temp`] is the explicit opt-in alternative.
42//!
43//! Wire format: bounded JSON lines (`rightkit-sidecar`), the same frames
44//! as the `rightkit-job-queue` engine contract:
45//! `hello`, `request{id,method,params}`, `event{id,event}`, `response{id,ok,result|error}`.
46
47mod app_root;
48/// Approval workflow mechanics: request binding, verified decisions, one execution claim,
49/// audit before effect (PK07-4).
50pub mod approval;
51pub mod chunk;
52mod client;
53pub mod contract;
54mod discovery;
55mod error;
56mod fail_limit;
57mod host_identity;
58pub mod lifecycle;
59#[cfg(feature = "link")]
60pub mod link;
61/// Loopback-only HTTP host with a bearer token (`remote-loopback` feature, not `remote`).
62#[cfg(feature = "remote-loopback")]
63pub mod loopback;
64pub mod mode;
65mod peer;
66pub mod private;
67mod record;
68mod registration;
69#[cfg(feature = "relay")]
70pub mod relay;
71/// Daemon host (`remote` feature) and device client (`link` feature); see the module docs.
72#[cfg(any(feature = "remote", feature = "link"))]
73pub mod remote;
74mod root_cutover;
75mod server;
76mod service;
77pub mod stop;
78mod suite;
79pub mod supervisor;
80mod takeover_watch;
81pub mod ticker;
82mod transport;
83
84pub use client::{Client, ClientConfig, ClientPool, RetryPolicy, DEFAULT_DEADLINE};
85pub use discovery::{
86    adopt_or_spawn, discover_apps_in, discover_apps_in_with, discover_macos, discover_macos_with,
87    discover_windows, discover_windows_with, parse_plist_string, parse_reg_query, plan_host,
88    poll_until, publish_takeover, record_process_matches, resolve_install_dir, resolve_rank,
89    should_request_takeover, spawn_detached, takeover_obliges_yield, try_adopt_or_spawn,
90    try_plan_host, wait_for_claim_winner, AdoptOrSpawn, Completeness, DiscoverOptions, HostPlan,
91    InstalledApp, SpawnClaim, SpawnClaimError, UninstallEntry,
92};
93pub use private::{
94    exit_when_stdin_closes, watch_reader_eof, watch_stdin_eof, PrivateChild, PrivateSecret,
95    SecretError, SECRET_BYTES, SECRET_HEX_LEN,
96};
97// Deprecated items stay exported for existing callers; their deprecation is reported at the
98// caller's own use site, not on this re-export.
99pub use app_root::{
100    valid_app_name, AppRoot, AppRootError, LinkNotCreated, MigrateOptions, MigratedLink,
101    MigrationOutcome, MigrationReport, MigrationScope, MigrationState, MigrationStatus,
102    OmittedLink, ReparseKind, SkippedReparsePoint, SkippedSpecial, SpecialKind, WriterLock,
103    MAX_APP_NAME_LEN, MIGRATION_MARKER, MIGRATION_STATE,
104};
105#[allow(deprecated)]
106pub use discovery::{discover_apps, KNOWN_APPS};
107pub use error::{
108    ConnectError, ServiceError, ERROR_BAD_CHUNK, ERROR_CONNECTION_CLOSED, ERROR_FORBIDDEN,
109    ERROR_HOST_HANDOFF, ERROR_HOST_IDENTITY, ERROR_NOT_ALLOWED, ERROR_RATE_LIMITED,
110    ERROR_REQUEST_TOO_LARGE, ERROR_RESPONSE_TOO_LARGE, ERROR_REVISION_CONFLICT,
111    ERROR_SUITE_UNRESOLVED, ERROR_TIMEOUT, ERROR_UNAUTHENTICATED, ERROR_UNAVAILABLE,
112};
113pub use fail_limit::FailLimiter;
114pub use host_identity::{HostExpectation, HostIdentityError, HostIdentityReason, HostProcess};
115pub use lifecycle::{
116    DaemonLifecycle, LifecycleError, Provenance, ERROR_NOT_OWNED, ERROR_RESTART_EXHAUSTED,
117    ERROR_SESSION_RELEASED, ERROR_STALE_GENERATION,
118};
119#[cfg(unix)]
120pub use peer::verify_peer_is_current_user;
121#[cfg(windows)]
122pub use peer::{
123    current_user_sid, owner_only_security_attributes, verify_pipe_peer_is_current_user,
124    verify_pipe_server_is_current_user, OwnerOnlySecurityAttributes,
125};
126pub use peer::{PeerError, PeerIdentity};
127pub use record::{
128    clear_takeover_if_owner, read_live_record, read_record, read_takeover, record_path,
129    takeover_path, try_read_live_record, try_read_record, try_read_takeover, write_atomic,
130    write_takeover, Allowlist, ServiceRecord, TakeoverRequest, PROTOCOL_MAJOR, PROTOCOL_MINOR,
131    RANK_UNKNOWN,
132};
133pub use registration::{
134    MethodDescriptor, Registration, RegistrationError, ServiceIdentity, TransportProfile,
135};
136pub use root_cutover::{
137    CutoverContext, CutoverError, CutoverParticipant, CutoverPhase, CutoverReport, CutoverStep,
138    FsMove, ParticipantError, RecoverOutcome, RetireMode, Retired, RootCutover, RootSelection,
139    RootStatus, Settlement, CUTOVER_RECORD,
140};
141pub use server::{
142    host, host_with_options, CallContext, Emit, Handler, HostError, HostOptions, Hosted,
143    ServiceConfig, ServiceEvent, ServiceStatus, DEFAULT_FRAME_LIMIT, DEFAULT_MAX_CONNECTIONS,
144    DEFAULT_WRITE_TIMEOUT,
145};
146pub use service::{BuildError, Service, ServiceBuilder};
147pub use suite::{
148    refuse_build_tool_real_data, refuse_build_tool_real_data_for_mode, valid_service_name, Suite,
149    SuiteError, ENV_SUITE_ROOT, MAX_SERVICE_NAME_LEN, SHARED_DIR_NAME, SUITE_DIR_NAME,
150};
151pub use transport::{
152    endpoint_candidates, endpoint_for, find_live_endpoint, open_stream, open_stream_expecting,
153    pid_alive, probe_endpoint, try_endpoint_for, Accepted, Conn, EndpointError, Listener, Stream,
154    DEFAULT_MAX_PIPE_INSTANCES, MAX_UNIX_SOCKET_PATH,
155};