Native capability-bounded command shell for EriX.
  • Rust 99.7%
  • Python 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Erik Inkinen 5249114a63
All checks were successful
CI / markdown (push) Successful in 12s
CI / rust (push) Successful in 7m23s
chore: Merge native CLI development into main
Merge the selected feature/native-cli history with an explicit two-parent commit so main retains the development lineage and the validated source snapshot. The resulting tree is identical to the selected feature commit; no dependency pins or runtime behavior are changed by this merge.

Previous main: 22de788cec
Selected feature: 70521817b0

Publish this integration point following the requested cross-repository merge. Preserve the feature branch and immutable dependency objects for reproducibility; do not squash, rebase or rewrite existing commits.
2026-09-12 08:28:42 +03:00
.github build: Pin dependency and CI helper source commits 2026-09-12 07:44:18 +03:00
docs feat: Implement cd with held directory authority 2026-09-07 17:39:06 +03:00
manifests fix: Admit native time metadata launch ceilings 2026-08-26 20:15:28 +03:00
scripts fix: Isolate descriptor cleanup fallback stack storage 2026-09-09 08:04:58 +03:00
src build: Pin dependency and CI helper source commits 2026-09-12 07:44:18 +03:00
tests fix: Isolate descriptor cleanup fallback stack storage 2026-09-09 08:04:58 +03:00
.editorconfig Initial commit 2026-08-05 19:47:26 +02:00
.gitignore Initial commit 2026-08-05 19:47:26 +02:00
.markdownlint-cli2.yaml Initial commit 2026-08-05 19:47:26 +02:00
ARCHITECTURE.md build: Pin dependency and CI helper source commits 2026-09-12 07:44:18 +03:00
Cargo.toml build: Pin dependency and CI helper source commits 2026-09-12 07:44:18 +03:00
CODE_OF_CONDUCT.md docs: Synchronize shared contribution and review policies 2026-09-12 06:38:10 +03:00
CONTRIBUTING.md build: Pin dependency and CI helper source commits 2026-09-12 07:44:18 +03:00
LICENSE Initial commit 2026-08-05 19:47:26 +02:00
linker.ld feat: connect delegated interactive parser 2026-08-21 04:31:03 +03:00
README.md build: Pin dependency and CI helper source commits 2026-09-12 07:44:18 +03:00
ROADMAP.md build: Pin dependency and CI helper source commits 2026-09-12 07:44:18 +03:00
rustfmt.toml Initial commit 2026-08-05 19:47:26 +02:00
SECURITY.md docs: Synchronize shared contribution and review policies 2026-09-12 06:38:10 +03:00

exsh

exsh is the authority-bounded native EriX shell and a clean-room implementation of the POSIX.1-2024 Shell Command Language.

EriX is a clean-room, capability-based microkernel operating system written entirely in Rust.

See:

Purpose of This Repository

This repository owns the POSIX shell language, shell execution environment, interactive workflow, and the freestanding exsh executable. Shell syntax, arguments, variables, pathnames, and environment entries are data. They never prove possession of a capability or authorize lookup, launch, or resource use.

