Expand description
rightkit-daemon: the one discovery and authentication convention for local
Right Suite services (the shared speech service, the in-app control server,
right-ctl).
- Discovery -
<suite>/runtime/<service>/service.json, written atomically after the endpoint is listening, removed on clean exit. A record whose endpoint does not answer is stale and ignored. - Ranking and takeover - one host per service per suite. A would-be host
that outranks the incumbent (lower rank wins) writes
takeover.json; the incumbent stops admitting work, finishes or hands off in-flight requests with the retryableERROR_HOST_HANDOFFat their next boundary, and releases. Clients re-read discovery and resubmit. The incumbent looks for a request from another process once a second (a stat oftakeover.json, no thread polls faster), so a takeover starts up to about a second after the request is written. - Authentication - no TCP and no bearer tokens. Unix: a socket in a 0700
directory, mode 0600, and the kernel-reported peer uid (
getpeereidon macOS and the BSDs,SO_PEERCREDon Linux) checked on every accept and by every client (verify_peer_is_current_user); a platform that cannot check refuses every peer. Windows: a named pipe whose DACL grants only the current user’s SID ([owner_only_security_attributes]), remote clients rejected, first-instance flag set against name squatting, a bounded instance count (HostOptions::max_pipe_instances), the client’s process-token SID checked on every accept, and clients opening withSECURITY_IDENTIFICATIONand checking the server’s SID. See the README threat model. - Verifying the host - the transport proves a host runs as the current user, not which
program it is.
Client::connect_expectingtakes aHostExpectation(the executable the host may be, for example a sibling inside the client’s own app bundle), asks the kernel who is on the listening end of the connected socket (macOSLOCAL_PEERPIDandproc_pidpath, LinuxSO_PEERCREDand/proc/<pid>/exe), and refuses anyone else with a typedHostIdentityErrorbefore thehelloor any request is written.Client::connectstays unauthenticated-host.ClientConfig::ignore_env_overridesmakes the suite root come only from the account database, so a launcher’s environment cannot redirect a release client. See the README, “Verifying the host”. - Per-app request allowlist - each client names its app in
hello; an app may call only the methods listed for it (the generalisation of HeardRight’sSERVICE_ALLOWED_SCHEMAS). The app label is an allowlist key, not a security boundary against code already running as the same user. - Fail-closed roots -
Suite::resolveerrors when no absolute app-data root can be determined; it never falls back to the working directory.Suite::per_run_tempis the explicit opt-in alternative.
Wire format: bounded JSON lines (rightkit-sidecar), the same frames
as the rightkit-job-queue engine contract:
hello, request{id,method,params}, event{id,event}, response{id,ok,result|error}.
Re-exports§
pub use private::exit_when_stdin_closes;pub use private::watch_reader_eof;pub use private::watch_stdin_eof;pub use private::PrivateChild;pub use private::PrivateSecret;pub use private::SecretError;pub use private::SECRET_BYTES;pub use private::SECRET_HEX_LEN;
Modules§
- chunk
- Chunked calls: messages larger than one frame (issue #294).
- contract
- The one request contract, independent of transport.
- mode
- The one runtime decision between a release run and a test run (M21), and the only sanctioned reader of a test or override selector (M23).
- private
- Private per-run child service: a secret passed over stdin, checked in constant time on every frame, and a child that exits when stdin closes.
- stop
- Stop fan-in for a daemon host (issue #375): one
StopSourcethat several inputs may trigger (an OS stop signal, the parent closing this process’s stdin, or the program itself), andStopTokens that the host’s threads wait on. The first cause wins and later triggers are ignored. - supervisor
- A supervisor loop for a daemon-pipe child (issue #295).
- ticker
- Periodic work and a heartbeat record for a daemon host (issue #376).
Macros§
- test_
selector - See the
test-modeform: this one drops the name.
Structs§
- Allowlist
- Which methods each calling app may use.
"*"as an app key is the default for apps with no entry of their own;"*"as a method allows every method for that app.pingis always allowed. An empty allowlist denies everyone. - AppRoot
<Orthic Labs>/<App>, created private.- Call
Context - What a handler knows about the request it is serving.
- Client
- A connection to one host of a service. One request in flight at a time;
open a second
Clientfor parallel calls. Dropping it closes the connection, which the host treats as a disconnect (jobs started withcancel_on_disconnectare cancelled). - Client
Config - What a client needs to find and call one service, including how much it trusts its
environment. Pass it to
Client::connect_expecting(verified host) orClient::connect_config(hardened root, unverified host). - Conn
- An accepted or connected duplex stream.
peeris who the OS reports on the other end. - Discover
Options - Options for discovery.
DiscoverOptions::default()reproduces the original behaviour. - Endpoint
Error - The endpoint for a service cannot be named within the OS socket path limit, even at the short fallback location. Names the offending path, its length and the limit.
- Host
Expectation - Which program the client is willing to talk to, passed to
Client::connect_expecting. - Host
Identity Error - The process behind the endpoint is not the host the caller expected. No
helloand no request frame was written to it. - Host
Options - Host-side transport options, passed to
host_with_options.HostOptions::default()is whathostuses. - Host
Process - The process the OS says is on the listening end of a connection this client opened, as it was
when every check passed. Returned by
Client::verified_hostand handed to a caller-supplied verifier (HostExpectation::with_verifier). - Hosted
- A running host. Dropping it releases the service.
- Installed
App - Installed Right Suite applications and their engine executable.
- Listener
- Peer
Identity - Who the kernel says is on the other end of a connection.
- Retry
Policy - Reconnect-and-resubmit policy for retryable errors (handoff, host restart).
- Service
- A running service: the local host, the remote daemon, or both, over one handler.
- Service
Builder - Service
Config - Service
Error - Typed wire error.
retryablemeans resubmitting the request can succeed. - Service
Record <suite>/runtime/<service>/service.json.- Service
Status - Runtime figures of a running host, computed on call by
Hosted::status. - Spawn
Claim - The claim around one process spawn attempt, backed by an OS exclusive lock
on
spawn.claim. The lock lives as long as the open file: the kernel releases it when the owner dies (any signal), and there is no time-based eviction of a live owner. The file is never unlinked, so every contender always locks the same inode; Drop closes only this claim’s own handle. - Stream
- A raw duplex byte stream to an endpoint, for callers that speak their own
line protocol over the shared transport (HeardRight’s v1
EngineFramedialect, which has nohello). Same current-user-only transport ascrate::Client. - Suite
- The per-user suite directory that holds
runtime/<service>/service.jsonand other shared state. Always absolute. - Takeover
Request <suite>/runtime/<service>/takeover.json: written by a would-be host that outranks the incumbent; deleted by the requester once hosting.- Uninstall
Entry - One uninstall registry row, independent of the Windows registry API so it
can be tested from captured
reg queryoutput. - Writer
Lock - Held while this process is the single writer of an app root. Released on drop or process death (OS lock).
Enums§
- Accepted
- Outcome of
Listener::accept: an authenticated connection, a rejected peer, or one that left before it could be identified. - Adopt
OrSpawn - AppRoot
Error - Build
Error - Why
ServiceBuilder::startfailed. - Completeness
- Whether an installed app is usable as a service host.
- Connect
Error - Why
Client::connect_expectingdid not return a client. - Host
Error - Host
Identity Reason - Why a host was refused. Always the host’s identity, never a connection problem.
- Host
Plan - Outcome of
plan_hostfor a process of a given rank. - Migration
Outcome - What
AppRoot::migrate_fromdid. - Peer
Error - Why a peer was not accepted as the current user.
- Service
Event - Content-free lifecycle events for the host’s journal.
- Spawn
Claim Error - Suite
Error
Constants§
- DEFAULT_
DEADLINE - Default per-request deadline (connect + hello, and each call’s write + read).
Override with
Client::connect_with/Client::set_deadline;Nonedisables it. - DEFAULT_
FRAME_ LIMIT - DEFAULT_
MAX_ CONNECTIONS - Default for
HostOptions::max_connections. - DEFAULT_
MAX_ PIPE_ INSTANCES - Default for
HostOptions::max_pipe_instances. - DEFAULT_
WRITE_ TIMEOUT - Default for
HostOptions::write_timeout: how long one frame write plus flush to a client may take before the host closes that connection. Matches the client’s default call deadline. - ENV_
SUITE_ ROOT - Overrides the platform default (tests, isolated dev profiles). Must be absolute.
RIGHT_SUITE_DIRandORTHIC_SHARED_DIRare honoured as legacy aliases. - ERROR_
BAD_ CHUNK - A chunk frame (
rk.upload,rk.fetch) was malformed, out of order, repeated or for an upload or result that is gone. Not retryable as sent: start the whole transfer again. - ERROR_
CONNECTION_ CLOSED - The connection dropped before a response arrived. Retryable.
- ERROR_
FORBIDDEN - The caller is known but lacks the scope for this method.
- ERROR_
HOST_ HANDOFF - The host is handing the service to a higher-ranked host. Retryable: re-read discovery and resubmit the whole request.
- ERROR_
HOST_ IDENTITY - The process behind the endpoint is not the host the caller expected
(
Client::connect_expecting). Not retryable: reconnecting reaches the same impostor, and no application byte was sent to it. - ERROR_
NOT_ ALLOWED - The calling app may not call this method (or is not on the allowlist).
- ERROR_
REQUEST_ TOO_ LARGE - A request is larger than the host will assemble from chunks
(
HostOptions::max_message), or than one frame when the host does not chunk. Not retryable. Ask for less. - ERROR_
RESPONSE_ TOO_ LARGE - The handler’s result did not fit the host’s frame limit, so it was not sent. Not retryable: the same request produces the same result. Ask for less (a page, a smaller body).
- ERROR_
REVISION_ CONFLICT - A write named a stale revision.
- ERROR_
SUITE_ UNRESOLVED - The suite root could not be resolved, or a
ClientConfigthat ignores environment overrides was handed a suite that came from the environment. Not retryable. - ERROR_
TIMEOUT - The per-request deadline (connect + write + read) expired and the connection was aborted. Not retryable: the request may already have run on the host, so it is never replayed automatically.
- ERROR_
UNAUTHENTICATED - The caller presented no valid credential (a device that is unknown, or whose signature failed).
- ERROR_
UNAVAILABLE - No live host for the service. Retryable.
- KNOWN_
APPS Deprecated - The original Right Suite table. Deprecated: the crate hard-coded one product
suite’s names. Products pass their own
(app name, engine executable)registry todiscover_apps_in. Kept with identical contents so existing callers behave the same in this release. - MAX_
APP_ NAME_ LEN - The longest app name accepted, in bytes.
- MAX_
SERVICE_ NAME_ LEN - The longest service name
hostaccepts, in bytes. - MAX_
UNIX_ SOCKET_ PATH - Longest Unix socket path this crate will bind:
sockaddr_un.sun_pathis 104 bytes on macOS and 108 on Linux, each including the trailing NUL, so 103 is the portable ceiling. - MIGRATION_
MARKER - The commit marker written at the app root once a migration is verified.
- PROTOCOL_
MAJOR - Bumped on any incompatible change to discovery files, frames or the allowlist semantics. A client rejects a different major.
- PROTOCOL_
MINOR - RANK_
UNKNOWN - Rank of a record or takeover request that carries none (HeardRight’s
service.json/takeover.jsonname only arole). Never wins a takeover: a host resolves it throughcrate::ServiceConfig::known_rolesor treats the incumbent as unbeatable. - SHARED_
DIR_ NAME - The shared-data subdirectory under
SUITE_DIR_NAME. HeardRight, ScrapeRight and CutRight already root their shared model store and speech discovery here (.../Orthic Labs/shared), so the one convention adopts it. - SUITE_
DIR_ NAME
Traits§
Functions§
- adopt_
or_ spawn - Adopt only a live, reachable record whose PID generation still matches.
- clear_
takeover_ if_ owner - Remove
takeover.jsononly ifpidwrote it. - discover_
apps Deprecated - Discover the built-in Right Suite apps. Deprecated: use
discover_apps_in. - discover_
apps_ in - Discover installed apps from the caller’s
(app bundle or display name, engine executable)registry. The first complete match per app wins. - discover_
apps_ in_ with discover_apps_inwith explicitDiscoverOptions.- discover_
macos - Scan roots in order; first complete bundle wins per app.
- discover_
macos_ with discover_macoswith explicitDiscoverOptions. Withinclude_incomplete, an app that has a bundle directory but no engine in any root is returned once, from the first root that has its bundle, asCompleteness::EngineMissing.- discover_
windows - Discover complete Windows installs only. Equivalent to
discover_windows_withwithDiscoverOptions::default. - discover_
windows_ with - Windows discovery with explicit options.
existschecks the engine file anddir_existschecks the install directory. Withinclude_incomplete, an entry whose directory exists but whose engine is missing is returned asCompleteness::EngineMissing. A complete install from any entry of the same app always wins. - endpoint_
candidates - Endpoints a client may find a live host on, in order: the current endpoint, then (Unix only) the pre-upgrade long fallback name when it would have fitted. Compatibility decision: for one release a new client still finds an old host on the old fallback name; that name is connect-only and is never bound. Where the old name never fitted the limit no old host could have bound it, so there is no second candidate.
- endpoint_
for try_endpoint_forfor callers that cannot handle the error: on failure it returns the path that did not fit, so a bind attempt fails with the OS error rather than panicking here.- find_
live_ endpoint - The first of
endpoint_candidatesthat answers a connect. - host
- Host
cfg.serviceonsuite: discover, rank, take over if outranking, bind the current-user-only endpoint, publishservice.json. Chunked calls (rk.upload) are off unless the options setHostOptions::max_message. - host_
with_ options hostwith explicitHostOptions.- open_
stream - Connect to
endpoint. On Windows a busy pipe is retried up to 3 s and a not-yet-created one up to 1 s (HeardRight’s behaviour). - open_
stream_ expecting open_streamthat first verifies the host is the programexpectnames, before a byte is written (seeClient::connect_expectingfor the checks and what each platform verifies). A host that is not the expected one isConnectError::HostIdentity; a host that cannot be reached isConnectError::Servicewith the retryableservice_unavailable.- parse_
plist_ string - Parse a plain XML plist without requiring a plist dependency. Binary plist
files return
None, while the app remains discoverable. - parse_
reg_ query - Parse
reg query ... /soutput. Unknown values and malformed rows are ignored; keys are grouped by their registry-key heading. - pid_
alive - Is process
pidalive? A dead pid marks a discovery record stale and a takeover request abandoned. Hardened from HeardRight’s version: pid 0 and pids that do not fitpid_tare never alive (they would signal process groups), and on Windows an exited process whose handle is still open is dead. - plan_
host - Decide how a process of rank
my_rankgets a host forservice. A live record whose rank cannot be resolved throughknown_rolescounts as absent.installed_engineis the executable the caller may start when nothing is live; it is used only when it exists. - poll_
until - Poll
readyevery 100 ms until it holds (true) orwaitelapses (false). Checks before the first sleep. - probe_
endpoint - Does something answer at
endpoint? (Connect, then drop.) - publish_
takeover - Publish
takeover.jsonfor this process asrole(withrank), atomically. - read_
live_ record - The record only when its owner process is alive (HeardRight’s staleness
rule: a dead pid means the record is stale). Does not probe the endpoint;
connecting does that and reports
service_unavailable. Unreadable isNone; seetry_read_live_record. - read_
record - The published record. A record that is absent, unparseable or unreadable is
None: usetry_read_recordwhere “unreadable” must not be taken for “no service”. - read_
takeover - The pending takeover request. Absent, unparseable and unreadable are all
None: usetry_read_takeoverwhere “unreadable” must not be taken for “no request”. - record_
path - record_
process_ matches - Check both liveness and process generation, protecting discovery from PID reuse.
- refuse_
build_ tool_ real_ data - Refuse a development run that the build tool started against the real suite root (issue #384).
- resolve_
install_ dir - resolve_
rank - Rank of a live record or takeover request: its own
rankwhen it writes one, elseknown_rolesby itsrole.Nonewhen neither resolves; such a peer is never taken over from and never obliges a handoff. - should_
request_ takeover - Takeover is requested only against a live, lower-ranked host held by another process.
incumbentis the live record’s(resolved rank, pid). - spawn_
detached - Start
programdetached: no inherited stdio, its own process group on Unix, and on Windows no console window (CREATE_NO_WINDOW | DETACHED_PROCESS) so a GUI host never flashes one. The child is not waited for. - takeover_
obliges_ yield - Does a pending takeover request
(resolved rank, pid)oblige a host of rankmy_rankto yield? Only a live, other-process, outranking requester does. - takeover_
path - try_
adopt_ or_ spawn adopt_or_spawnwithErrwhenservice.jsonexists but could not be read: whether a live host exists is then unknown, so look again rather than spawn.- try_
endpoint_ for - Endpoint for
serviceonsuite, or the typed reason none can be named. Windows: a pipe name that carries the user and a hash of the suite root, so an isolated suite never collides with the user’s installed host (at most 96 characters for a 48-byte service name and a 20-character user, far under the 256-character pipe name limit). Unix: a socket in the private service dir, or, when that path would exceed thesockaddr_unlimit, a boundedrk-<tag>-<hash>.sockin the per-user temp dir. - try_
plan_ host plan_hostwithErrwhenservice.jsonexists but could not be read. Whether a live host exists is then unknown, so no plan is made:plan_hostwould answerHostMyselforStartInstalled, which starts a second host beside a live one.- try_
read_ live_ record read_live_recordwithErrfor a record that could not be read.- try_
read_ record - The published record, telling “absent” from “unreadable”.
- try_
read_ takeover - The pending takeover request, telling “absent” from “unreadable” as
try_read_recorddoes.Errmeans whether a request exists is unknown: a host must not conclude there is none. - valid_
app_ name - Whether
sis an app name: 1..=MAX_APP_NAME_LENbytes of ASCII letters, digits,-,_and.; starts and ends with a letter or digit (so no.,.., hidden or trailing-dot names); no separators; not a Windows reserved device name (with or without an extension). Case is preserved on disk but is significant for identity on every platform: two names that differ only by case are refused as conflicting byAppRoot::newwhen one already exists. - valid_
service_ name - Whether
sis a service namehostaccepts: non-empty, at mostMAX_SERVICE_NAME_LENbytes, and only ASCII lowercase letters, digits and-. This is the single rulehostenforces. - verify_
peer_ is_ current_ user - Verify the peer of a connected Unix stream is the current user.
- wait_
for_ claim_ winner - Wait for the winner of a competing claim to publish a matching live record.
- write_
atomic - Atomic, durable, owner-only replace (
rightkit_fs::atomic_write_private: unique temp file in the same directory,fsync, rename, directoryfsync). The parent directory must exist. - write_
takeover - Publish a takeover request (atomic, owner-only).
Type Aliases§
- Emit
- Sends an
eventframe for the request being handled.