Map: core / builtins
core/src/builtins/ are the commands implemented in Rust that run inside the
shell process. builtins.rs holds the builtin_registry! macro: each entry
binds its facets at once — names, fixed arity, [[map/core/typecheck|type
rule]] (ty), doc line, and runtime body (call) — into the CORE_BUILTINS
static (&[BuiltinEntry]), so the facets cannot drift apart, and the macro
asserts a Sig rule’s structural arity agrees with the written arity. The
type-rule facet is a BuiltinTypeRule of two arms: Sig (a command signature)
or Scheme (a first-class polytype). The streaming reducer fold-lines
registers as an ordinary Scheme whose factory bakes the reducer boundary
directly (reducer_spec); there is no separate reducer
arm. Fixed arity is the registry’s job (fixed-arity).
Builtins are shell-scoped: each shell’s session carries a BuiltinTable
(shell-state) seeded from CORE_BUILTINS
(core_builtin_table), and a host’s extra sets ride a HostSurface into
driver::boot_shell, so the typechecker and the runtime read one table —
there is no process-global registry. register clones the baked prelude’s
bindings into each fresh environment. Two builtins sit outside the macro, a
host-installed pair with the hosts swapped: the public WATCH_BUILTIN
(&[BuiltinEntry]) wraps the still-private concurrency::builtin_watch /
scheme::watch so a host with a durable stdout sink (the interactive and
batch ral hosts) installs it while an agent host omits it
(watch-repl-builtin); its mirror
SERVICE_BUILTIN wraps concurrency::builtin_service / scheme::service so
the agent host (exarch), whose lease frame reaps ordinary workers, installs
the durable-birth verb while the ral hosts — which grant no lease, so every
spawn of theirs already lives until cancel or exit — omit it.
Bodies are grouped by concern, one submodule each:
strings.rs— string and regex primitives.dedentowns the raw-block framing rule: blank lines around a multiline block fall away before the common margin is stripped, while content-line whitespace is preserved;collections.rs,predicates.rs,fs.rs,codecs.rs;shell.rs—cd,alias/unalias;concurrency.rs—spawn/watch/serviceand the handle verbsawait/poll/race/cancel(builtins under their bare names;parand theis-donepredicate are prelude code over them, not builtins). All butwatchandserviceseed throughCORE_BUILTINS; those two live here too but are installed by their hosts viaWATCH_BUILTIN/SERVICE_BUILTIN, not core. On completion a block’s buffers drain once into a cachedCompletedHandle { stdout, stderr, outcome }(value.rs); the eliminators project that one settle.try_settleis the shared non-blocking sample (cached outcome, else atry_recvcompleted throughcomplete_handle; aDisconnectedreceiver — a panicked worker — settles as the same failureawaitreports, sopoll/racesee a finished block rather than spinning).await/raceproject_completedthe outcome to{value, stdout, stderr}, re-raising`err;pollis total, wrapping it as`settled{stdout, stderr, outcome:ok/err}(the`errpayload built through the sharedevaluator::scope::error_record, the recordtryhands its handler) or`pending{stdout, stderr}(a cumulative, non-destructivepeek_buffersnapshot of the running worker’s output — the buffers are left for the one-shot completiontake_buffer, so a partial poll never steals bytes), and leavinglast_statusat 0 since the block’s status is data.awaitandpollgate first onensure_live, the cancelled pre-check (the settle decision, partial-poll-pending-output). A detached worker hangs under the durable session root, not the turn’s foreground scope, so a foreground cancel never reaps it;awaitsharesrace’s cancel-aware wait loop (wait_first_settled), so a deadline unwinds the wait while the root-scoped worker survives (concurrency-detached-vs-structured). Under a frame that grants aWorkerLease,spawnarms a self-re-armingprocess::reapercallback — the idle-observation lease chain: a still-running worker unobserved foridleis reaped, where everypolland everyawait/racesweep renews the handle’slast_observedcell, under an absolutebackstopno polling extends; a worker that finished ends the chain silently, its entry lingering as an unclaimed result. A reap removes the registry entry, records aReapNoticethe host drains (Shell::take_worker_reap_notices), and cancels the worker’s scope withDeadline— never detaching the handle, so a laterpoll/awaitstill observes the partial output and failure. The class decides the chain at the spawn door:spawn_childtakes aLeaseClass, and only aWorkerbirth arms it —serviceregistersDurableand arms nothing, so no reaper entry ever exists for it; the absent chain is the durable policy, whose only bounds are the handle’s owncancel, the host’s/clear, and process exit. The spawn door also enforces the frame’s admission cap (TurnState::worker_cap): a birth of any class reserves its seat at the door (WorkerRegistry::reserve) — refused whilecapworkers are running or reserved, with an error namingawait/cancelas the remedies, the reservation held across thread spawn and released into the registered entry, so a racing sibling birth never sees a filling seat as free (workersis retired — builtins); settled entries lingering under retention hold no seat. A settled entry’s own lease is retention: the host’s per-call epoch sweep (Shell::advance_worker_epoch) stamps an entry at the first sweep that observes it settled and expires it — aRetention-causeReapNoticeon the same drain — once its unclaimed result has sat a full retention of ral calls; the eliminators still remove entries the moment a result is claimed, so the sweep only catches what nobody claimed. A worker runs its thunk on a freshstd::threadviaShell::spawn_thread(shell-state), which inherits a snapshot of the parent’s mobile state; the body is evaluated directly witheval_comp(.., Tail::Yes)under a child scope, deliberately bypassing theeval_top_level/eval_blockboundary, because the worker’s ownShellis the only one its bindings touch and they die with the thread. The worker carries the parent’s grant stack, so a forced block inside the worker still meets the standard boundary rule and any external child it spawns is confined per-command — aspawnunder agrantcannot escape it. Everyspawn_childalso files the freshly-minted handle onshell.local.workers— a per-shell registry, host-independent and carrying no policy (shell-state);await,race’s winner and its cancelled losers, and a settledpollremove the entry from whichever shell observes it, an explicitcancelremoves it too, and a pendingpollor a bare listing never touches the registry. AHandleis a resident, process-local reference: it cannot cross the pipeline-stage helper wire, so returning one from a helper-evaluated stage raises the wire diagnostic “cannot return a handle from sandboxed evaluation” (core/src/serial.rs) rather than a generic failure (capability-enforcement);modules.rs— the cachelessuse/sourceloader.evaluate_sourceis the shared guarded parse + elaborate + evaluate core (cycle stack, depth bound,ScriptContextGuard);useis a scope-projecting wrapper over it,sourceevaluates into the caller’s scope. Module loads carry no cache, so the guards keep re-evaluation terminating — see cacheless-module-loader;misc.rs— includingsurface, which forwards a tagged variant to the host’s `SurfaceSink` and is the identity under a bare REPL;math.rs— the Float rounding builtins (round,floor,ceil,trunc);help.rs—help(arity-0 command index) andexplain <name>lookup;print.rs— the value pretty-printer shared by the REPL and exarch’s tool-result rendering (PrintParams; a rendering utility, not a registered builtin);util.rs— shared helpers, JSON coercion.
The capability Value-map decoder is not a builtin: it lives beside the
authority layer in capability/decode.rs (decode_capability_map), consumed by
the grant control operator (evaluator/scope.rs) and the --capabilities
ceiling (capability/load.rs) — see capabilities,
grant.
Why a capability lands in one of these layers rather than another — builtin vs.
coreutil vs. prelude vs. control operator — is design: name-resolution;
what a builtin is and the shape of the set is design: builtins;
the from-X/to-X byte↔value typing in codecs.rs is design: codecs.
Bundled coreutils, diffutils, and ripgrep
uutils.rs declares the bundled tools as three feature-gated families and the
predicate and dispatch that unify them.
- coreutils —
declare_coreutils!takes two parallel lists:cross(always on under thecoreutilsfeature) andunix(additionally undercoreutils-unix-only,cfg(unix)-gated). It emits one mergedCOREUTILS_TOOLSslice, acoreutils_invokearm, and the platform-unconditionalCOREUTILS_UNIX_ONLY_TOOLSlist — the one authoritative spelling of theunixnames, so a caller that must know a bundled name does not exist off-Unix (a profile loader dropping dead exec grants) reads this list rather than keeping a second copy. - diffutils —
DIFFUTILS_TOOLS(["cmp", "diff"],diffutilsfeature), whosecmp_main/diff_mainshims faithfully translate the upstreamdiffutilslibentrypoints (re-audit on a version bump). - ripgrep —
RIPGREP_TOOLS(["rg"],ripgrepfeature), routed throughral-ripgrep-core::run_clibyrg_main(which drops the argv[0] slot).
uutils_invoke is the bare dispatch over all three families (diffutils and
ripgrep matched ahead of the coreutils fall-through, each arm feature-gated);
is_uutils_tool is the membership predicate. These bundled heads share the
capability chokepoint with every other command — part of why ral is a
single-binary. The grep cargo feature separately
backs the re-* regex string builtins.
A bundled head is a resolved command image, not a builtin in CORE_BUILTINS:
it runs uutils_invoke in-process only on the clean-terminal fast path, and is
otherwise an ordinary ral --ral-bundled-tool <tool> child carrying process
semantics (bundled-tools-as-exec-images).
That dispatch — the ExecImage::BundledTool placement, the hidden entrypoint,
and the inline gate — is the runtime’s
(command/uutils.rs); this page owns only the registry of names, shims, and
the in-binary uutils_invoke they converge on. docs/SPEC.md §21 covers the
single-binary tool surface.