The current first implementation slice provides:

  • an allocation-free, no_std token recognizer over borrowed caller input
  • longest-match POSIX.1-2024 operators, including ;& and IO locations
  • exact raw source spans with quote and line-continuation metadata
  • grammar-aware recursive $() recognition through caller-owned work, boundary, inner-frame, and nested here-document storage, including command strings inside double quotes, parameter expansion, and arithmetic expansion
  • allocation-free preparation of the POSIX backquoted commands string in caller-owned byte and raw-offset arenas, with exact unquoted/double-quoted backslash rules and no private command-size ceiling
  • explicit incomplete-input diagnostics for interactive continuation
  • exact NUL rejection and tests with caller-sized input larger than 256 KiB
  • an allocation-free command-unit parser that emits balanced events into caller-provided storage without a product-sized command or event ceiling
  • contextual assignment and reserved-word recognition for simple commands, redirections, pipelines, AND-OR groups, and list separators
  • ordered here-document delimiter handoff at the terminating newline without tokenizing any body byte
  • opaque caller-retained checkpoints that resume pipelines and AND-OR groups after ordered body consumption without replaying emitted syntax events
  • allocation-free pre-effect validation and source-order iteration of complete simple-command pipeline lists, including exact caller-owned status reduction for POSIX pipefail and leading-! semantics without a command-count ceiling
  • iterative parsing of brace groups, subshells, complete if/then/elif/else/fi branch structure, case clauses, and for/while/until do-groups plus POSIX function definitions with caller-provided opaque frames, streamed names/selectors/patterns, explicit control/header events, and trailing redirections
  • allocation-free here-document quote removal and ordered body scanning, including multi-line delimiters, <<- tab stripping, continuation rules, explicit command-string EOF treatment, atomic caller-byte/raw-offset preparation, and caller-sized body emission
  • a freestanding position-independent exsh_entry executable contract
  • allocation-free parsing of the complete canonical LCS1 v1.5 argument, environment, shared-open-description, and arbitrary sparse-descriptor image, including its authenticated nonzero descriptive child PID and route-bound non-authoritative script-envelope cross-check; sparse aliases address one child-local namespace where IDs zero through two name the fixed stdin/stdout/stderr descriptions and IDs from three name authenticated dynamic descriptions
  • allocation-free interpretation of the complete POSIX.1-2024 sh invocation synopsis, including grouped flags, attached/separate -o and +o names, --, source selection, invocation-derived $0 and positionals, explicit or terminal-dependent interactivity, and 20,000 caller-sized operands; borrowed command-file bytes remain data and cannot authorize lookup or opening
  • invocation-selected persistent $0, positive parameters, and option letters; invalid invocation state is rejected before any environment or parameter arena changes
  • a caller-owned shell variable table that borrows initial LCS1 values, applies the POSIX invocation default when IFS is absent, retains an explicitly transported IFS state exactly, preserves export/readonly attributes on both set and declared-but-unset names, distinguishes unset from a set null value during parameter lookup, and supports exact-capacity assignment, export, readonly, unset, iteration, and live-state rebuild without a private variable or byte ceiling
  • caller-owned positional/special state, simple scalar parameter expansion, braced operator recognition and lazy set/null selection, and initial $@/$* field formation with exact field/byte/origin storage that preserves empty fields, quote state, and later splitting eligibility
  • allocation-free POSIX field splitting over those initial fields, applying IFS only to unquoted expansion-result bytes while retaining literal, quoted, and zero-width quoted-null provenance in exact caller replacement arenas
  • allocation-free whole-word composition over independently interpreted literal, scalar-parameter, and positional-parameter parts, including repeated $@ edge attachment and subsequent POSIX field splitting in exact caller arenas
  • stateless two-pass traversal of exact raw word ranges through unquoted, escaped, single-quoted, and double-quoted spelling into those composed fields, decoding POSIX dollar-single-quoted escapes, and resolving the supported scalar and positional parameters plus POSIX tilde prefixes from current HOME and explicit caller-supplied named-user data, with exact absolute diagnostics and strong caller-arena rollback
  • transactional traversal of selected default, assignment, error, and alternate parameter words through caller-owned frames, mutation/capture scratch, replacement environment storage, and output arenas, preserving lazy ordering and quote origins without evaluator or namespace authority
  • an assignment-value transaction entry that composes those guarantees with suppressed splitting, empty-value retention, and initial/post-colon tilde recognition while keeping selected operator words under regular tilde rules
  • a here-document transaction entry that preserves quoted-delimiter bodies literally or performs ordered parameter, command, and arithmetic expansion for unquoted delimiters, with POSIX body backslash/quote rules, one unsplit result, exact raw-offset recovery, staged mutation, and no evaluator or stream authority
  • POSIX arithmetic expansion in that transaction, including recursive parameter-token expansion, required signed-long operators and constants, ordered variable assignment in the shared mutation stage, decimal result composition, quote-aware splitting provenance, and exact caller-arena failures
  • authority-free integration of externally captured $() and legacy backquote stdout into ordinary words, selected parameter words, assignment values, and arithmetic tokens, with canonical retained grammar boundaries, pending/complete retry state, trailing-newline removal, exact command byte origins, transactional rollback, and no private capture-count or byte ceiling
  • allocation-free POSIX pathname expansion over completed split command fields: exact borrowed requests leave namespace enumeration and locale collation in an explicitly authorized evaluator, validated ordered matches replace each pattern, no-match and set -f retain literals, assignment values bypass the phase, and final caller fields discard quote origins without acquiring namespace or capability authority
  • allocation-free POSIX case selector/pattern finalization and lazy streamed selection: exact pending/complete comparison captures bind to source-order clause/pattern indices, the first match prevents every later pattern expansion, ;& executes later bodies directly, and final status remains descriptive data without evaluator, locale, process, or launch authority; the runtime copies only the expanded selector into stable caller scratch, commits selector and reached-pattern assignment effects transactionally, and executes selected or fall-through bodies in the current environment
  • an authenticated deployment-patched NOBITS workspace whose exact page-rounded extent, command bytes, parser arenas, pending outer here-documents, command events, retained raw here-document ranges, persistent variable slots/bytes, persistent and replacement-candidate trap slots/action bytes, positional slots/bytes, open-description slots, sparse bindings, reverse journal, per-description I/O state, command-local producer/alias/body arenas, launch-record bytes, distinct command-search bytes, caller-sized typed pipeline-job slots, and compiler type layouts are bound into the signed executable; no source constant limits command length, event count, nesting, or initial shell-state capacity; the sole sub-page rounding tail is explicitly zeroed and remains unavailable to every typed or byte arena; packaging proves the section and writable-segment extent, while runtime uses only its stable aligned start and signed length
  • interactive shell-local Up/Down command history, Left/Right insertion cursor, Backspace-before/Delete-after editing, and fragmented physical/serial CSI input. EXS21 deployment-selected line/draft/history arenas replace hidden history limits; only parser-accepted complete entered logical lines are retained, including all semicolon-separated units rather than only the final command. Accepted prefixes use the inactive draft arena until the line's final newline or exact EOF; continuation editing preserves them without enabling history navigation. A private retained-input cursor offers each original byte once, even when runtime planning splits a list or reorders asynchronous here-documents. Syntax errors and cancellation discard the staged line, oversized entries are skipped without rejecting execution, and eviction/exit erase storage. Multiline recalls retain exact bytes and display control bytes as caret notation. A bounded moving viewport handles wrapping without addressing another terminal or erasing the whole screen
  • editing acquires raw/no-echo mode only through the shell's already-held, active, focused own-session lifecycle endpoint; input identity is a compatibility check, not authority. Saved flags/dimensions come from that lifecycle, not an arbitrary input provider. Other modes, including signals, remain intact, and exact prior flags are restored before foreground commands, traps, EOF and error cleanup. Foreign inherited input or a rejected mode acquisition keeps canonical input; scripts and command strings are unchanged
  • receipt-free stream calls over proof-built stdin/stdout/stderr descriptors, with one correlated lib-stdio cursor set shared by every alias of an open description, partial progress, forward-only aliased-writer resynchronization, backpressure retry, explicit EOF/half-close for process-owned descriptions, and fail-stop retirement of any unexpected received authority; fixed stdin/stdout/stderr descriptions are borrowed session-scoped PTY aliases and therefore never half-close the shared stream merely because one process exits
  • program-independent external launch projection: executable spelling and utility operands never select object grants, timed routes, inspection, or child cwd/resolver authority. Launchd normalizes the requested generic ABI against authenticated manifest policy; the exsh-owned deployment catalog exposes the native-command read/write/enumerate/create/unlink/rename delegation families, one-shot directory metadata/touch delegation, and exact job-lifetime inspect-process(session). These are upper bounds, not grants: independent tty-session, script-envelope, child-manifest, exact-request, and invocation-intent intersections remain mandatory
  • a mode-aware command-source loop that streams ordinary stdin without prompts, emits primary/continuation prompts only for explicit--i standard input and only to stderr, and feeds borrowed -c bytes without reading stdin, including multiple units, a final line without newline, and command-string here-document EOF; shared stdin is fetched one byte at a time so a command that consumes that stream sees the exact first byte after its parsed command. The loop retains fragmented input in an amortized-linear sliding window, preserves parser frames plus exact raw body and delimiter ranges across ordered here-document handoffs without replay, terminates non-interactive input on syntax, expansion, or resource failure, and clears every retained byte and parser record at explicit command finish or source teardown
  • an allocation-free borrowed synchronous-list validator that checks the complete event prefix and every source-backed assignment, word, and redirection before any effect; its action stream exposes caller-sized simple commands, arbitrarily nested brace groups, complete if/elif/else regions, case selector/pattern/item actions, for name/word/body actions, and while/until condition/body boundaries in source order with exact &&, ||, ;, and newline conditions. These compounds execute in the current shell environment without recursion or authority. An explicit for word list is expanded once in source order before iteration; an omitted in snapshots the then-current positional parameters as if by "$@". Loops retain only bounded values plus opaque action/here-document replay positions and the last body status in the validated, caller-owned parser- frame and authenticated item/byte arenas; every active slot and owned byte is erased at completion. Parenthesized compound lists execute in a caller-sized subshell environment: exact variable, positional, option, status, descriptive parameter, and function-namespace state and the complete trap slot/action-byte table are snapshotted into authenticated workspace arenas and restored at the matching ). Caught conditions reset to default on entry, ignored conditions remain ignored, and child replacements cannot escape. Definitions, replacements, and unset -f inside the list therefore disappear on leave while inherited functions remain callable inside it. Nested exit terminates only that subshell, break and continue cannot cross its loop boundary, and leading ! inverts only its completion status. The runtime reuses the already admitted descriptor table; it neither clones capabilities nor imports process or namespace authority. POSIX function definitions are inert namespace mutations over authenticated caller-owned slot, byte, event, here-document, and command-substitution- boundary arenas. Invocation snapshots the selected definition so replacement or unset -f during the call cannot alter its active body, gives the body a restorable positive-parameter scope while preserving $0, and executes nested calls through caller-owned frames rather than Rust recursion. Special built-ins retain their required precedence; functions precede regular built-ins and external utilities. Prefix assignments commit before entry, return [n] leaves only the innermost active call, and every call snapshot, parameter checkpoint, and execution frame is erased on completion or error. Each nested dispatch checkpoints the live compound-control state at its call site, so break and continue can target only loops opened by that callee; exit retains its ordinary meaning and unwinds every active call scope. Invocation-site redirections are expanded in the caller before entry. The function definition's compound trailing redirections are then expanded for every call after its positive parameters are installed, so they can observe the call arguments. Both layers apply in parser order and remain active through the body. Normal completion, return, scoped exit, loop transfer, and every error restore them in strict LIFO order while erasing their retained local here-document producers and reverse journals. Scope checkpoints contain only descriptive table accounting; endpoint/provider cleanup remains in the already admitted job-bound resolver path. Expanded POSIX break [n] and continue [n] special built-ins commit prefix assignments, then request a descriptive transfer only after command-local descriptors are restored. Transfer unwinds caller frames and compound regions in source order, retires skipped here-documents, clamps an oversized positive depth to the outermost active loop, and replays only the selected loop for continue. Skipped patterns are never expanded, skipped here- documents are retired without preparation, and pipes, asynchronous work, negated non-subshell compounds, general non-function compound redirections, and other unsupported compounds reject the complete unit. An active pathname pattern in a for word remains an explicit unavailable-expansion result until the general authorized enumeration path can complete it
  • a runtime-integrated caller-owned simple-command expansion transaction that expands regular words first, resolves saved redirections left-to-right, and then expands prefix assignment values. It applies tilde/parameter/command/ arithmetic, field-splitting, pathname-request, and logical quote-removal phases as applicable, stages every variable effect in alternating replacement environments, and returns aggregate command fields plus a borrowed typed redirection iterator with explicit commit or discard. It can revalidate and prepare exact retained raw here-document associations just in time for a selected command; skipped list entries advance only the association cursor. No aggregate operation imports namespace, stream, process, launch, or capability authority
  • an allocation-free authority-free redirection transaction that resolves one validated operator into a caller-owned target descriptor and typed pathname- open, descriptor-duplicate/close, or here-document intent. Ordinary operands receive tilde/parameter/completed-command/arithmetic expansion and quote removal without splitting or pathname lookup; prepared here-document bodies retain their literal/expanded policy. set -C, default descriptors, caller-sized decimals, ordered staged mutations, command-capture retry, exact capacity diagnostics, and total discard/failure cleanup are explicit, while lookup, opening, descriptor application, and stream authority remain outside the language layer
  • a separate allocation-free descriptor-startup proof that requires canonical bound-receiver-skipping capability slots, complete description references, dynamic standard-descriptor bindings only when their corresponding fixed endpoint slots are proven absent, and an exact full-CSpace match of send-only endpoints; a proof-built caller-owned table seeds only the authenticated fixed stdio subset plus arbitrary sparse u64 aliases with shared cursors and exact access, while a caller-owned reverse journal provides parser-order duplicate, close, already-resolved bind, error rollback, built-in restore, and explicit future-exec commit without performing I/O or capability transfer
  • live target construction of that proof-built table before the first stream effect. Signed workspace layout v8 supplies caller-sized description, binding, reverse-journal, matching per-description reader/writer state, and disjoint local-producer, local-alias, and body-byte arenas; fixed and inherited aliases therefore select one shared endpoint cursor, while every alias of one selected here-document shares one bounded local cursor. The evaluator applies descriptor duplicate, idempotent close, and here-document intents left-to-right, keeps the expanded candidate uncommitted until application succeeds, routes built-in output and diagnostics through the live candidate table, and erases every command-local alias, producer, cursor, reference, and body byte before continuing. A local producer manufactures no endpoint or capability. Pathname-open intents for implemented intrinsics now use only the installed job-bound resolver and are restored after completion. Redirection-only exec instead commits candidate aliases and exported prefix state, stable-compacts unreachable dynamic descriptions and local bodies, finishes the final alias of each dynamic stream, and releases each unreachable pathname provider by its authenticated cleanup identity. Fixed session streams are dropped locally but never finished. Retained outer-scope journals protect only descriptions they can restore, and ambiguous cleanup fail-stops further evaluation
  • descriptor cleanup keeps its full-capacity emergency IPC buffer in one non-inlined fallback, used only while a resolver is retained. Explicit restore, accepted handoff, retained scope, persistent exec, and poisoned launch cleanup still perform the original metadata/local rollback and uncertainty checks without another transport buffer. Early-error Drop still performs exact provider release and capability retirement, retains uncertain ownership for fail-stop/retry, and erases its scratch before returning. CI checks actual freestanding release PIC cleanup/evaluator entry-frame bounds; it does not substitute a larger stack or hide malformed history records
  • literal lexical export and readonly command names select POSIX declaration- utility assignment context for later name=word operands: their values use assignment tilde and quote rules without splitting or pathname expansion, while quoted or expanded command names deterministically retain regular word expansion; line continuations are removed from every assignment name before it enters state or a final argument
  • transactional export, readonly, and unset special-built-in execution over the expanded candidate: Utility Syntax Guideline options and --, set versus declared-unset assignments, readonly ordering, variable and explicit absent-function unset modes, prefix assignments, $?, and leading ! are exact. Semantic operand failures retain every successful effect, while resource or persistent-commit failure discards the complete candidate
  • allocation-free export -p and readonly -p declaration emission from committed state using reinput-safe single-quote syntax; the language layer streams borrowed fragments through a caller callback without retaining stream authority, and the runtime uses only its authenticated bounded stdout cursor with existing partial-progress and backpressure handling
  • a transactional POSIX exit special built-in whose optional expanded unsigned-decimal status is accepted across the portable 0--255 range, whose absent operand preserves the current $?, and whose prefix assignments commit before control transfers. The runtime restores every command-local descriptor and erases local here-document state before terminating; an operand error exits a non-interactive shell with status two or ends only the current interactive command unit
  • transactional POSIX break [n] and continue [n] special built-ins whose final expanded fields select a positive caller-bounded depth. Prefix assignments commit and command-local descriptors restore before the runtime unwinds inner loop/compound state, retires skipped here-documents, and exits or replays the selected loop. Oversized depth selects the outermost active loop; out-of-loop use is the documented successful POSIX-unspecified no-op
  • transactional POSIX shift [n] over caller-owned positional state: the absent operand selects one, unsigned decimal counts through $# are exact, zero is a no-op, and removed owned bytes are erased before remaining borrowed metadata moves. Prefix assignments commit on semantic operand failure; interactive input continues after the command while the deterministic non- interactive policy exits with status two. The built-in acquires no stream, process, namespace, resolver, launch, or capability authority
  • allocation-free POSIX eval: final expanded arguments after the command name are joined with exactly one space into clear caller- owned source storage, complete byte/capacity preflight precedes prefix- assignment commit, and completion, discard, or every failure erases the entire supplied source slice and expanded candidate. Authenticated EXS21 source, event, here-document, substitution, parser, and execution-frame arenas retain fully validated programs in LIFO suffixes. The iterative current-environment executor supports nested eval, compounds, functions, loop transfer, return, command-local redirections, here-documents, exact status and leading ! without an AST or Rust recursion. Syntax failure ends non-interactive input with status two and only the current interactive unit. Eval state is descriptive and retains no evaluator callback, stream, namespace, process, launch, endpoint, or capability authority
  • authority-bounded POSIX dot (.) execution: the expanded special-built-in name is distinct from external lookup, exactly one file operand or the standard -- delimiter plus one file is accepted, and option-like, missing, or extra operands produce deterministic diagnostics. Slash-free operands use only the command-local PATH; slash operands bypass search, and unset PATH supplies no ambient default. A temporary descriptor opens through the admitted cwd-scoped resolver, reads the complete non-executable file through its explicitly selected stream cursor, and releases all provider authority before parsing. The canonical whole-program parser then retains only bounded LIFO source/events and executes in the current environment with command-local redirections, prefix persistence, exact empty/last-command/leading-! status, and return targeted to the innermost function or dot script. Every path, source, parser, frame, descriptor scope, and capability is restored or erased on completion and failure
  • authority-free POSIX exec preparation: classification precedes function, regular-built-in, and external lookup; an optional leading -- is consumed, option-like first operands are rejected exactly, and a lone - remains a utility name. Prefix assignments for exec and external candidates are marked for child export while they are already being staged, without another arena requirement. The resulting preparation borrows only caller-owned fields, atomically commits persistent prefix state when directed, and owns no pathname, resolver, descriptor, process, launch, endpoint, or capability authority. The target executes the operand-free form by persistently committing parser-order redirections and retiring authority that no live or restorable alias can reach. The utility form performs external-only direct or child-PATH search and uses the authenticated zero-return successor route. Only an exact executable-format rejection, or exact manifest absence for an executable-mode selected file, activates script compatibility. The already selected file is read through the transaction-held cwd-scoped resolver, provider authority is released and retired, and the fixed ext4 selector lib/erix/dynlink/executables/exsh.elf receives -c, the complete source, selected path as $0, and the remaining original arguments. The fallback never searches PATH; it inherits only the explicit candidate environment and descriptor view and preserves the stable job and POSIX $$ identity while retaining the successor's distinct actual lifecycle number only for collision-free, per-process transport-correlation domains. Fixed terminal descriptions request fresh successor-session stdin/stdout/stderr endpoints; they never transfer senders owned by the predecessor session that commit must hang up. Explicit provider-backed redirections keep their exact scoped description handoff. A malformed or rejected manifest remains a hard launch failure rather than a script bypass
  • bare POSIX set variable output, behavior-backed grouped set -Cfu / +Cfu changes, their named noclobber, noglob, and nounset aliases, exact bare set -o / set +o output, and implicit set [argument...] or explicit set -- [argument...] positional forms. Bare variable output commits prefix assignments, omits declared-but-unset variables, quotes every value for reinput, streams through the existing bounded stdout cursor, and currently uses deterministic portable-name byte order pending external current-locale collation. Complete slot, byte, NUL, and accounting preflight precedes either positional replacement; successful replacement reuses and clears the live caller arena so repeated operations have no history-derived ceiling. A non- option first operand starts the implicit list, after which -/+-prefixed values are data; explicit -- admits such a value first. All forms preserve $0 and every descriptive special parameter, honor leading !, restore command-local descriptors, and obtain no authority. Leading grouped or successive -Cfu and +Cfu fields update $-; separate -o option / +o option and attached -ooption / +ooption forms map the three admitted names to those same flags without installing o. They preserve an existing positional list when no operand follows, or commit atomically with explicit or implicit replacement. -C drives the existing noclobber open intent and -f suppresses pathname expansion. Under -u, unset braced or unbraced scalar parameters other than @ and * and reached unset arithmetic variables diagnose with exact ranges and fail the complete expansion; lazy logical/conditional arithmetic branches remain unvisited. +u restores the ordinary empty/zero results. Deferred standard names and letters remain distinct exact diagnostics until their complete behavior is implemented. Exact bare set -o writes the three behavior-backed names with deterministic tab-separated on/off state; set +o writes complete enabling or disabling commands in the same order so reinput recreates all three settings. Both output forms commit prefixes, leave option and positional state unchanged, stop on the first stdout failure, honor leading !, and restore descriptors
  • an authority-free first stateful execution slice for assignment-only commands and arbitrary expanded : arguments plus exact true and false, including atomic persistent variable commit, $?, and leading-! status; synchronous simple-command AND-OR/lists execute left-associatively, skip unselected effects, and retain the last executed pipeline status. Authenticated redirections apply only around those implemented intrinsics. Each validated pipeline command also exposes its exact contiguous reparseable source span, including parser-order redirections and excluding surrounding whitespace; here-document commands reject that view because their bodies are not contiguous with the command header. Parser events additionally retain the first and first-after-command source boundaries for every simple, compound, and function-definition command. An allocation-free isolated-pipeline view uses those boundaries to borrow complete mixed-syntax commands, rejects asynchronous lists and dirty or insufficient caller frames, and never creates execution authority. Piped compound commands and function definitions with contiguous source now use that view for isolated runtime launch; inherited function invocation remains a separate fail-closed checkpoint. A synchronous simple-command pipeline is validated in full before effects, reserves every low-half stream identity, and installs only authority-free launchd reader/writer correlations, then applies every parser-order duplicate, close, here-document, or authenticated pathname-open redirection in the same reversible child descriptor transaction. Every pipe role must remain explicitly referenced, including through a parser-requested alias; a redirect that erases the last reference fails before BEGIN until a no-authority logical-half-close record exists. Once COMMIT succeeds, launchd is the sole owner of every provider cleanup route; exsh drops its duplicate operation and cleanup aliases without issuing a premature semantic release, then stages the following child. It commits all children before waiting left- to-right and reduces their authenticated terminal statuses with the invocation-time pipefail and leading-! rules. External utilities retain the direct expanded launch path. A non-external simple command, compound command, or function definition is instead reparsed from its exact source by a fresh ext4 exsh.elf -c child, so built-in, compound, definition, exit, and redirection effects stay in that pipeline environment. The child receives explicit cwd, lifecycle, stream, and resolver authority plus exact set/unset variable values, export/readonly attributes, positional parameters, and supported option state. Mixed pipelines are pre-admitted in full: noncontiguous here- documents and compound commands that could observe an untransferred parent function namespace fail before any child BEGIN. Until current-environment execution is interleaved with that isolated list, every list entry must be a real multi-command pipeline; a singleton entry fails before stream allocation rather than being incorrectly isolated. A simple command that resolves to a retained function also fails explicitly instead of being misclassified as an external utility. Shell options outside the transported f, i, and u set remain fail closed; exact function-namespace transport and mixed singleton-list execution remain open. Every pending child occupies one caller-sized typed workspace slot containing its exact non-copyable status/control routes. Terminal observation or an authenticated terminating-signal acknowledgement retires both; the latter supplies the canonical POSIX signal status directly, while an ignored signal keeps the job pending. A later lookup failure observes each already committed predecessor to its finite terminal state before reporting 126 or 127; other partial submission failures and later observation failures send exact job cancellation for every remaining slot. Both paths clear the arena and retire the returned routes. Function invocations, noncontiguous command source and untransferred compound state remain explicit failures rather than silently acquiring a parent environment or residual job authority
  • an exhaustive 4,096-slot capability intake admitting only intrinsic self-authority, the bound receiver, session context, standard streams, and the job-bound cwd-resolver and CPU-accounting senders plus only the exact canonical dynamic description endpoints declared by LCS1. A nonzero script-envelope identity selects a bounded launchd endpoint with exact SEND|GRANT, while an execution-only image requires that launch slot to be absent. The accounting endpoint is always exact SEND and accepts no launch operation; the live entry consumes the descriptor-aware proof rather than treating numeric slots as authority
  • an endpoint-bound startup context read through the exact send-only session- lifecycle route: the receipt-free request carries only correlation, the response supplies the current nonzero session and realm as descriptive data, and every malformed, mismatched, authority-bearing, or cleanup-uncertain result fails before command-source or stream effects
  • an allocation-free bounded-launch planner over borrowed argv, environment, program/cwd scopes, standard streams, ordered exact-object requests, mixed provider-backed or launchd-provisioned descriptions, and sparse descriptor bindings. Complete preflight accepts either direct slices or stable indexed runtime views, rejects any declared-but-unavailable entry, and therefore needs no reference-bearing staging array or fabricated lifetime. It validates canonical order, every lib-ipc record, exact record/count/byte totals, send-only endpoint-transfer instructions, an optional explicit destination for a fresh child-job redirection resolver, and the SHA-256 transcript digest before exposing an iterator or BEGIN/COMMIT request. Each exact-object record transfers no capability: only a successful BEGIN supplies the launchd-local cwd receipt slot that scopes it, and the caller cannot name an alternative scope. Resolver and object child destinations are data, strictly ordered where applicable, and collision-checked against every fixed stream and dynamic description; exsh never transfers its own resolver. Reusable caller scratch is cleared on every result, and the planner performs no IPC, lookup, endpoint creation, authority receipt, or cleanup
  • target-side translation of one fully expanded external simple command into that indexed launch view. It borrows argv and the candidate exported environment, selects the currently held current-directory scope, preserves fixed standard-stream swap sources while retaining each target descriptor's canonical child slot. Every external invocation carries zero operand-derived authority records, including utility syntax that the utility may reject after starting. Dynamic descriptions therefore begin at the same delegated base independently of executable name and arguments. Requested child ABI slots are identical; launchd derives the actual narrowly scoped routes from the authenticated manifest, not caller spelling. The view carries every live fresh provider with its exact operation/cleanup/digest proof, and applies authority-free pipeline reader or writer correlations before parser-order command redirections. Pipeline correlations occupy the low identity half while selected local here-documents use the disjoint high half; both become launchd-provisioned descriptions, and aliases map to the same separately authenticated dynamic description without a duplicate fixed stream transfer. Remaining sparse provider/local aliases merge in numeric order. A syscall adapter drives BEGIN, fragmented SUBMIT, and COMMIT through only the fixed launchd sender, returning one non-copyable pending job whose separate status observation consumes and retires its exact routes. Monotonic transaction identities are never reused, so multiple commands may be committed before any wait without correlation collision. Ordinary child exit/denial/kill states update $?; uncertain cleanup or disposition poisons the descriptor transaction and immediately fails stop without issuing an unsafe provider release
  • a focused job-bound redirection-client boundary that derives every POSIX open flag combination, transfers only the currently held cwd sender through the fixed resolver, accepts only exact operation-then-cleanup send receipts into two caller-selected empty descriptor slots, proves the returned endpoint objects independent, and exhaustively retires observable aliases on every rejected or malformed result
  • a reverse-journaled open-description mutation that preflights all metadata before receiving authority, shares one cursor across parser-order aliases, and owns both provider endpoints until restore or committed launch handoff. Restore performs the receipt-free identity-bound semantic release; committed handoff instead proves launchd accepted cleanup ownership and drops only exsh's duplicate local aliases. A confirmed release deletes the operation sender before its cleanup sender; an ambiguous release deletes the operation sender but retains the cleanup sender and identity for exact retry. Cleanup uncertainty poisons the state rather than hiding retained authority; the freestanding target now drives this exact boundary through a narrow syscall adapter, monotonic request IDs, and audited caller-selected slots

