specs/076-cli-run-params.md
Rama main · réplica de lectura del repositorio original.
# Spec 076: CLI Run Parameter Input
## Status
Implemented for local `dagu start` and `dagu enqueue`.
## Scope
This spec owns parameter-source selection and stdin parameter input before a
local run is started or enqueued.
It does not define parameter declaration schema, general runtime-parameter
grammar or type conversion, remote contexts, HTTP APIs, scheduler behavior,
queue processing, or history output formats.
[Spec 005: Value Resolution Params](005-value-resolution-params.md) owns
`${params}` and `${params.name}` after runtime input has been resolved.
## Goal
Callers can explicitly supply parameters through stdin without losing inherited
input intended for a shell loop or another command. Source precedence, value
boundaries, and input failures have predictable results.
## Behavior
### Source Selection
Both commands select one parameter source, in this order:
1. Arguments after an explicit `--` separator.
2. An explicitly supplied `--params` flag.
3. Piped or redirected stdin when `--params-stdin` is enabled.
4. Workflow defaults when no override is supplied.
Rules:
- A bare `--` selects an empty override. It suppresses `--params` and stdin.
- `--params=` selects an empty override. It suppresses stdin.
- An empty override retains workflow defaults; it does not supply an empty
string value for a parameter.
- `--params-stdin` defaults to false. Omitting it or setting it to false leaves
stdin unread, including input inherited inside a `while read` loop.
- A higher-precedence source leaves stdin uninspected and unread even when
`--params-stdin` is enabled. An open pipe cannot block that invocation, and
oversized or unreadable stdin cannot make it fail.
- Enabling `--params-stdin` with a terminal or other character device does not
read or prompt for input. With no higher-precedence source, defaults apply.
### Reading Stdin
- Selected piped or redirected input is read through EOF. Read boundaries do
not separate parameters or end the input; fragmented bytes, including UTF-8
and escape sequences, produce the same values as contiguous input.
- The maximum selected input size is 1,048,576 bytes, including leading and
trailing whitespace. Input at that limit is not rejected for size; larger
input must fail.
- The limit applies before whitespace is removed. Rejection of larger input
does not require waiting for EOF.
- Leading and trailing whitespace outside the parameter text is removed.
- Empty or whitespace-only input supplies no override and retains defaults.
### Supported Stdin Values
The following forms must be accepted for declared string parameters:
- A positional string can override the first declared parameter.
- Named `name=value` assignments supply values by parameter name.
- A JSON object supplies named values; a JSON array supplies positional values,
except that an object inside the array supplies its members as named values,
so `[{"value":1.10}]` sets `value` to `1.10`. A JSON string supplies its
text. Any other JSON value supplies its JSON text with each number as
written, so `98765432109876543` and `1.10` are not rounded.
- Double quotes delimit a single string containing whitespace. Whitespace
inside those quotes is preserved, including leading and trailing spaces.
- An equals sign inside a double-quoted positional value is literal text,
including text that resembles named assignments or contains escaped quotes.
- A quoted `""` is an explicit empty string and overrides a default.
- Quoted strings decode `\"`, `\\`, `\n`, and `\t` as a quote, backslash, newline,
and tab. `\\n` preserves a literal backslash followed by `n`.
- Quoted strings may contain literal newlines. JSON strings use JSON escaping.
- Literal newlines and escape sequences may appear in the same quoted value;
escape decoding must still preserve escaped quotes at either end.
- Unicode text is preserved. Parameter input does not evaluate shell variable
references or command substitutions contained in a supplied value.
These rules specify accepted input forms, not the behavior of malformed
parameter text or undeclared parameter names.
### Quoted Value Compatibility
Quoted parameter text in YAML defaults, explicit `--params`, and selected stdin
shares value decoding. Existing shell line continuations must retain their
backslashes and literal newlines without requiring `--params-stdin`.
A quoted value containing an odd run of backslashes immediately before a
literal newline follows legacy literal handling for the entire value. Those
backslash-newline sequences are preserved; backslash pairs and `\n` or `\t`
sequences elsewhere in the value remain literal. When no odd run is present,
even runs decode backslash pairs and preserve the newline under the supported
quoted-value rules above.
### Saved Runs
`start --from-run-id` restores saved parameters. Enabling `--params-stdin` with
`--from-run-id` must fail even if stdin is empty, is still open, or would be
ignored by a higher-precedence parameter source. Disabling `--params-stdin`
does not create this conflict.
## Errors and Side Effects
- If selected stdin cannot be inspected or read, the command must exit nonzero
and identify the stdin inspection or read failure on stderr.
- If selected stdin exceeds the byte limit, the command must exit nonzero and
identify the size limit on stderr.
- The saved-run flag conflict must exit nonzero and identify the incompatible
parameter input on stderr.
- These errors must occur before run admission: no new run or queue entry is
created, and no workflow step executes.
- Resolving valid input supplies the decoded values to the started or enqueued
run. This spec does not prescribe their history display serialization.
## Examples
Save this workflow as `params_stdin.yaml`:
```yaml
params:
- name: value
type: string
default: default
working_dir: .
steps:
- id: write
run: printf '%s\n' "$value" > params_stdin.out
```
```sh
printf '%s\n' '" hello world "' | dagu start --params-stdin params_stdin.yaml
printf '%s\n' '""' | dagu start --params-stdin params_stdin.yaml
printf '%s\n' '{"value":"line1\nline2"}' | dagu enqueue --params-stdin params_stdin.yaml
printf '%s\n' 'value=stdin' | dagu start --params-stdin --params= params_stdin.yaml
```
Expected values are ` hello world `, an empty string, `line1` and `line2` separated
by a newline, and `default`, respectively. The last command leaves stdin unread.
## Conformance
`conformance/spec076_cli_run_params/` owns binary-level coverage:
| Contract | Tests |
| --- | --- |
| Supported forms, quoting, escapes, Unicode, empty input and defaults | `TestParamsStdinValues` |
| Shell line continuations in existing YAML defaults and explicit flags | `TestParamsLineContinuation` |
| Source precedence and preservation of inherited bytes | `TestParamsStdinPrecedence` |
| Open, oversized and unreadable unselected input | `TestParamsStdinUnusedInput` |
| Inherited shell-loop input | `TestParamsStdinShellLoop` |
| Fragmented input through EOF | `TestParamsStdinFragments` |
| Read failure before admission | `TestParamsStdinReadError` |
| Byte limit before trimming and rejection without EOF | `TestParamsStdinLimit`, `TestParamsStdinLimitOpenPipe` |
| Character-device input | `TestParamsStdinCharacterDevice` |
| Saved-run conflict independent of available input | `TestParamsStdinFromRunID` |
Stdin inspection failure uses lower-level coverage in `TestRunClosedStdin`
under `internal/cmd/`: an invalid descriptor cannot normally be inherited by
the child binary.
The binary harness does not attach interactive terminals. Terminal exclusion
is represented by character-device selection in `TestStdinHasParamsInput` and
the null-character-device binary tests. These check selection and defaults;
they do not directly exercise an attached terminal.