Add ADR 0009 defining the unit / integration / e2e tiers by the seam each exercises, with the value-returning vs process-side-effecting line as the boundary between in-process and e2e-only. Add one line to ADR 0008 noting the Result seam is what makes error propagation assertable in-process, cross-linking 0009.
32 lines
2.2 KiB
Markdown
32 lines
2.2 KiB
Markdown
# ADR 0008 — Errors as Values at the CLI Boundary
|
|
|
|
**Status**: Accepted
|
|
|
|
## Context
|
|
|
|
`@kitchen-md/bin`'s `view` command performs a fallible operation: reading a file from disk.
|
|
Node's `readFileSync` signals failure by throwing.
|
|
Two idioms are available: let exceptions propagate and catch them at the entry point, or represent failure as a value the type system tracks.
|
|
`@kitchen-md/core`'s parser already takes the second path — it is a total function that never throws and reports problems through the Document AST's diagnostics (see ADR 0004).
|
|
|
|
## Decision
|
|
|
|
Model fallible operations in `@kitchen-md/bin` as a neverthrow `Result<T, E>`, with error types expressed as plain-data tagged unions (e.g. `ViewError = { tag: "read-failed"; … }`).
|
|
A throwing API is wrapped in a small adapter that converts the exception into an `err`, so nothing above the adapter leaks exceptions.
|
|
The CLI entry point matches the `Result` at the boundary: stdout on success, stderr plus a non-zero exit on failure.
|
|
|
|
## Rationale
|
|
|
|
It keeps the bin layer consistent with core's total-function stance, so the whole codebase treats recoverable failure as data rather than control flow.
|
|
The possibility of failure becomes visible in a function's signature instead of hidden behind a `throw`.
|
|
The success value cannot be read without first handling the error case, which removes a class of mistakes at compile time.
|
|
A tagged-union error type gives exhaustive handling: a new failure mode is a new tag that every match must account for.
|
|
Because failure is a returned value rather than a side effect, error propagation can be asserted in-process by the integration tier, without spawning the binary (see ADR 0009).
|
|
|
|
## Consequences
|
|
|
|
`@kitchen-md/bin` takes a dependency on neverthrow.
|
|
`try`/`catch` is confined to the thin adapters that wrap throwing APIs, and the rest of the layer is exception-free.
|
|
Must-use enforcement — that a `Result` is never silently dropped — is a convention (always terminate a `Result` at a `.match` at the boundary), not a lint rule, because the project uses Biome alone and does not add ESLint's `eslint-plugin-neverthrow` for now.
|
|
New failure modes stay additive: a new tag on the error union, handled at the boundary.
|