The entry validates authenticated child-owned startup bytes and the complete local capability inventory before authenticating workspace capacity or touching a stream. It then reads the endpoint-bound current session and realm without supplying either identity as request data. It initializes the persistent environment, parameter zero, positionals, option set, $?, and $$ entirely in caller-owned typed workspace slices. Descriptor v9 additionally authenticates proof-built description, binding, journal, shared I/O-state, local-producer, and local-alias slots plus a separate local-body arena, retained here-document slots, processed-body bytes and source-offset scratch, aggregate redirection slots, pathname/body bytes, reusable operation bytes, and launch-record scratch alongside every earlier typed simple-command transaction arena; the shell PID remains descriptive data and cannot select process authority. It can now read, parse, validate, expand, and atomically commit assignment-only, :, export, readonly, and unset commands in addition to the stateless literal utilities above, including left-associative &&, ||, ;, and newline sequences. Short-circuited commands perform no here-document preparation, expansion, mutation, output, or diagnostic and leave $? at the last executed pipeline status. Environment built-ins preserve valid operand effects around semantic failures and route declaration listings through the currently applied descriptor-one binding. Duplicate, close, selected here-document, and pathname-open redirections apply in parser order for implemented intrinsics and restore afterward. Provider- backed aliases share one cursor; restore performs semantic release and retires operation before cleanup authority. Before allocation, the runtime snapshots the exact status and job-control routes retained by committed foreground and background launches; those non-descriptor slots are skipped but never accepted as stream sources. Any other unexplained occupied slot, uncertain inventory, malformed hidden receipt, or unprovable release fails stopped with no ambient fixed-stream fallback. Local input aliases share one bounded cursor, and a later lookup/access/capacity failure rolls back earlier aliases, zeroizes retained local bytes, and reports status 1 without committing candidate state. An expanded external command now searches the command-local PATH in source order, bypasses search for names containing /, keeps the original name as argv[0], and uses the bounded launchd transaction for each candidate. Only exact executable-selector VFS absence advances toward status 127; any safely reconciled non-absence failure is remembered as status 126, while a later candidate may still succeed. Unset PATH supplies no ambient default, empty components select the authenticated current-directory scope, and caller-sized authenticated scratch is the only candidate-byte bound. Closed standard streams, inherited dynamic descriptions without explicit cleanup ownership, and any other unrepresentable launch state fail closed before submission; fixed-stream swaps and fresh provider/provisioned dynamic standard descriptors are supported. Pipeline descriptions are data-only correlations: exsh holds no pipe endpoint and the launch view rejects zero, high-half, missing, or non-canonical declarations before BEGIN. Missing, mismatched, or unpreparable retained-body associations, external expansion, or exact workspace failures report status 1 without committing candidate state. No diagnostic, incomplete parse, event-capacity failure, or invalid source-backed event exposes a partial plan or reaches an effect. Controlled EOF, diagnostic termination, and runtime error all clear retained command/parser state. The executable remains ext4-only and is launched through the authenticated post-mount session owner; focused start/exit and missing-program VM evidence is owned by integration.

