From 4ed06c7e4bcd543e9a45bffa26188cdb3bf902e9 Mon Sep 17 00:00:00 2001 From: Mikkeli Date: Sat, 22 Aug 2026 17:04:11 +0900 Subject: [PATCH 1/2] docs: compose the failure-handling handbook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two sections, forged the same way as git-operations.md: input contract discipline (degenerate input as the default case, validate/sanitize/reject as distinct responses, the meaning-preserving line for when sanitizing is safe) and failure-propagation discipline (never substitute a plausible artifact for a failed operation, retry/abort/surface, one signaling convention per boundary, the anti-pattern catalogue). Environment/verification parity was scoped out deliberately — different axis (confidence-establishment, not code behavior), earns its own handbook later rather than a section here. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_011EBCcjJvEvhpToahqdsLLA --- failure-handling.md | 66 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 62 insertions(+), 4 deletions(-) diff --git a/failure-handling.md b/failure-handling.md index 0078d00..4a1fbf6 100644 --- a/failure-handling.md +++ b/failure-handling.md @@ -1,7 +1,6 @@ # 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. +> Composed 2026-08-22. General practice, not tied to any one project or language. ## Scope @@ -63,6 +62,65 @@ or worse in production, is what leaves it open. ## Failure-propagation discipline -*(Not yet composed.)* +**Never substitute a plausible-looking artifact for a failed operation.** When something can't +complete its contract, the correct output is a signal that it didn't — not a value that resembles +success closely enough to pass unnoticed. A blank result where real output should be, a guard that +reports "fine" because it couldn't read what it was checking, a stale cached value returned as if it +were fresh: each of these is a failure wearing success's clothes, and the cost is always paid later, +by whoever trusted the output. -*(Handbook in progress — sections above stand as citable units; the rest is outline.)* +**A check that can't verify must refuse — and "couldn't verify" must be a different state than +"verified, and it's fine."** Collapsing both into one boolean is the mechanism behind the failure +above: a function that returns `true` for both "the input passed validation" and "validation itself +couldn't run" cannot be trusted by anything downstream, because the caller has no way to tell which +one happened. Whatever the check returns, its failure-to-run and its pass result need to be +distinguishable — a third state, a different return type, an exception path, anything that doesn't +require the caller to assume good faith on the checker's behalf. + +**Retry, abort, and surface are three different responses to a failure, and picking the wrong one is +a fail-loudly violation on its own.** + +- **Retry** only for failures that are plausibly transient and where repeating the operation doesn't + change its meaning (a network read, not a payment). Bound it — a fixed number of attempts or a + timeout, never unconditional — and don't let a bounded retry become an unbounded one by accident (a + retry nested inside another retry, a caller that itself retries a function that already retries + internally). +- **Abort** when the failure indicates a broken invariant or a state that's unsafe to continue from — + retrying or asking would just compound whatever's already wrong. +- **Surface** — to a caller, a human, or an error return — when the system doesn't have enough + information to pick correctly on its own. This is usually the right default when nothing above + clearly applies: asking is cheaper than guessing wrong. + +**A retry that eventually succeeds is still a failure that happened, and needs to be recorded as one, +not discarded because the second attempt worked.** The caller downstream can get the successful +result — that's the point of retrying — but "the first attempt failed" is real information: it's how +a dependency's slow slide toward actually breaking becomes visible before it breaks outright, instead +of only being visible in the moment it finally does. A retry policy that only speaks up once retries +are exhausted throws that signal away for free. + +**Pick one failure-signaling convention per boundary, and don't mix it with another.** Exceptions, a +`Result`-shaped return, a sentinel value, an error code — any of these work, but only if every caller +at that boundary can rely on which one is in play. Mixing them within the same boundary is its own +failure mode: a caller checking a sentinel value for a function that actually raises, or catching an +exception type a function never throws, proceeds past the real failure exactly as if it hadn't +checked at all — the check existed, and did nothing. + +**Anti-patterns, all variations on the same shape:** + +- A swallowed exception (`except: pass`, an empty `catch` block) — the failure existed for one + instruction and then didn't. +- `continue` on a caught exception inside a loop — one bad item disappears with no record it was ever + there, and the loop reports success for everything it actually attempted. +- A fallback default that isn't marked as one — returning `0`, `null`, an empty collection, or a + stale cached value in place of a failed real result, with nothing distinguishing it from a genuine + `0`, `null`, empty collection, or fresh value. +- A comment explaining away a shortcut instead of the code being fixed — `# TODO: handle this + properly` sitting next to the exact silent failure it describes, left in place. +- An unbounded retry — functionally identical to abort, except it costs resources first and never + actually informs anyone that it gave up. + +The same discipline shows up in test coverage: a suite that only exercises the happy path hasn't +tested the code, it's tested the narrative about the code. + +*(Handbook composed 2026-08-22. Both sections above stand as citable units — a chunk boundary, not +just a heading.)* From 57e03af2b90e1e6c41b5e88a1ad9a39f086ca04b Mon Sep 17 00:00:00 2001 From: Mikkeli Date: Sat, 22 Aug 2026 17:05:23 +0900 Subject: [PATCH 2/2] docs: add frontmatter convention (title, collections) to handbooks Each handbook now opens with a flat YAML frontmatter block so the two ingest-time values that actually describe the document - title and collections - live with it instead of being retyped by hand at admission time. That drift was real, not hypothetical: the ingest recipe documented `--collection dev-practice` while the actual admission used `handbooks`. `version` deliberately stays out of frontmatter - a file can't contain the hash of the commit that writes it - and remains a value the ingest recipe computes fresh, now scoped per-file (git log -1 -- ) rather than the whole repo's HEAD. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_011EBCcjJvEvhpToahqdsLLA --- README.md | 65 +++++++++++++++++++++++++++++++++++---------- failure-handling.md | 5 ++++ git-operations.md | 5 ++++ 3 files changed, 61 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index e477dcc..b5974ae 100644 --- a/README.md +++ b/README.md @@ -28,28 +28,65 @@ a retrieval store, not in a skill. One markdown file per topic at the repo root. Each file should stand as a single admittable document — the same shape a human vouches for at KB admission time. -## Ingesting a handbook +Each file opens with a small flat YAML frontmatter block — `title` and `collections` only, no nested +structure, so it stays greppable without a YAML dependency (same reasoning `index.py` itself gives +for hand-parsing `corpora.yaml`): -Markdown needs no conversion and no extraction verifier — admit it straight from this repo: - -```bash -index.py admit ~/dev/handbooks/git-operations.md --corpus engineering \ - --vouch mikkeli --title 'Git operations' --collection dev-practice \ - --version "$(git -C ~/dev/handbooks rev-parse --short HEAD)" +```yaml +--- +title: Git operations +collections: [handbooks, dev-practice] +--- ``` -Without `--title` the document is titled by its filename, which is what a retrieval hit then shows. -`--collection` is a flat cluster tag; getting it wrong costs recall and nothing else. +`title` and `collections` are the two ingest-time values that are actually about the document, so +they live with it instead of being retyped by hand at admission time — that's what let `--collection +dev-practice` and an actual admission of `handbooks` drift apart on 2026-08-22, silently, until it was +noticed by inspection rather than by anything catching it. -⚠ **`--version` is this repo's commit, and it is the one that matters.** Re-admitting replaces a -document outright with no version history, so the pin is the only record of which revision a chunk -reflects. +`version` is deliberately **not** a frontmatter field — a file can't contain the hash of the commit +that writes it, the same self-reference problem any `__COMMIT__` build stamp runs into. It stays a +value computed at admission time, same as before, just scoped to the file rather than the whole repo. -⚠ **Editing a file here does not update the corpus.** Re-admission is deliberate, because the vouch -is a claim about a specific version of a document. +## Ingesting a handbook + +Markdown needs no conversion and no extraction verifier — admit it straight from this repo, reading +`title`/`collections` from the frontmatter instead of retyping them: + +```bash +F=~/dev/handbooks/git-operations.md +TITLE=$(sed -n '/^---$/,/^---$/{/^title:/s/^title:[[:space:]]*//p}' "$F") +COLLECTIONS=$(sed -n '/^---$/,/^---$/{/^collections:/s/^collections:[[:space:]]*//p}' "$F" \ + | tr -d '[]' | tr ',' '\n' | sed 's/^ *//;s/ *$//') +VERSION=$(git -C ~/dev/handbooks log -1 --format=%h -- "$F") + +index.py admit "$F" --corpus engineering --vouch mikkeli \ + --title "$TITLE" --version "$VERSION" \ + $(for c in $COLLECTIONS; do printf -- '--collection %s ' "$c"; done) +``` + +⚠ **`--version` is the last commit that touched *this file*, not the repo's `HEAD`.** A whole-repo +`HEAD` pin would bump every handbook's recorded version on any unrelated edit, even one that never +touched this document — the per-file form is what actually answers "which revision does this chunk +reflect." + +⚠ **Re-admitting replaces a document outright, with no version history kept.** The `--version` pin is +the only record of which revision a chunk reflects, which is also why editing a file here does not by +itself update the corpus — re-admission is deliberate, a fresh claim about a specific version of a +document, not something that should happen automatically on every commit. ## Status Started 2026-08-21. First handbook, `git-operations.md`, composed 2026-08-21 – 2026-08-22: branching, committing, merging vs. rebasing, reverting and undoing, remotes and collaboration, tags and releases. Confirmed ingestible 2026-08-22 — 30 chunks, heading paths intact, retrieved correctly. + +Second handbook, `failure-handling.md`, composed 2026-08-22: input contract discipline, failure- +propagation discipline. Environment/verification parity was scoped out of it deliberately — a +different axis (how confidence is established, not how code behaves), queued as a handbook of its +own rather than folded in. + +Frontmatter (`title`, `collections`) added to both 2026-08-22, after `--collection dev-practice` in +this file's own recipe was found to have drifted from `handbooks`, the value actually used at +admission — the two ingest-time values that describe the document now live with it instead of being +retyped by hand. diff --git a/failure-handling.md b/failure-handling.md index 4a1fbf6..d99ce68 100644 --- a/failure-handling.md +++ b/failure-handling.md @@ -1,3 +1,8 @@ +--- +title: Failure handling +collections: [handbooks, dev-practice] +--- + # Failure handling > Composed 2026-08-22. General practice, not tied to any one project or language. diff --git a/git-operations.md b/git-operations.md index 8702894..e8fc182 100644 --- a/git-operations.md +++ b/git-operations.md @@ -1,3 +1,8 @@ +--- +title: Git operations +collections: [handbooks, dev-practice] +--- + # Git operations > Composed 2026-08-21 – 2026-08-22. General practice, not tied to any one project's Gitea/GitHub