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}.
Modules§
- 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).
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). - 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.
- 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§
- 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_ 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_
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_
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. - 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.
- 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.