Clean-Room Policy

EriX follows a strict clean-room philosophy:

  • No external source code may be copied.
  • No external Rust crates are allowed.
  • No code generation tools that embed third-party code.
  • All code must be authored within the project.

Violations will result in rejection of the contribution.

License

All EriX repositories are licensed under the ISC License.

Development Model

The language library is no_std, denies missing public documentation, forbids unsafe code, and borrows complete source and startup images rather than retaining them in fixed-size arrays. The target-only runtime contains documented unsafe boundaries for the kernel-verified immutable startup mapping, the single-owner deployment NOBITS workspace, its non-overlapping parser and persistent shell- state slices, and one process-lifetime stream message buffer. All parsing, capacity arithmetic, wire validation, and authority inventory policy remain safe Rust.

CI denies Clippy warnings for host targets and the freestanding runtime, with both runtime-only and all-feature target checks covering code excluded from host execution. Tests, Rustdoc, and debug/release builds also deny warnings.

Token recognition retains quoting bytes exactly. The parser classifies reserved words and assignments only in command-name context, preserves command precedence as a balanced event stream, and hands here-document bodies to the scanner in lexical order. The caller retains an opaque fixed-size control checkpoint while bodies are consumed and resumes at a checked forward offset; only previously unseen events are emitted and all counts remain cumulative. The same iterative control engine parses function definitions, brace groups, subshells, if branches, case clauses, and for/while/until loops without recursive Rust calls. A function definition validates its Rule 8 name, rejects special built-in names, requires one compound body, and streams balanced definition/name/body events without creating a binding. A for header streams its checked name and unexpanded words directly to caller events, distinguishing an omitted list from explicit in. A case command streams its selector, each pattern, exact item boundaries, and ;;, ;&, or final-esac termination without retaining a pattern or clause list. Callers supply opaque frame slots and preserve only the reported active prefix across a body handoff; completion and errors clear that prefix. The canonical parse_command_unit_with_storage interface also locates $() boundaries by parsing each commands string with that grammar. Caller-owned iterative work frames and an exact boundary cache replace parenthesis guessing; case patterns, comments, quoted text, groups, and here-document bodies therefore cannot close the surrounding substitution. Nested here-document specifications are consumed in lexical order, and the grammar-matched closing ) provides the standard's synthetic commands-string newline when it immediately follows a delimiter. Recursive scratch is descriptive, carries no authority, and is cleared after each token and every error. The prepare_backquote_command interface shares the lexer's first-unescaped- backquote boundary, removes only the context-specific escapes required before the legacy commands string is parsed, and records the original source offset of every normalized byte. Both caller arenas are checked before either is changed. The result can feed the same canonical grammar. Once an external evaluator has executed that parsed command string and completed stdout capture, its exact raw range and borrowed bytes can re-enter the word transaction without transferring the stream or process authority that produced them. evaluate_arithmetic_expression parses one already-expanded expression into caller-owned opaque nodes, operators, values, and iterative evaluation frames. It implements the required POSIX signed-long C operator subset, decimal/octal/ hexadecimal constants, variable reads, persistent simple and compound assignments, short-circuit logical operators, and lazy conditional branches. The raw-word transaction supplies nounset policy: a reached unset arithmetic variable is an exact transactional failure, while an unvisited branch and a plain assignment target are not reads. Without nounset, unset variables retain the required zero value. The current environment remains immutable; success returns a replacement caller environment and signed 64-bit result, while every failure clears work and replacement state. Optional comma, increment/decrement, sizeof, casts, arrays, members, and address operations are rejected. The raw-word transaction expands supported parameter tokens, evaluates nested arithmetic with caller frames, stages all assignments in source order, and composes the decimal result. Command substitutions inside the expression consume only exact externally completed capture records; a missing or pending record fails before any staged change commits. ShellEnvironment builds an open-addressed variable table in caller-owned metadata slots. Initial names and values remain borrowed from the validated startup image; duplicate incoming names have the standard's undefined consequences, for which exsh deterministically retains the last record. Mutable values use only the caller byte arena. Every mutation validates all inputs and capacities before changing state, unset clears owned bytes, and rebuild_into copies only live variables into exact replacement storage so discarded assignment history cannot become a product ceiling. The table performs no expansion, command search, I/O, launch, or capability operation. ShellParameters separately borrows parameter zero and initial positional values from ordered startup arguments. Caller slots and bytes support exact repeatable positional set replacement, live-state relocation, and independent function-local scopes whose removal restores the unchanged outer list. Exit status, option letters, the authenticated LCS1 child PID, and the latest asynchronous process ID are descriptive state, never process authority. The runtime retains this parameter block for the complete shell loop and records every current literal-evaluator or diagnostic result there as the sole $? source. expand_scalar_parameter resolves longest unbraced names, one unbraced digit or special symbol, and simple braced name, decimal, or special forms into exact caller output. Unset scalars produce an empty value. expand_positional_parameter_fields forms the initial @/* field list in caller ExpansionFieldSlot, byte, and ExpansionByteOrigin storage. Separate fields retain empty values and attach surrounding fragments only at the first and last positions; joined *, and the deterministic no-splitting @ choice, use the first IFS character. Per-byte origins keep literal bytes ineligible for later splitting even when they share a field with unquoted parameter bytes. split_positional_parameter_fields then implements the POSIX candidate algorithm over that field list. It distinguishes IFS-white-space from other delimiters, preserves required empty candidates and quoted-null word edges, removes wholly empty unquoted results, and preflights the exact output field, byte, and origin capacities before changing caller storage. compose_word_expansion_fields accepts the already-interpreted subset as an ordered borrowed WordExpansionPart sequence. It appends ordinary parts to the active final field, joins the first field of each positional sequence there, preserves every later positional field, and retains quote/ origin/splitting state. split_word_expansion_fields applies the same candidate algorithm after composition, including preservation by an empty quoted part anywhere in an otherwise removed result. Tilde parts remain unsupported in the already-interpreted composer because raw traversal owns prefix recognition. The basic raw traverser keeps selected parameter-operator words explicit until the caller opts into traverse_word_expansion_transaction. That transaction evaluates supported nested words with opaque caller frames, stages := effects in caller scratch, builds a replacement ShellEnvironment only after complete preflight, and returns selected :?/? cases as descriptive errors without I/O or process action. It also applies shortest/longest prefix/suffix removal with *, ?, bracket ranges/classes, nested expansion, and per-byte internal quote origins; an enclosing double quote protects the result without quoting the pattern. Arithmetic expansion uses opt-in caller scratch and evaluator arenas in the same transaction. resolve_command_substitution_with_storage retains canonical $() grammar evidence across retries, while CommandSubstitutionCapture binds an exact raw range to pending state or borrowed complete stdout. The transaction stops at the first selected missing or pending result, never evaluates an unselected parameter word, strips every trailing newline from a completed result, retains embedded newlines, rejects NUL output deterministically, and never re-expands captured bytes. Quoted and unquoted command origins preserve empty-field and later-IFS behavior. Unselected operator words are skipped lazily, and braced length expansion counts valid UTF-8 scalar values (falling back to bytes for invalid input). The composer also rejects a raw-only dollar-single-quote part because it does not decode source syntax. traverse_assignment_value_expansion_transaction uses the same transaction boundary while suppressing splitting, preserving an empty value, and recognizing top-level initial and unquoted post-colon tilde prefixes. ScannedHereDocument::prepare_body_into atomically writes processed body bytes and one original-source offset per byte into exact caller arenas. traverse_here_document_body_transaction then returns one unsplit body and a replacement environment. A quoted delimiter makes every prepared byte literal. An unquoted delimiter permits parameter, command, and arithmetic expansion but not tilde expansion, field splitting, pathname expansion, or top-level quote removal. Top-level quote bytes remain data, backslash is special only before dollar, backquote, backslash, or newline, and nested parameter words retain their ordinary grammar. $@ and $* join with the first complete IFS character. Command results still enter only as exact immutable capture records; the language library receives no evaluator, stream, process, namespace, locale, launch, or capability authority. traverse_word_expansion_fields walks one exact raw SourceRange twice. Its first read-only pass removes line continuations, interprets ordinary quote syntax, decodes the complete portable POSIX dollar-single-quote escape set, resolves the supported simple scalar and $@/$* forms, expands a bare initial tilde from the current HOME, and derives exact field/byte/origin requirements. The named-user form accepts only a validated, strictly sorted caller table of portable login-name and NUL-free initial-directory bytes; it performs no user- database or namespace lookup. The assignment-value form additionally recognizes prefixes after unquoted colons and suppresses splitting. Successful replacement bytes carry protected QuotedTilde provenance, including a null HOME that preserves one empty field. Unknown, nonportable, or unavailable prefixes remain literal, and a source slash removes one redundant trailing home-directory slash. Only an identical second pass writes caller arenas. Dollar-quoted bytes remain split-opaque quoted literals. Unknown escapes retain both spelling bytes, hexadecimal escapes consume at most two digits, an octal value above one byte fails exactly, and a decoded NUL discards itself plus the rest of that dollar-quoted segment. Quote delimiters are omitted while every resulting byte retains the provenance needed by pathname expansion and logical quote removal. Malformed, incomplete, deferred, dirty-slot, and capacity failures therefore remain strong. The stateless word-traversal operations perform no namespace or pathname lookup, command or arithmetic evaluation, I/O, process, launch, allocation, or capability action. The opt-in transaction may evaluate arithmetic using only descriptive caller storage and the shell environment; neither it nor the standalone evaluator gains any route or callback for those external actions. next_pathname_expansion_request selects active patterns from the completed split fields. An authorized external evaluator may enumerate a namespace and derive locale collation keys, then return immutable descriptive PathnameExpansionCapture records. The language library validates exact field identity, wildcard/bracket/component/period semantics, ordering, uniqueness, completion, and all output requirements before finalize_word_expansion_fields replaces patterns or performs literal fallback. The finalized fields contain bytes only, completing logical quote removal without retaining a route, handle, callback, operation, or capability. finalize_case_selector and finalize_case_pattern instead consume one splitting-suppressed transaction field. The selector drops origins immediately; the pattern retains them only through an exact CasePatternMatchRequest. Portable comparison can use case_pattern_candidate_matches, while a locale- aware evaluator can return only an immutable pending/complete Boolean capture. CaseSelection accepts the exact current capture, advances nonmatches in source order, changes the first match directly into body execution, and makes any later pattern request illegal. It models ;& as body-only fallthrough and returns either no-match status zero or the last executed body's u32 status. All state and capture types contain only bytes, provenance, indices, phase, and status; they contain no callback, endpoint, locale service, process handle, stream operation, route, or capability. The scanner performs delimiter quote removal, body location, required continuation/tab processing, atomic prepared-byte/source-map formation, and caller-sink emission. Expansion remains a separate explicit transaction, so the lexer and scanner never guess evaluation authority. There is no arbitrary input, line, token, event, command, body-handoff, delimiter, body, expansion, or nesting-count constant.

