0071: Conformance Adapter — Failure-Mock Directive Catalog¶
- Status: Accepted
- Author: Chris Colinsky
- Created: 2026-06-16
- Accepted: 2026-06-17
- Targets: spec/conformance-adapter/spec.md (§5.1 — formalize the failure-mock node-behavior directives the fixtures use but §5.1 never documented:
flaky(sequence + compact forms),flaky_by_index,flaky_per_index,flaky_instance_only,flaky_resume_aware, and thefailure_sequenceentry shape; document each by its failure-keying axis; sharpen theflaky_per_index(invocation-keyed) vsflaky_by_index(attempt-keyed) distinction so they aren't confused; flag — without changing — the inconsistent success-state field naming across variants) - Related: 0055 (conformance-adapter capability — this fills the §5.1 mock-vocabulary gap it left), 0070 (formalized the crash/resume directives + added the §5.1
causefield, and deferred this mock catalog here), 0008 / 0009 / 0010 (checkpointing — whatflaky_per_index/flaky_resume_awareexercise), 0050 / 0065 (retry + failure isolation — whatflaky/flaky_by_index/flaky_instance_onlyexercise) - Supersedes:
Summary¶
0055 was "descriptive of what exists," but §5.1 documents only the simple raises: node directive — not the failure-mock family the retry / failure-isolation / checkpoint-resume fixtures actually use. That family is five named node-behavior directives (used across ~40 fixtures): flaky, flaky_by_index, flaky_per_index, flaky_instance_only, flaky_resume_aware. 0070 added the cross-cutting cause field that attaches to any of them and deferred the family's catalog here.
This proposal documents the five directives in §5.1, each with its verified shape, grouped by the failure-keying axis it models. It is a descriptive catalog — these are the directives the adapter already implements and the fixtures already exercise; nothing changes in behavior. The one substantive clarification is distinguishing flaky_per_index from flaky_by_index (same _index suffix, different trigger), and it flags (without changing) the inconsistent success-state field naming across the family.
Why explicit named directives, not one parameterized flaky. A self-describing directive name (flaky_per_index) declares the failure model at the fixture site; a single flaky parameterized over every axis would bury that behavior in a config blob — less readable and less explicit, against the same principle the spec applies elsewhere. The five are not redundant: they model genuinely different failure-injection axes (below). One parameterized directive would also be a god-object whose axes (a single attempt vs. a whole-instance re-run vs. an invocation-id) don't compose into meaningful blends. So the catalog keeps them explicit and distinct.
Motivation¶
These mocks are the only way fixtures inject failure with the precision retry / isolation / resume tests need: a transient on attempt 1 that succeeds on attempt 2; a specific fan-out instance that fails the first run then succeeds on resume; a retry budget that resets across a checkpoint. They drive ~40 fixtures, yet a reader of §5.1 finds only raises:. An adapter author implementing the conformance suite has to reverse-engineer the mock vocabulary from the fixtures — exactly the gap 0055's "specify the harness primitives implementations MUST provide" was meant to close. Drafting 0070 surfaced this (and that the family is larger and more varied than a glance suggests — five variants, each with two sub-forms), which is why the catalog earned its own focused proposal.
Detailed design¶
Anticipated bump: MINOR (pre-1.0); concrete version assigned at acceptance. All changes are documentation added to conformance-adapter §5.1 — descriptive of the adapter behavior the existing fixtures already require; no new conformance expectation, no fixture changes.
Failure-injection patterns¶
The family models three failure patterns, several optionally selecting fan-out instances by index:
- Per-attempt — a later attempt within one invocation succeeds, exercising retry:
flaky's sequence form,flaky_by_index'sfail_count_per_idx,flaky_instance_only,flaky_resume_aware. - Per-invocation — the first run fails and a checkpoint resume succeeds, exercising checkpoint/resume:
flaky's compact form,flaky_per_index'sfail_first_run_indices,flaky_resume_aware. - Deterministic — the selected instance fails on every attempt and invocation, for collect-mode / error-contribution tests:
flaky_by_index'sfail_when_idx,flaky_per_index'salways_fail_indices.
flaky_by_index and flaky_per_index both select fan-out instances but serve different primary uses — by_index for retry (attempt-counted) or a deterministic collect-mode seam; per_index for resume (first-run vs. always). See the disambiguation note after the catalog.
§5.1 — failure-mock directives (formalize)¶
Document, alongside raises::
failure_sequenceentry — each entry is{transient: <bool>, category: <category|null>, message: <str|null>}; anullentry denotes a non-failing attempt at that position.transient: true+ acategoryraises a transient (retry-classifier-friendly) error;transient: falseraises a non-transient one.flaky— a node mock with two sub-forms:- Sequence form:
{failure_sequence: [<entry|null>, …], success_update: {<field>: <value>}}— raises once per entry across successive attempts; on an exhausted sequence (or anullentry) returnssuccess_update.- Compact form:
{fail_first_invocation_only: <bool>, on_success: {<field>: <value>}}— fails the first whole invocation only (raised asnode_exception), succeeding (returningon_success) on any resume.flaky_by_index— fan-out mock withsuccess_computeand an optionalcategory(defaults toprovider_unavailable; meaningful only for the retrying form, where it drives retry classification), in one of two forms:{fail_when_idx: <int>}— the instance whose item value equals<int>fails deterministically (no retry; a collect-mode seam,categorytypically omitted) — or{fail_count_per_idx: <int>}— every instance fails its first<int>attempts, then succeeds (retry).flaky_per_index— fan-out mock, invocation-keyed, withsuccess_compute, in one of two forms:{fail_first_run_indices: [<int>, …]}(those indices fail the first invocation only, then succeed on resume) or{always_fail_indices: [<int>, …]}(those indices fail every invocation — a deterministic failure, e.g. for collect-mode error-contribution resume).flaky_instance_only—{fail_count_per_instance: <int>, category: <category>, success_compute: {…}}— each fan-out instance fails its firstfail_count_per_instancewhole-instance invocations (the subgraph re-runs from scratch on retry), then succeeds.flaky_resume_aware—{fail_first_invocation_count: <int>, fail_resumed_invocation_count: <int>, category: <category>, on_success: {…}}— fails N attempts on the first invocation, then M attempts on any resumed invocation before succeeding; used to verifyattempt_indexresets on resume.Any failure these mocks raise MAY carry a
cause(§5.1, proposal 0070) to chain an originating cause.
In any of these success-state mappings (success_update / on_success / success_compute), a
<value> that is a string naming a declared state field is read from that field; any other value
is taken as a literal.
flaky_per_index vs flaky_by_index (disambiguation)¶
Both select fan-out instances by index, but for different purposes — the shared _index suffix
invites confusion:
flaky_by_indexhas no checkpoint/resume semantics:fail_count_per_idxfails the first N attempts of each instance (retry);fail_when_idxfails the instance with that item value deterministically (a collect-mode seam). Use it for fan-out + retry / collect-mode fixtures.flaky_per_indexis invocation-keyed (checkpoint/resume):fail_first_run_indicesfail the first invocation then succeed on resume;always_fail_indicesfail every invocation. Use it for fan-out + checkpoint fixtures.
Success-state field naming (flagged, not changed)¶
The family names the success-path state update three ways: success_update (flaky sequence
form), on_success (flaky compact form, flaky_resume_aware), and success_compute
(flaky_by_index, flaky_per_index, flaky_instance_only). This is organic drift, not a semantic
distinction — all three are "the partial update the mock returns on the success path." This proposal
documents each as-is (renaming would churn ~40 accepted fixtures + the adapter for no behavioral
gain); unifying the name is noted as a candidate future cleanup (Out of scope).
Conformance test impact¶
None. This is a descriptive catalog of directives the fixtures already use and the adapter already implements. No new or changed fixtures; the existing ~40 retry / failure-isolation / checkpoint-resume fixtures are the coverage.
Versioning¶
MINOR bump (pre-1.0): §5.1 gains the failure-mock directive documentation. Descriptive of existing adapter behavior — no behavior change, no new conformance expectation, no fixture changes. A PATCH classification is defensible (purely documentary); the bump is the maintainer's call at acceptance.
Out of scope¶
- Rationalizing the family into one parameterized
flaky. Rejected (see Summary): explicit named directives are more readable at the fixture site and model genuinely distinct axes; one parameterized directive trades self-description for a config blob and risks a god-object. - Unifying the success-state field name (
success_update/on_success/success_compute→ one name). A real inconsistency, but fixing it churns ~40 accepted fixtures + the adapter for no behavioral gain; left as a candidate future cleanup. This proposal documents the names as they are. crash_injection, mockcause, and the crash/resume directives. Those are proposal 0070; this proposal only catalogs the failure mockscauseattaches to.- Capability behavior changes. Nothing here changes graph-engine / pipeline-utilities / etc.; it documents the test vocabulary that exercises already-accepted behavior.
Alternatives considered¶
- One parameterized
flaky(the rationalization). Rejected on design grounds, not just churn: a single directive parameterized over attempt / index / invocation axes buries the failure model in configuration where a name would declare it, and the axes don't compose into meaningful blends. The explicit catalog is the more spec-idiomatic choice. - Rename the
_indexpair (flaky_by_index/flaky_per_index) to axis-explicit names (e.g.flaky_attempt_by_index/flaky_invocation_by_index). Tempting for clarity, but a rename churns the accepted fixtures + adapter; a sharp documented distinction achieves the clarity without the churn. Left as a candidate cleanup alongside the success-field unification. - Leave §5.1 mock-silent (status quo — mocks documented only inline per fixture). Rejected: it forces every adapter author to reverse-engineer the vocabulary from ~40 fixtures, the exact gap 0055 set out to close.