ADR 0257 — CLI dotted values follow their declared schema
Invariants: I1, I2, I3, I8, I14.
Context
A dotted CLI argument arrives as text. The old fallback guessed from spelling when the receiving
schema was a free record<string, unknown>: 5 became a number and true became a boolean, while
007, +007 and 1e3 lost or changed lexical meaning. The same syntax under a declared object has
enough schema information to coerce safely, so treating both cases alike either guesses without a
contract or makes typed leaves inconvenient.
This behavior belongs to the stable stitchkit/cli entrypoint. Correcting it changes values seen by
handlers whose free records depended on the guess, so the change needs one named migration rather
than an undocumented parser heuristic.
Decision
- A dotted leaf under a declared number or boolean schema is coerced and then validated by that leaf. String leaves remain lexical. Literal sets, unions, wrappers and arrays use the same schema-derived rule rather than the option name or spelling alone.
- A dotted leaf under
record<string, unknown>preserves the exact argv text. Unknown means there is no contract authorizing conversion. - A whole-object JSON value retains JSON’s explicit primitive types. A caller that needs numbers or booleans in a free record passes the record as JSON, or declares a structured object schema for a dotted interface.
- A wholly unknown root admitted by
allowUnknownkeeps its existing best-effort coercion. That passthrough has no declared record boundary and is outside this correction; changing it would widen the migration without improving schema fidelity. - Invalid values for typed dotted leaves fail before the handler. Tests cover lexical lookalikes, valid typed leaves and invalid number/boolean controls through the real CLI argument builder.
Consequences
--data.count=5under a free record now yields{ count: "5" }; use--data='{"count":5}'for a numeric value.- A declared
z.object({ count: z.number() })continues to receive the number5from the dotted form. - The release is a pre-1.0 minor with an explicit breaking section and migration.