Status

cd now changes the held directory capability, not just PWD. Relative and absolute operands, logical/physical -L/-P, .., HOME, OLDPWD, cd -, and ordered CDPATH candidates remain inside the original delegated navigation ceiling. Vfsd resolves pinned provider objects and returns a disjoint job-bound sender; shell variables never authorize a lookup. Program search, child cwd, redirections, and nested shell execution use the selected sender. Exact saved senders survive nested subshells and are restored without acquiring authority.

Ordinary and exec launches preserve the original selector and arguments without recognizing native applet names or parsing another program's operands. There is no /bin classifier, duplicated utility grammar, or startup pathname query for such classification. The running utility owns its resource requests and errors; launchd still authenticates the actual executable, its manifest, and the generic child ABI. Removing caller-side classification does not grant a global resolver or expand the held cwd's original navigation ceiling.

command -v and command -V use a generic executable probe after checking shell functions and builtins. The probe transfers only the currently held cwd sender on the authenticated launch route, receives no authority, and never launches a process. Its destination is the fixed held launchd SEND|GRANT route, never the shell's own bound receiver; that receiver remains excluded from borrowed descriptor slots. Unknown utility names are not filtered through a built-in catalog.

The environment replacement is preflighted before cwd commit. Ordinary lookup, stale-context, or environment failures preserve the old cwd and variables; uncertain cleanup fails stopped. Successful cd - and nonempty CDPATH choices print the selected logical directory, and a subsequent output failure does not undo the committed change. Focused host tests cover these state and authority contracts; fresh interactive VM acceptance is tracked by Integration.

