README: pin --version to this repo's commit in the ingest recipe

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EvxzSKi1zR395wvYXUenVE
This commit is contained in:
2026-08-22 16:53:10 +09:00
parent 119309debf
commit ccefd1751b
2 changed files with 74 additions and 1 deletions
+68
View File
@@ -0,0 +1,68 @@
# Failure handling
> Composed 2026-08-22. General practice, not tied to any one project or language. Input contract
> discipline is composed; failure-propagation discipline is still outline.
## Scope
General discipline for how code should behave when it encounters conditions it can't cleanly
complete against — malformed input, an unreachable dependency, an assumption that turns out false.
Two sections: what a function's contract should say about the input space before it's written, and
what happens once something has actually gone wrong.
Excluded on purpose: language-specific exception syntax, logging infrastructure choices,
testing-framework mechanics, and environment/verification parity ("tested" vs. "tested where it
actually runs") — that last one is close enough to earn a future handbook of its own rather than a
section here.
## Input contract discipline
**Degenerate input is the default case to design for, not an edge case bolted on after.** Empty
collections, duplicate entries, out-of-range values, malformed or wrong-typed data, oversized
payloads, and concurrently-modified reads are not rare — they're what actually arrives once code
leaves a demo. Design for that space first, and treat "obviously fine" input as the special case, not
the other way around.
**Validate, sanitize, and reject are three different responses — not one blurred behavior.**
- **Validate** — check conformance against the contract, making no change to the input. Proceed only
if it holds.
- **Sanitize** — transform nonconforming input into conforming input. The transformed value is what
proceeds, treated exactly as if the caller had sent it that way to begin with.
- **Reject** — refuse to proceed, surfacing why (see Failure-propagation discipline for what
"surfacing why" should actually look like).
**Sanitize only what's meaning-preserving; reject anything that requires guessing intent.**
Sanitizing has a real cost: the caller's actual input is never seen again, only its cleaned-up
substitute. That's fine when the correction is unambiguous — trimming incidental whitespace,
normalizing Unicode form, case-folding under an already-documented case-insensitive contract. It's
not fine when the correction requires guessing what the caller meant — clamping an out-of-range
number to its nearest valid bound, silently truncating an oversized value, defaulting a missing
required field. The test: *would the caller be surprised to learn what actually happened to their
input?* If yes, reject instead of sanitizing — finding out via an error immediately is strictly
better than finding out weeks later that a value was quietly substituted for the one actually sent.
Silent sanitization of an out-of-range value and a guard that returns success without checking are
the same failure shape: something couldn't proceed correctly, and produced a plausible-looking result
instead of saying so.
**Validate at trust boundaries; trust your own internals.** Check input where it crosses from outside
your control into it — user input, an external API response, a file read from disk, a message from
another service. Code on the inside of that boundary, already known to satisfy its own contract (by
type, by test, by the caller being code under the same control), doesn't need to re-validate what its
caller already guaranteed — that's redundant defense that doubles maintenance cost without doubling
safety. The boundary is where trust actually changes; that's the only place validation earns what it
costs.
**Before writing the implementation, write the contract's answer for each category.** A function's
contract should state, explicitly, what happens for: empty input, a duplicate where uniqueness was
assumed, a value outside the expected range, a type mismatch, an oversized input, and a stale or
concurrently-modified read. Leave any of those unanswered, and the implementation will invent an
answer under latitude — usually the same way, by quietly doing something plausible instead of
refusing. Writing the answer down first is what closes that gap; discovering it during implementation,
or worse in production, is what leaves it open.
## Failure-propagation discipline
*(Not yet composed.)*
*(Handbook in progress — sections above stand as citable units; the rest is outline.)*