# CLI Internals This document describes **internal/cli**, Notarius's production composition root. The [CLI reference](../cli.md) owns command syntax and exit statuses; [Configuration](../config.md) owns configuration values; and [Operations](../operations.md) owns filesystem layout, recovery, and operator procedures. ## Inputs, Outputs, And Boundaries The CLI accepts process arguments, standard streams, and injectable options used by tests and embedding code. It writes command results to the supplied streams and returns a process exit status. For a run, it also creates the production catalog and runtime collaborators, hands a prepared pipeline and source bytes to the framework, and places the logical files returned by the runner. It is the only boundary allowed to compose concrete registries, LLM clients, cache/checkpoint collaborators, debug recorders, and physical output paths. Pipeline modules receive interfaces and request data rather than CLI streams or filesystem roots. The [Architecture](../policy/architecture.md) defines this composition-root boundary; [Pipeline Internals](pipeline.md) owns resolution, preparation, and runner mechanics after their inputs are supplied. ## Dispatch And Configuration Handoff The root dispatcher handles help, configuration validation, pipeline listing, and a pipeline run. It normalizes injectable options before dispatch so that a missing production dependency fails as a command error rather than reaching execution. Commands that need configuration use one shared loader. The CLI discovers the file, parses it through **internal/core/config**, starts from defaults, applies the file and supported environment overrides, and then validates it for the command. The configured discovery and precedence contract is in [Configuration](../config.md), while the loading and resolution mechanics are in [Configuration Internals](configuration.md). Configuration validation without a selected pipeline checks structural configuration only. Validation with a selected pipeline also builds the effective catalog, resolves the pipeline, and verifies explicitly selected Scriptorium profiles. Pipeline listing validates configuration before returning normalized, sorted identifiers. ## Production Composition The production composition helper allocates every framework registry and the prompt-asset registry, then registers the generic, Seriatim, and D&D module families in that order. The resulting registries provide both the module catalog used for resolution and the concrete constructors used for preparation. Tests may provide a catalog or registries instead; production code must not silently merge an injected partial catalog with production registrations. The production LLM factory builds the Scriptorium-backed client from resolved configuration, creates one scheduler from the effective global LLM limit, and wraps the client before it reaches modules. Registration and LLM construction errors are returned before a pipeline is prepared. Concrete module keys and validator chains are public configuration choices and remain documented in [Configuration](../config.md). ## Run Orchestration After parsing and validating a run invocation, the CLI performs this ordered handoff: 1. load and validate configuration, then apply command-level operational overrides; 2. create and validate a safe run identity, then allocate a debug bundle only when requested; 3. build the effective catalog, resolve requested reference changes, resolve the effective pipeline, and verify explicit Scriptorium profiles; 4. materialize external or generated references and record redacted invocation and resolution provenance when debug capture is enabled; 5. construct registries, the scheduled LLM client, prepared modules, and the requested cache/checkpoint collaborators; 6. read the source input and invoke the framework runner; and 7. write the runner's logical output files only after a successful run, then complete the command report and user-facing result. Preparation happens before source parsing, so module construction and dependency failures cannot begin stage execution. The CLI also preserves the framework's result and warning information when it writes summaries and the final command result. Detailed state lifecycle, resume handling, and physical path confinement are maintained in [Run State Internals](state.md) and [Operations](../operations.md). For `run --json`, the CLI constructs and encodes its private run-result receipt after a successful runner result is available, before it publishes logical output files. It writes the prepared receipt to standard output only after output publication and requested debug terminalization succeed. A receipt-write failure exits with runtime status 1 and may leave partial standard-output bytes, but the already-published output bundle remains complete and requested debug reporting remains successfully terminalized. The CLI reports a bounded command-owned error and does not repeat terminal reporting. The receipt remains a CLI reporting concern rather than a framework or output-module responsibility; its public contract is the [run-result receipt](../integrations/run-result.md). ## Failure Mapping And Terminal Reporting Argument, flag, and invocation-combination failures are reported to standard error before runtime composition and use the syntax error class. Once an invocation is syntactically valid, configuration loading and validation, resolution, registration, profile checks, reference materialization, module construction, input reads, runner failures, output publication, and requested debug handling use the runtime failure class. The public status numbers and stream contract are defined in the [CLI reference](../cli.md#output-streams-and-exit-statuses). When debug capture has been allocated, one command-state value records the known run result. Guarded terminalization writes a success report once, or attempts a failure report and error record once. A persistence failure is reported in addition to the original failure and never replaces it. If a debug path exists, failure output includes that path so the retained diagnostic data is discoverable. ## Invariants To Preserve - Only the CLI composes production implementations and physical runtime roots. - Configuration and resolved composition failures occur before module preparation or source parsing. - A runner's logical files are published only after a successful run. - Production registries and a caller-supplied catalog or registries are alternative composition sources, not an implicit mixture. - A requested debug bundle has one terminal report attempt; its persistence errors supplement rather than obscure the primary command error. - User-facing flags, paths, exit codes, and configuration fields are defined by their public documentation, not duplicated here. ## Focused Tests - **internal/cli/command_contract_test.go** covers dispatch, help, syntax and runtime error classes, discovery, validation, and listing. - **internal/cli/run_contract_test.go** covers the run handoff, publication, debug reporting, and command-owned state collaborators. - **internal/cli/production_contract_test.go** covers registrar composition, production catalog contents, assets, and representative configuration validation. - **internal/cli/reference_contract_test.go** covers CLI reference overrides, origin separation, and materialization boundaries. - **internal/cli/state_hardening_test.go** covers safe run identity, state roots, and failure ordering. Run **go test ./internal/cli** after changing command composition or command behavior. Pair it with **go test ./internal/core/config** when the configuration handoff changes.