Implemented:

  • checked source ranges and a streaming lexical interface
  • the complete POSIX multi-character and single-character operator set
  • quote/substitution-aware word boundaries and explicit incomplete states
  • IO-number and POSIX.1-2024 IO-location lexical recognition
  • allocation-free parsing of simple commands, assignments, redirections, pipelines, AND-OR groups, and ;, &, and newline lists
  • exact ordered here-document header/body boundary handoff
  • resumable pipeline and AND-OR continuation across any caller-sized sequence of ordered here-document bodies
  • nested function-definition, brace-group, subshell, if, case, for, while, and until grammar through caller-sized frame storage, including Rule 8 function names, compound-only function bodies, streamed for headers and case patterns/items, explicit structural delimiters, ordered body pauses, and trailing redirections
  • parenthesized compound-list execution with exact caller-owned nested environment/parameter snapshots, isolated variable/positional/option/status effects, scoped exit and loop transfer, leading-! status, ordered retained here-document retirement, and no cloned or newly acquired authority
  • quote-removed multi-line delimiter recognition and processed body-byte streaming plus atomic prepared-byte/raw-offset mapping without private storage
  • transactional quoted-literal or unquoted parameter/command/arithmetic here-document body expansion with top-level quote bytes retained, POSIX backslash rules, first-IFS positional joining, exact rollback, 20,000 expansions, and a 300,000-byte caller-sized body
  • iterative grammar-aware $() boundary recognition, including nested command strings, parameter/arithmetic contexts, comments, case patterns, groups, ordered nested here-documents, exact caller-capacity diagnostics, and 10,000- level/sibling/body-handoff regressions
  • caller-buffered backquoted commands-string normalization with exact raw ranges, byte-to-source offset mapping, unquoted/double-quoted escape semantics, strong storage failure, grammar handoff, and a 300,000-byte caller-sized regression
  • caller-owned startup variable initialization, last-record duplicate handling, POSIX default IFS, exact assignment/export/readonly/unset semantics, live- state rebuild, byte clearing, and a 20,000-variable caller-sized regression
  • borrowed parameter zero and initial positionals, exact reusable caller-owned replacement/rebuild/in-place shift with removed-byte zeroization, restorable function scopes, descriptive special-parameter state, and a 20,000- positional/300,000-byte regression
  • first scalar parameter expansion for names, arbitrary braced decimal positionals, 0, #, ?, -, $, and !, with exact source/capacity diagnostics, complete nounset rejection, and a 300,000-byte name/value regression
  • braced parameter-operator recognition and the complete colon/no-colon unset/null/non-null selection matrix, including nounset exceptions, lazy unselected words, character length, exact word metadata, and deterministic rejection of POSIX-unspecified field/removal cases
  • caller-framed selected default/assignment/error/alternate word traversal, ordered replacement-environment mutation, diagnostic-only error words, internal quote provenance, exact work/candidate/output rollback, and 10,000 nested selected words without a private stack ceiling
  • smallest/largest prefix/suffix removal over allocation-free shell-pattern matching, including active/quoted wildcards, negated bracket expressions, ranges, POSIX character classes, nested pattern expansion, Unicode scalar boundaries, and deterministic invalid-byte units
  • transactional assignment-value expansion with initial and unquoted post-colon tilde prefixes, nested selected-word assignment, one preserved empty field, protected origins, and no accidental post-colon tilde rule in regular words
  • recursive arithmetic token expansion and required signed-long evaluation in the word transaction, ordered parameter/arithmetic assignment, decimal result composition, unquoted-only splitting eligibility, exact dirty/short arena diagnostics, and 10,000 nested arithmetic levels without Rust recursion
  • exact parser-bound $() and prepared-backquote result integration through ordinary, selected, assignment, and arithmetic contexts; lazy missing/pending retries, unordered exact-range records, newline trimming, quoted-empty and unquoted splitting behavior, opaque non-reexpansion, duplicate/range/NUL diagnostics, every command-result storage shortage, 2,048 captures, and a 300,000-byte result
  • exact rollback for malformed selected words, missing command captures, arithmetic errors, every transaction arena, and replacement-environment failure; 10,000 nested selected/arithmetic words and 300,000-byte removal or command-result values impose no library-owned ceiling
  • initial quoted/unquoted $@ and $* field formation with edge-fragment attachment, default/unset/null/multibyte/invalid IFS choices, exact strong field/byte/origin capacity failures, 20,000 fields, and 300,000 bytes
  • POSIX 2.6.5 field splitting for the resulting fields, with expansion-only delimiter eligibility, portable IFS-white-space coalescing, non-white-space empty candidates, quoted-null edges, exact rollback, 20,000 output fields, and a 300,000-byte output field
  • whole-word composition across already-interpreted literal, scalar, and positional parts, including multiple $@ sequences, zero-field attachment, interior quoted-null preservation, exact all-arena rollback, 20,000 composed fields, and a 300,000-byte field
  • raw word traversal across escapes, continuations, single/double and dollar- single quotes, simple scalar and positional forms, regular and assignment- value tilde prefixes, protected current/named-home provenance, absolute source diagnostics, explicit deferred command evaluation, exact all-arena rollback, 20,000 fields, dollar-quoted segments, or tilde prefixes, and 300,000 bytes
  • POSIX pathname expansion and final logical quote removal over caller-owned transaction fields, including quoted wildcard bytes, slash components, leading-period rules, bracket ranges and negation, deterministic ./.. omission, external collation keys with pathname-byte ties, no-match fallback, set -f, assignment suppression, exact rollback, 2,048 patterns, and a 300,000-byte result
  • POSIX case selector/pattern finalization plus lazy source-order matching, exact pending/complete capture identity, first-match expansion suppression, ;;/;&/final-esac control, no-match/final-body status, strong transition failure, 20,000 patterns, and a 300,000-byte selector/pattern
  • all redirection operators/default descriptors/open modes, set -C, decimal duplication and close, optional IO-location rejection, non-splitting operand expansion, literal/expanded here-document intent, ordered alternating mutation state, completed/pending command captures, exact descriptor and output shortages, total cleanup, 20,000 transaction reuse, and a 300,000-byte operand
  • whole-command parser-order aggregation of typed open/duplicate/close/body operations, exact prepared and retained raw-body association/count validation, selected-only just-in-time preparation, redirection-to-assignment mutation flow, dirty/short storage zeroization, 20,000 aggregate operations, and 300,000 aggregate pathname bytes
  • complete canonical LCS1 startup-image intake before runtime effects
  • authenticated descriptive child PID intake and persistent caller-owned environment/positional/option/status initialization before stream effects
  • exact receipt-free session-context correlation and endpoint-bound nonzero session/realm discovery before command-source or stream effects
  • exact full-CSpace validation with the required fixed send-only core and exact fixed standard-stream subset, one mandatory send-only job CPU-accounting route, plus either one bounded SEND|GRANT launch route or an absent execution-only route
  • descriptor-aware exact CSpace admission, fixed-description collision and unreferenced-description rejection, caller-sized sparse tables, shared alias cursors, strong dirty/capacity failure, reverse rollback, built-in restore, and future- exec commit across 20,000 descriptors
  • live descriptor-proof/table construction before stream effects and proof- selected fixed I/O through one cursor set per shared description
  • parser-order target application of duplicate, close, and selected here- document input, including shared-cursor local aliases, bounded reads, exact arena exhaustion, rollback, and complete body/cursor/reference/alias erasure without endpoint or capability creation
  • target-connected pathname-open application through the held cwd and job- bound resolver, strict known-slot inventory, monotonic request correlation, multiple provider-backed aliases/cursors, exact semantic release or committed launchd handoff, ordered authority retirement, hidden-receipt poison, and no fixed-stream fallback
  • redirection-only POSIX exec with persistent exported prefix assignments, duplicate/close/here-document/pathname state, shared-cursor preservation, dynamic stream close/finish, provider release, stable metadata/body compaction, nested-scope restoration, rollback before the commit point, and fail-stop cleanup uncertainty
  • utility-bearing POSIX exec with external-only direct/PATH search, utility-only argv and exported-prefix transport, complete descriptor-graph handoff, route-authenticated successor BEGIN/SUBMIT/COMMIT, zero returned job authority, stable replacement identity, persistent state on safe 126/127 failure, fresh successor-session endpoints for fixed TTY descriptions, exact scoped transfer for redirected descriptions, direct-special-built-in source disposition, and fail-stop ambiguity; exact executable-format rejection reads the selected non-ELF file through transaction-scoped resolver authority, releases both provider capabilities, and replaces the predecessor with an ext4 exsh.elf -c successor carrying full script syntax, selected-path $0, and the remaining original arguments
  • POSIX times as a no-operand special built-in: after prefix and redirection preflight it reads one receipt-free four-counter snapshot through the dedicated authenticated job CPU-accounting route, formats shell and waited-child user/system CPU durations as two deterministic C-locale lines, and reports snapshot or stdout failure without receiving clock, process, job, endpoint, or capability authority; failed launches are excluded, observed children are charged once, and an exec successor retains its predecessors' self time
  • POSIX wait over a caller-sized EXS21 background-job ledger. Authority-free preparation validates every decimal PID operand before an effect. Runtime slots retain only the descriptive child PID and its exact non-copyable status and control routes; ordered operands report the last selected status, unknown PIDs report 127, and operand-free wait consumes every known completion. A caught signal interrupts with its shell signal status while preserving the job and pending action for the next safe point. Explicit ignores are consumed, default conditions remain pending, and no PID is treated as authority
  • Shell teardown first observes each still-pending background job once through its exact status route. Natural completion retires both routes immediately; otherwise teardown sends immutable KILL over that job's retained exact control route. A correlated NOT_FOUND is terminal cleanup evidence only after an authenticated running observation, because it then proves an intervening transition on the same exact job. A deferred status reply is re-observed after yielding, up to 64 times, and grants no such inference; if still deferred, cleanup fails closed without signalling. A signal reply that claims termination must also carry a terminal job state. Exsh then consumes completion through its already-owned exact status route, requiring the same terminal state and exit status before retiring either handle. Deferred grant cleanup permits at most 64 yielded re-observations; a running, mismatched, rejected, lost, or still-deferred result fails closed. Control-only signal delivery never substitutes for status authority. Cleanup therefore neither discovers processes from numeric identifiers nor detaches a child after an ambiguous reply
  • POSIX kill as a function-shadowable regular built-in. It prevalidates the complete -s name, -name, --, and unsigned-decimal PID form before an effect. The shell may select only its authenticated $$ identity or a child already represented by an exact retained background-job control route; every other numeric PID fails without process discovery. Self delivery uses only the private trap table: ignored and caught conditions respectively complete or become pending, terminating defaults exit with the canonical 128+signal status, and default stop actions fail until an explicit stopped- shell lifecycle is available. Numeric process-group selection is deliberately absent because a scalar group identifier is not authority
  • POSIX getopts over either current positionals or explicit arguments, with clustered-option progress and an assignment-sensitive restart revision in descriptive caller-owned state, fresh-shell OPTIND=1, silent-mode OPTARG, atomic variable/cursor replacement, and no retained argument, process, stream, or capability authority. Function return restores function-local positionals while preserving the shell-wide cursor; unrelated prefix assignments remain command-local
  • POSIX umask with portable octal and symbolic mutation, deterministic octal or -S output, function-visible state, subshell checkpoint restoration, and immediate attenuation of already-authorized pathname-create redirections. The mask is descriptive permission data and does not select a VFS object or grant creation authority
  • POSIX command option parsing, recursive function-bypass execution, implementation-default /bin:/usr/bin search for -p, and deterministic -v/-V identification of functions, built-ins, and packaged ext4 utilities. Lookup uses command-local PATH unless -p is selected, prefix variables are exported only when the unwrapped form reaches an external or exec boundary, and no lookup result conveys authority
  • asynchronous-list execution for each complete AND-OR group terminated by a root &. Mixed root lists are consumed one validated element at a time, so foreground elements retain the parent environment while asynchronous elements use a fresh ext4 child. The retained unit is fully validated before BEGIN, reparsed by a fresh ext4 exsh.elf -c child, requests exact job-control and status routes, records its descriptive PID in $!, and returns status zero without waiting. Ledger exhaustion cancels the just-committed exact job; normal shell teardown cancels every remaining pending route before descriptor retirement. A selected asynchronous here-document prefix rewrites only its validated root & to a child-local newline before launch. When a same-line parent command follows that separator, the retained bodies and delimiters rotate ahead of the untouched suffix in the existing command buffer. The fresh child therefore reparses one exact contiguous synchronous unit, and the parent reparses the successor without a second source arena or added authority
  • an allocation-free POSIX trap language core over caller-owned storage: exact EXIT and canonical signal names plus the XSI numeric aliases select a fixed descriptive condition set through lib-ipc's authority-free v1 identity catalog; all 27 signal names, default-action classes, pristine dispositions, and immutable conditions correspond exactly, while EXIT remains shell-only; default, ignore, and arbitrary NUL-free shell-source actions replace atomically in compact shared byte storage; KILL and STOP retain only their immutable default; and deterministic single-quoted output can be reinput. Expanded trap commands prevalidate -p, --, action, decimal-reset, and every condition before requesting a listing or replacement. The target runtime retains live and candidate arenas in the authenticated workspace, applies redirections before effects, preflights prefix-environment and trap-table replacement together, and then commits both without another fallible step. Listings first use the clear, otherwise dormant launch-record scratch so bounded output reaches selected stdout as one logical write; output larger than that caller-selected capacity retains the complete fragment-streaming fallback. Both paths preserve partial-write status and clear scratch without acquiring another route; invalid conditions do not end input, while utility syntax follows direct- special-built-in source disposition. At normal shell termination or EOF, an executable EXIT disposition is copied into the authenticated LIFO eval arenas and reset before evaluation in the exact post-command environment. Normal completion restores the pre-action $?; exit n selects n, and operand-less exit selects that same pre-action status even after an intervening command. Source, parser, execution-frame, and replaced action bytes are erased on completion, error, or teardown. External utility, pipeline-child, utility-bearing exec, and ENOEXEC shell replacement launch intents carry only the current validated ignored-signal bit set: ignored conditions remain ignored, caught actions reset to default, and action source bytes never cross into a child. Redirection-only exec remains in the current shell and keeps the live trap table unchanged. During an idle wait, exsh reads authority-free PTY events by exact monotonic cursor from its authenticated session-lifecycle sender. Foreground waits first poll that same route with the persistent idle/foreground cursor because ordinary children inherit the shell's input PTY. Both exact job-status and independent job-control senders are validated before any event is consumed. A selected signal returns immediately; otherwise the poll reads the child's own event queue through its read-only status route with a separate job-local cursor. Each poll performs at most two reads, never consumes a second deliverable signal, and adds no authority or event queue. Exsh coalesces canonical pending conditions, consumes inherited ignores without job mutation, and delivers each other signal only through that control route. A terminating response is followed by the same exact status-consumption proof for foreground, pipeline and background jobs. A background kill retains the observed descriptive completion for later wait; wait does not consume the remote status twice or infer authority from the PID. Pipeline waits poll every live job's event route round-robin and broadcast the identity once to each live job before another status round; terminal signal status remains distinct from explicit cancellation. A caught condition also interrupts the POSIX wait built-in without forwarding through a background job-control route. Caught actions run at deterministic post-command safe points and before every potentially blocking standard-input retry. The dispatcher reads the current live disposition, clears default or newly ignored conditions, executes actions through nested retained eval frames in canonical condition order, restores the incoming $? after normal completion, and resumes exact function, eval, dot, and pipeline-list continuations without replay
  • foreground pipeline cleanup is isolated in a dedicated lifecycle module that observes or cancels every returned exact job route before clearing its authenticated slot; no cleanup path performs discovery or accepts a scalar process selector
  • exact deployment-patched NOBITS command/parser/environment/parameter storage with no local input or nesting ceiling; only the authenticated, zeroed, unusable sub-page rounding tail may follow the declared capacities
  • receipt-free stdin/stdout/stderr transport with correlated partial progress, retry, EOF, process-owned close, borrowed fixed-stream exit, output-alias resynchronization, and authority rejection
  • primary/continuation prompting, fragmented and multiple-unit input retention, ordered here-document continuation with exact raw ranges, syntax/resource/EOF diagnostics, and transactional plan retention and finish
  • borrowed simple-command validation, exact :, true, and false status including ignored regular-utility operands and !, transactional exit status/control transfer, transactional shift mutation, bare set output, implicit and explicit positional set replacement, and execution-unavailable reporting for every unsupported valid plan
  • transactional export, readonly, and unset, including declaration expansion, option parsing, mixed operand outcomes, prefix persistence, readonly order, reinput-safe output, exact sink failure, partial progress, backpressure retry, $?, leading !, candidate/commit shortage, and 4,096 caller-sized operands
  • complete-prefix validation and left-associative execution of synchronous simple-command, current-environment brace-group, if/elif/else, and case selector/pattern/item plus POSIX for/while/until &&, ||, ;, and newline lists, including first-successful consequent or first-matching item selection, source-order lazy pattern expansion, standard ;& body fallthrough, one-time for word expansion and positional snapshots, condition reevaluation, zero/last-body loop status, depth-selected break/continue, replayed retained here-document associations, skipped effects and here-document bodies, $?, leading simple-command !, later unsupported structure, 20,000 caller-sized commands, 20,000 validated compound levels, and 512 simultaneously executing loops without recursion or a private nesting ceiling
  • exit with current or explicit portable status, exact operand diagnostics, committed prefix assignments, command-local descriptor restoration, and suppression of every later effect in the current command source
  • shift with default, zero, leading-zero, malformed, excessive, overflowed, extra, and leading-! counts; exact prefix-assignment persistence, $#/$1 updates, owned-byte erasure, atomic rejection, and descriptor restoration
  • implicit and explicit positional set, grouped/successive -Cfu / +Cfu, and separate/attached named aliases with clearing, empty and later option-like values, $0 preservation, immediate $-, later noclobber/noglob/nounset behavior, nounset arithmetic short-circuiting and rollback, leading !, prefix persistence, reusable live storage, atomic capacity/option rejection, exact diagnostics, and descriptor restoration
  • bare set with prefix persistence, set-only variable selection, portable- name byte ordering, apostrophe-safe reinput quoting, leading !, exact sink failure, bounded redirected output, descriptor restoration, and 4,096 caller- sized variables without a private listing ceiling
  • complete allocation-free two-state preflight of canonical direct-launch transcripts: an unbound intent derives exact BEGIN record/byte/receipt demand without launchd-private geometry, while only the correlated consecutive reservation can bind transfer destinations and the final transcript digest; coverage includes mixed provider/provisioned descriptions, sparse fixed/ dynamic aliases, independent digest verification, strong scratch cleanup, and a 300,000-byte provisioned body
  • host-tested launchd transaction transport with process-scoped monotonic request correlation, authority-free BEGIN/SUBMIT/ABORT replies, final- fragment-only send transfers, exact COMMIT receipt admission, cursor-exact authority-free idle lifecycle-event intake, per-job event reads over returned exact status routes, event-aware round-robin foreground waiting, signal delivery and pending-job cancellation over independent returned exact control routes, and explicit abort/route cleanup when delivery or authority evidence is uncertain
  • event and status polls borrow at most 128 and 112 bytes respectively, including the existing two-receipt audit allowance, before clearing or calling; unused caller scratch remains untouched, and cadence, signal order, cursor checks, unwanted-receipt retirement and terminal route cleanup remain unchanged
  • host tests, target-none checks, and debug/release freestanding linking

