Separate rush-client and rushx-client binaries, opt-in until cutover. Existing
rush, rushx, and their reporter entrypoints are unchanged.
The dependency on @microsoft/rush is intentional: it is the version-selecting
frontend, whereas @microsoft/rush-lib is the execution engine. Native routing
and safe daemon fallback in src/launchClient.ts restore the rush or rushx
executable name and load the frontend's exported lib/start entrypoint. They
must not call the client's bundled Rush.launch() or Rush.launchRushX()
directly: the workspace or RUSH_PREVIEW_VERSION can select a different engine.
The client also reuses the frontend's MinimalRushConfiguration for native
Rushx discovery output instead of maintaining a second implementation.
The startup sequence in apps/rush/src/start.ts and RushFrontend.ts selects
and initializes the reporter before RushVersionSelector installs or loads
the selected engine. RushCommandSelector then handles engine capabilities
and old-engine output compatibility. Selection supports releases predating
rush-lib (before Rush 4), and cannot depend on a new API being present in the
selected engine. Calling a selector alone would also bypass reporter startup,
preview-version validation, and native launch options.
The runtime dependency direction is client → frontend → engine; rush-lib
does not depend on this client or on the frontend. Re-exporting the existing
frontend selector from rush-lib would introduce an engine → frontend → engine
cycle. A future standalone bootstrap package would need to own the complete
startup/reporting contract, not just engine installation. Until that separation
is warranted, reusing the existing frontend preserves version selection and
reporter behavior without duplicating or relocating bootstrap code.
Routing precedence:
--no-daemonbefore--, help, never-daemonize commands, and Rushx with any TTY stdio stay in-process.- CI stays in-process unless
RUSH_DAEMON=1explicitly opts in, even if config enables the daemon. RUSH_DAEMONoverridesrush.json'sdaemon.enabled; the default is false.- Auto-start is considered only after selecting daemon execution.
--no-wait fails immediately when daemon admission is unavailable.
--wait-timeout SECONDS (or --wait-timeout=SECONDS) overrides the configured queue
timeout; finite nonnegative decimal seconds up to 2147483.647 are accepted and
rounded down to milliseconds. These controls are mutually exclusive and are
consumed before forwarding, never appended to a project script. Arguments after
-- remain literal script arguments.
The queue timeout is measured from when the daemon receives the request. An
explicit --no-wait, --wait-timeout, RUSH_DAEMON_QUEUE_TIMEOUT_SECONDS, or
daemon.queueTimeoutSeconds in rush.json bounds the entire wait: waiting for
workspace admission and waiting for a running build that the request could not
join. The built-in 30-second default bounds only workspace admission (for example,
waiting for a command that needs exclusive access). With the default, a build that
arrives while a compatible build is already running waits for it to finish and then
runs, instead of failing after 30 seconds. On a timeout, the client exits with
code 1 and says how to wait longer.
Admission controls also apply to experimental graph requests, but not
start|stop|restart|status|logs. They affect daemon admission only; native fallback
retains native command behavior. Waiting positions are shown on interactive stderr,
and admission failures report their typed reason and a nonzero exit code.
Explicit reporter/output/log-level controls (--reporter, --output, --log-level,
RUSH_REPORTER other than legacy, or RUSH_LOG_LEVEL) retain the native frontend
reporter path, with or without --no-daemon, including --reporter=ai. The daemon client
does not silently reinterpret requests for JSON, AI, file, or other reporter formats.
A repository that opts into the native reporter with "useRushReporter": true in
common/config/rush/experiments.json also stays on the native (in-process) path, so
that its reporter output is honored rather than silently replaced by the daemon
stream. Native reporter rendering over the daemon protocol is a follow-up.
The rush-client daemon path has two output modes (rushx-client always uses legacy).
Requests that use the native reporter path (see above) always get native output, and
agent mode writes nothing ahead of it. Otherwise, selection precedence is:
RUSHD_OUTPUT=agentorRUSHD_OUTPUT=legacy.- An active
COPILOT_CLIagent marker selectsagent, matchingdetectAgent()in@rushstack/reporter(a value is inactive when empty,0,false,nooroff). Other agents can opt in withRUSHD_OUTPUT=agent. - Otherwise
legacy: the unchanged collated operation stream.
Agent mode is plain text for humans and agents, not the AI reporter's JSON record format;
use --reporter=ai for machine-parsed records. It writes a first status line before
@microsoft/rush-lib is loaded, then at most three live rows on a TTY (append-only lines
throttled to one per 2 seconds on a pipe), the queue position when waiting for admission,
and always one final summary line (rush build: SUCCESS 12/12 operations (...) in 3.1s, or
up to date (no operations needed)). On failure, it lists failed operations and a
bounded tail (10 lines) of their stderr, or of their stdout when they wrote no stderr.
Operation logs are otherwise not printed; use RUSHD_OUTPUT=legacy for full logs. When
a request falls back to in-process Rush, agent mode stops and native output follows.
Positively identified built-in install and update follow the same opt-in routing
precedence as workspace builds and require protocol 0.10
(DAEMON_WORKSPACE_RESTART_PROTOCOL_MINOR). They are not submitted to older peers.
Other package mutation, publishing, setup, management, and administrative commands
remain native rather than being forwarded as execution requests. Daemon management
subcommands use their separate control path. Rushx script names are not interpreted
as Rush built-ins. Arguments after -- are preserved.
Request cwd, environment, argv, width and color are captured before connecting.
The protocol currently expresses request color as a boolean; subscriptions carry
the corresponding color level. There is no SIGWINCH forwarding.
The standalone host now binds native build/rebuild requests to a reusable
all-project graph. Native Rush parsing, project selection, graph plugins, and
incremental/cache semantics are reused rather than spawning another Rush CLI.
The client renders operation headers, collated text, and activity events; global
command byte streams remain byte-preserving. A rushx build script never claims
to be a workspace build.
Rushx requests carry invocationKind: "rushx" (protocol 0.8), independently of custom
command origin. Native parsing recognizes -q, -d, and --ignore-hooks before the
command; subsequent flags and -- belong to the script, apart from this client's
explicit admission/escape controls. Older peers fall back before receiving the request
or consuming input.
The standalone Rushx client conservatively keeps every script with TTY stdin, stdout, or stderr on the native path, before connecting, auto-starting, or consuming input. Arbitrary scripts may require raw mode or a controlling terminal, and a pipe is not a PTY. There is no attempt to discover this requirement by running and retrying a script. Fully non-TTY invocations retain daemon forwarding, binary byte parity, and EOF handling. An embedded client that knows its script is pipe-safe can still submit a Rushx envelope directly; it must declare any controlling-terminal requirement.
The default daemon installs RushDaemonRequestResolver(existingRushResolver) to enable real
package-script execution alongside native workspace builds. It reuses native Rushx parsing, escaping,
banner/diagnostics, lifecycle PATH and INIT_CWD preparation, dotenv precedence, and
pnpm injected-dependency synchronization. Only the actual script shell is spawned;
there is no Rush CLI child or synthetic warm graph. Native configuration discovery
is captured by the client and emitted only with daemon output, avoiding duplicate
discovery messages on fallback.
On Windows, Rushx retains native invocation-path spelling in script cwd, lifecycle variables and
pnpm-sync output, including 8.3 aliases and junctions. Daemon identity and confinement remain physical;
an alias does not create another workspace identity or permit execution outside the workspace.
Alias retargeting while queued fails before execution. Relative RUSH_TEMP_FOLDER initialization
uses safe in-process fallback.
The composite is exported for embedded hosts and wired into the standalone daemon.
No default or cutover flag is flipped. Active Rushx hooks, encrypted dotenv
vaults, changed process-global Rush configuration variables, stale workspace configuration,
and native help require pre-execution fallback. Ignored/recursive hooks retain native
behavior, including skipping post hooks after failure. PTY requirements remain in-process.
For forwarded Rushx, TTY output color/width overrides are applied after native
lifecycle environment preparation using the existing terminal policy helper.
Non-TTY requests retain their explicit environment values, including intentional
FORCE_COLOR/COLUMNS settings; no ambient daemon environment is merged in.
Compatible requests reuse the same native graph. Source changes refresh inputs;
changed configuration or command shape replaces the session and graph in the same
process. Environment, installed dependencies, implementation content, or selected
Rush version changes require a process restart rather than patching the existing
engine. Direct, inherited, and rig-based project configuration uses private native
loaders and is rechecked before execution. External plugins, .env, phased
watch/install options, and unsupported event-hook scripts still use typed
pre-execution fallback; this does not exclude the built-in install and update
commands described above. The native Rush lock is held for preparation and each
coalesced iteration, not while idle; native commands and --no-daemon can run
after a completed request without stopping the daemon.
Native workspace dispatch copies the request envelope and normalizes only the
engine-owned _RUSH_LIB_PATH to this daemon's real engine. Foreign client SDK
paths therefore neither select the wrong SDK nor cause a false restart. All other
environment inputs remain unchanged and participate in normal lifecycle checks.
Protocol 0.10 permits a bounded retry only when a pre-execution command result
explicitly carries retryAfterRestart: true. executeWithDaemonRestartAsync
waits for old ownership release and a validated successor, then resubmits an eligible
request at most once. Command input/output or cancellation prevents retry,
even with the typed flag. Unknown rejections and connection loss never authorize replay.
A started install or update is never repeated, including after a nonzero exit;
only an unstarted request can receive
the typed retry authorization. Accepted queued requests drain their typed restart
results before the old connection closes.
Piped input uses protocol 0.7's negotiated stdin admission and EOF. The client does
not read input until the command attaches an input destination, and sends bounded
chunks only as the daemon grants write credits. EOF follows all preceding writes;
binary Ctrl+C bytes in a pipe are data, not cancellation signals. Older peers fall
back before requestStart or input consumption, and pre-execution command fallback
preserves the complete pipe for the native entrypoint.
The existing Rush entrypoints resolve project scripts from cwd. Fallback loads the
existing @microsoft/rush version-selecting entrypoint in the client process,
preserving its startup checks, output and reporter integration instead of
inventing a cached module path. Before auto-start or explicit start/restart, the client
selects an available daemon whose installed engine is exactly the requested Rush
version, including a native RUSH_PREVIEW_VERSION override. It can install a published
daemon release declaring that exact engine dependency into a node-specific Rush cache;
it never overrides dependencies or relabels the bundled engine. Foreign installations
are probed in isolation, and startup rechecks actual runtime version, protocol, and
default request-launch APIs before binding. Incompatible or unavailable launchers use
native fallback for ordinary invocations and fail explicitly for management commands.
Connect-only calls never install packages. Older Rush versions may also reject the new daemon config block; use
environment-only opt-in until a supporting Rush release is selected.
Every setting uses environment > config > default. Boolean overrides accept
only 0/1; numeric overrides accept finite unsigned decimal numbers. Unknown
keys and unknown RUSH_DAEMON* variables fail validation.
daemon key |
Environment override | Default | Runtime status |
|---|---|---|---|
enabled |
RUSH_DAEMON |
false | Client routing |
autoStart |
RUSH_DAEMON_AUTO_START |
true | Only after opt-in |
idleTimeoutSeconds |
RUSH_DAEMON_IDLE_TIMEOUT_SECONDS |
900 | Host idle shutdown after request/output/cleanup drain |
queueTimeoutSeconds |
RUSH_DAEMON_QUEUE_TIMEOUT_SECONDS |
30 | Admission wait limit. The default does not bound waiting behind a running compatible build; an explicit value does |
watch |
RUSH_DAEMON_WATCH |
false | Persistent host observation of requested warm projects; false keeps root/config guards only. Never schedules builds |
usePersistentIpcRunners |
RUSH_DAEMON_USE_PERSISTENT_IPC_RUNNERS |
false | Enables explicit per-operation daemonIpc Node launchers for unsharded incremental daemon builds |
warmIdleTimeoutSeconds |
RUSH_DAEMON_WARM_IDLE_TIMEOUT_SECONDS |
300 | Idle runner, project-watcher and retained-result eviction |
warmMemoryBudgetMB |
RUSH_DAEMON_WARM_MEMORY_BUDGET_MB |
512 | Best-effort sampled RSS budget in MiB, not a hard ceiling. Compared against whole-daemon RSS plus measured child RSS, so keep it above the daemon baseline (~130-190 MiB) |
warmSetMaxProjects |
RUSH_DAEMON_WARM_SET_MAX_PROJECTS |
20 | Best-effort limit on projects holding warm resources (active runners, watchers); retained results of resource-free projects do not count. Never trims requested execution |
autoWarmByTelemetry |
RUSH_DAEMON_AUTO_WARM_BY_TELEMETRY |
false | Measured retention ranking with conservative LRU fallback; no speculative scripts |
For genuine persistent Node execution, enable usePersistentIpcRunners and add
operationSettings[].daemonIpc: { entryPoint, args? } in the project's config/rush-project.json.
entryPoint is explicitly project-root-relative, including for inherited/rig settings, and must reside in a
dedicated implementation subdirectory. Node is spawned directly on Linux and Windows; args are literal tokens,
followed by non-ignored native custom parameters. Arbitrary shell strings are not reinterpreted.
The complete implementation directory is fingerprinted (256 entries, 16 levels, 8 MiB maximum); imports outside
it other than Node built-ins are unsupported. Ordinary input/output files must live outside that directory.
Code or descriptor changes replace the old generation, while ordinary input changes reuse the child.
The tool must implement the native IPC protocol and report real RSS; no extra runs manufacture telemetry.
IPC is non-cacheable. Rebuild, NoOp/missing-script behavior, preassigned shards and non-opted-in/native paths
stay unchanged, and existing watch-only IPC declarations do not activate this mode.
Ordinary daemon status reports raw measured workspace.warmSet.projectRanks when available.
Timeouts must be positive and at most 2147483.647 seconds; queue timeout additionally
accepts zero and is rounded down to milliseconds. Memory budget must be positive
and no larger than JavaScript's maximum safe integer. Project count must be a
positive safe integer. The session automatically owns these warm policies for its real
graph and watchers. Executing/prepared work and protected resources are not evicted.
Missing child-memory measurements stay explicitly unknown; unavoidable active/base
memory pressure is reported rather than hidden. No warm-set setting changes build correctness.
Project observation previously ran regardless of watch. Its existing default false
now disables host project observation; set it to true to retain observation between
requests. Every explicit native request still refreshes inputs and effective configuration.
Changing this flag neither discards warm results/runners nor starts scripts; safe idle
maintenance applies watcher changes and reports deferred or failed cleanup.
rush-client daemon start explicitly requests startup, independently of
daemon.enabled, autoStart, or CI execution routing. It conflicts with
--no-daemon. It is idempotent: an existing compatible daemon is reused, not
reconfigured. Startup uses the same detached, locked launcher as automatic
startup and selects an attested launcher rather than guessing a path for another Rush version.
With an explicit matching launcher, a daemon implementation-version mismatch triggers
ownership-checked replacement under the start mutex before executing any command.
It does not replace a peer lacking safe shutdown support. Foreign package installation
is a client preparation step; host self-restart selects only bundled or already cached
compatible installations, never installing while the old workspace is being cleaned up.
rush-client daemon status only connects and checks hello/pong. It never starts
a process, reclaims files, or treats a PID file as evidence of readiness. Both
commands print one JSON object with state: "ready", socketPath, and the actual
pong fields (uptimeMs, available versions, optional pid and
residentMemoryBytes, and an optional workspace snapshot). Exit code 0 means protocol
readiness, not build support. An unreachable/incompatible endpoint, invalid
arguments, or startup failure returns exit code 1 with a diagnostic.
The optional workspace snapshot reports the provider generation/token, graph existence, and available warm accounting without initializing a graph. Missing fields are unknown, not proof of zero memory or successful reload. Status can inspect a protocol-compatible daemon with a different implementation version; start requires the bundled version to match.
workspace field |
Meaning |
|---|---|
generation, generationToken |
Current provider generation and installed session identity |
lastReloadTier |
Lifecycle-owned 0 initial/reuse, 1 successful in-process reload, or 2 requested restart; older peers may omit it |
graphInitialized |
A graph exists; this does not attest build success |
warmSet.configuration |
Effective watch and four warm-resource settings; older peers may omit watch |
warmSet.maintenanceState, warmSet.maintenanceFailure |
Running, quiescing, stopped or failed maintenance; stopping it does not itself free resources |
warmSet.retainedProjectNames, warmSet.protectedProjectNames, warmSet.watchedProjectNames |
Actual retained/protected projects and resident project observation |
warmSet.daemonResidentMemoryBytes, warmSet.measuredRunnerMemoryBytes, warmSet.unmeasuredRunnerCount |
Daemon RSS, last-completion child RSS samples, and explicitly unmeasured resident runners; descendants are not included |
warmSet.overMemoryBudget, warmSet.overProjectLimit, warmSet.cleanupFailures, warmSet.deferredReason |
Outstanding footprint pressure, cleanup failures and maintenance deferral |
An absent warmSet means no controller is attached, not that the workspace consumes
no memory. Status reads lastReloadTier from the lifecycle (zero for a host without one);
it does not infer a tier from PID/generation changes or initiate a reload. Tier 2
attests a restart request, not completion of successor startup or success of a command.
rush-client daemon stop requires protocol >= 0.6 and waits for shutdownAck
followed by EOF. It reports state: "shutdownAccepted" with exit code 0; this
does not assert successful workspace disposal. Stop is idempotent: when nothing
listens at the endpoint it reports state: "notRunning" with exit code 0. An
unsupported protocol, missing acknowledgement, handshake failure, or timeout
returns exit code 1. It does not auto-start anything.
rush-client daemon stop --force stops a running daemon the same way, then waits
(up to 15 seconds) for it to release its listener and ownership record and removes
any remaining artifacts, such as an abandoned startup reservation, reporting them in
removedPaths. When none is listening, it removes this workspace's leftover ownership record
(<key>.pid.json), socket, and startup reservation (.starting), then reports
state: "reset" and the removedPaths (or state: "notRunning" if nothing was
left behind). It holds the start mutex, proves that no listener is bound, and
refuses (exit 1) while the recorded owner PID still exists and cannot be shown to
be a reused PID. It never kills a process. Automatic startup already reclaims
the common leftovers on its own (see below); this is the documented escape hatch
that every fail-closed startup message points to.
rush-client daemon restart first verifies that the selected Rush version has a
launcher and captures the original lock's PID/start timestamp, checking that it
matches pong's positive PID and the selected endpoint, then performs acknowledged
shutdown. It waits for original ownership release or a demonstrably dead owner
before calling the existing locked starter. A live owner fails closed at
the startup deadline; no PID is killed and no live ownership record is deleted.
A newly
started/reused successor must pass hello/ping before reporting state: "ready".
When nothing listens at the endpoint, restart starts a daemon exactly like daemon start.
Automatic and explicit startup reclaim stale artifacts only when that is provably
safe: while holding the start mutex with no .starting reservation, a socket
without an ownership record, or an unreadable/corrupt record, is removed only after
a connection attempt is refused (so no listener exists). On Linux, a record whose
PID now belongs to a process that started after the record's startedAt (PID reuse)
is treated as dead; other platforms fail closed and point to daemon stop --force.
Restart is explicit even when automatic startup or CI execution routing is
disabled, but conflicts with --no-daemon. The two-phase host retains ownership
until workspace disposal succeeds, so embedded hosts can restart a workspace
without exiting their process. Failed cleanup retains the live lock and causes a
bounded restart failure, even if the socket has already disappeared. A changed
owner is reconnected and validated, not overwritten.
rush-client daemon logs prints a snapshot of the selected workspace's launcher
log, whether the daemon is running or stopped. It never connects or auto-starts.
The stable path comes from getDaemonLogFilePath(paths): <lockfilePath>.log.
Detached child stdout and stderr are appended to this file across restarts; the
parent closes its descriptor after spawning. On POSIX the launcher enforces mode
0600 and rejects linked destinations; Windows uses the existing per-user
transport directory permissions.
Default reading is bounded to the size observed when the log is opened, with chunked, backpressured output. Empty snapshots succeed without output; missing/unreadable logs or invalid destinations fail with a diagnostic and exit code 1. A launcher log may not exist for a daemon started outside this client.
rush-client daemon logs --follow also streams bytes appended after EOF, using
at most 64 KiB per read and waiting for output backpressure before reading more.
It never connects or starts a daemon, including when following an empty log.
SIGINT/SIGTERM cancel following with exit code 130 after closing the log descriptor;
pending display bytes may be discarded rather than keeping the client alive
indefinitely behind a stalled output pipe. Read/write failures remain explicit errors. Truncation,
replacement, or removal fails explicitly instead of silently following the wrong
file; reopen the command after rotating a log. No automatic rotation policy is added.
On Windows, following redirected stdout uses an invocation-owned, output-only Node
process. Windows pipe writes can block the CLI event loop, and moving a blocked
write to its filesystem thread pool alone can still prevent shutdown. The CLI
instead sends one acknowledged chunk of at most 64 KiB to the isolated writer.
Cancellation stops and joins that writer before the CLI exits; a lost parent
also terminates the writer, so blocked display output is not orphaned. The worker
does not run user NODE_OPTIONS preload hooks or any Rush command. This is not
daemon startup. Default snapshots, TTY output, and the non-Windows output path
retain their existing stdio behavior.
This is the text launcher stdout/stderr log, including startup errors—not WS5 structured observability or a subscription to request-scoped events.
Set RUSH_DAEMON_EXPERIMENTAL=1 and use an explicitly started daemon:
export RUSH_DAEMON_EXPERIMENTAL=1
rush-client daemon start
rush-client daemon graph show
rush-client build --to my-project
rush-client daemon graph scope-out --project my-project
rush-client daemon graph scope-in --operation 'my-project (compile)'
rush-client daemon graph invalidate --project my-project
rush-client daemon graph pause
rush-client daemon graph status
rush-client daemon graph resume
rush-client daemon graph watchAll graph commands connect only; they never auto-start, initialize the graph, or
fall back to native Rush. Cold show and status report initialized: false
without an operations field. Other verbs fail explicitly until a supported,
explicit build request has initialized the graph. The gate is checked both by the
CLI and against the server request's environment. Older or unsupported servers,
invalid arguments, and unknown selectors fail, never invoke a shell.
show and status both emit a complete point-in-time metadata snapshot, including
operation IDs, exact project/phase names, native enabled states, observed statuses,
dependency IDs, manual-mode/scheduled flags, and a path-free invalidation summary.
An operation without an observed execution status reports null. An idle operation
whose actual completed result was evicted reports READY for request-time revalidation,
not historical success; inspection does not schedule work. Snapshots contain no
environment, runner, log, or terminal objects.
Protocol 0.9 snapshots include an opaque workspaceGeneration token. Every mutation
echoes a token, checked under exclusive admission before touching the graph. The
token changes on soft reload and process replacement, preventing stale operation
references from affecting a new generation. Use --generation TOKEN with a token
from an earlier snapshot to preserve that reference; the client never refreshes an
explicit token. Without this option, the client privately reads current status
before submitting the mutation. A reload between those requests fails closed.
Mutations reject older peers before submission; read-only inspection remains compatible.
Graph requests use the same resolved queue options as ordinary execution:
--no-wait/--wait-timeout override environment, config, and the 30-second default.
An implicit generation lookup and its mutation share one positive admission
deadline; time spent connecting and reading the token is not reset before the
mutation. If the budget expires, no later query is sent. Zero requests immediate
server admission, as does --no-wait, without imposing a zero-length connection
timeout. Cancellation covers generation preflight too. Once a mutation is admitted,
its execution is not timed out by the admission budget.
scope-in, scope-out, and invalidate require one or more repeated
--project NAME or --operation ID pairs. Names and IDs match exactly; there are
no globs or implicit all-project selections. Every selector is validated before
any mutation. Scope-in enables transitive dependencies. Scope-out includes
transitive consumers and uses native safe-disable, which also prunes dependencies
no longer needed by enabled operations. Invalidation marks selected native results
stale without scheduling work. Mutations use exclusive workspace admission and
never change an active iteration. Scope changes/invalidation reject an already
prepared iteration instead of modifying stale execution records.
Pause/resume change native pauseNextIteration: manual mode gates automatically
scheduled iterations, not explicit build requests. Resume does not create new
work. If an engine owner has already prepared an automatic iteration, resume
acquires the native execution lease, discards the old unstarted plan, reconciles
inputs, and prepares its replacement before releasing it. Admission and the native
lease remain held until native idle, including
if the resume client disconnects. The reference client's watch command is not a
build scheduler; the default lazy engine still rebuilds only on explicit requests.
Native build requests apply their own selections, so a graph scope is not a
persistent override of later build arguments.
Graph stdout is NDJSON only. Snapshots use the existing extension event envelope
with payload.name: "rushd.graph-snapshot" and
payload.data: { requestId, snapshot }. The existing requestResult is emitted as
the terminal record; queue positions and request rejections also retain their
control-message shapes. Local failures use { kind: "graphError", message }.
No human renderer, ANSI styling, or graph-specific transport is involved.
watch emits an initial snapshot followed by relevant graph state, invalidation,
and idle updates. It holds no scheduler lease, so other clients can build.
Slow consumers receive coalesced latest snapshots rather than every intermediate
transition or an unbounded event history. SIGINT/SIGTERM cancel the subscription
and wait for the authoritative aborted result (exit 130). Disconnect removes
subscriber resources without cancelling another client's build. Graph hooks are
installed once per graph, not once per connection.
Origin issues #5894 and #5896 still require reconciliation with the canonical
reporter contract and built-in reporters. The current wire consumers use
IDaemonEventEnvelope/DaemonEventType; ClientOperationRenderer feeds
DaemonRendererHost, whose default is LegacyCollatedRenderer. These inherited
contracts are not replaced by the WS4 terminal, graph-admission, or log-follow
fixes. Explicit reporter controls continue to select the native reporter path.
Reporter reconciliation remains a separate epic decision, not an assertion that
the WS5 default flip or the full daemon transparency matrix has passed.