Not yet implemented:

  • command evaluation and stdout capture for $() or prepared backquotes; locale-backed nonportable pattern interpretation and evaluator collation-key generation, named-user table population, general word evaluation, and the remaining built-ins
  • child-environment execution of built-ins, functions, and compounds inside pipelines, plus asynchronous lists; negated non-subshell or redirected compounds, an authenticated command-file content envelope, asynchronous wait trap interaction, and complete job control. Foreground event forwarding, deterministic caught-action dispatch, and external-child default/ignore inheritance are present. Functions, eval, and dot share the current trap table; parenthesized environments reset catches, preserve ignores, and restore the exact parent table. A future general command-substitution executor must take that normal subshell path and may not special-case a trap-only commands string
  • pipeline redirections that erase the final reader or writer reference; these require an explicit no-authority logical-half-close record so the surviving peer observes EOF or closed-reader denial without a hidden child descriptor
  • external redirection of standard descriptors to closed descriptions; fixed-stream swaps and fresh provider/provisioned dynamic descriptions are connected
  • a collision-proof child-slot remap once a procd-assigned bound-receiver slot can overlap an arbitrarily large dynamic descriptor namespace

Governance Principles

exsh is a language and workflow client, not an authority source.

  • It must never receive direct loaderd, dynlinkd, procd, global VFS, block, device, key, or unrestricted discovery authority.
  • It may submit non-authoritative command intent only through its exact bounded launchd endpoint.
  • Only a shell whose authenticated invocation has the POSIX interactive -i state sets the descriptive launch approval opt-in. There are no automatic operand authority requests. Noninteractive scripts and shell children keep the flag clear; ttyd, launchd, the manifest, and any script envelope still gate every prompt independently.
  • Standard streams, the session-lifecycle route, and returned job/status routes must be explicitly installed at authenticated child-local slots; session and realm scalars are accepted only from a correlated authority-free reply on that exact route.
  • Variables, working-directory values, command search text, and redirections stay non-authoritative until resolved by the broker path.
  • Every received endpoint remains owned until transferred, explicitly deleted, or retained in visible retryable cleanup state.
  • Git dependencies use full 40-character commit rev pins. Integration source revisions and CI caches preserve those identities, with no branch-head or synthetic-commit substitution.

Component Boundaries

  • exsh owns shell language and execution-environment semantics.
  • launchd owns command lookup, manifest validation, and ordinary child launch.
  • powerboxd and ttyd own trusted exceptional approval.
  • loaderd, dynlinkd, and procd remain private broker peers.
  • lib-stdio owns the client stream cursors used by the runtime; lib-tty owns PTY policy and mode contracts.

Contact

Development occurs in the EriX organization. Decisions and rationale are recorded in issues and design documents.

Maintainers can be reached at admin@erikinkinen.fi.