0018: Pipeline Utilities — State Migration Chain Ambiguity Category and Fixture¶
- Status: Accepted
- Author: Chris Colinsky
- Created: 2026-05-15
- Accepted: 2026-05-15
- Targets: spec/pipeline-utilities/spec.md (modifies §10.10 to add error category; modifies §10.12.1 and §10.12.2 to reference the category by name); spec/pipeline-utilities/conformance/ (adds fixture 047)
- Related: 0014 (state-migration hooks)
- Supersedes:
Summary¶
Name the canonical error category for state-migration chain
ambiguity (currently mandated by §10.12.1 and §10.12.2 as "a
configuration-time error" without a specified identifier), and add
fixture 047 covering both the registration-time (duplicate
(from_version, to_version) pair) and resolution-time (multiple
distinct shortest paths) ambiguity cases.
Motivation¶
Proposal 0014 introduced state migrations and §10.12 mandates configuration-time errors for two distinct ambiguity cases:
- §10.12.1 — "Two migrations with the same
from_versionand sameto_versionMUST raise a configuration-time error (the chain is ambiguous)." - §10.12.2 — "When multiple distinct shortest paths exist (same
edge count, different edge sequences), this is an ambiguous chain
and the engine MUST raise a configuration-time error — the same
category §10.12.1 raises for duplicate
(from_version, to_version)pairs."
Both rules reference "the same category" but neither names it. Spec §10.10's runtime-category list does not include an ambiguity category. Implementations have nothing concrete to surface; harnesses have nothing concrete to assert.
Two consequences:
- Cross-implementation drift. A Python implementation might
surface this as
ValueError; a TypeScript implementation might surface it as a genericErroror a custom class. Each is spec-conformant under "configuration-time error" but they cannot share a conformance fixture. - Fixture coverage gap. Neither the §10.12.1 duplicate-pair rule
nor the §10.12.2 multi-shortest-path rule is exercised by an
existing fixture in
spec/pipeline-utilities/conformance/. Fixtures 039–046 cover every other §10.12 rule but skip both ambiguity paths.
This proposal resolves both gaps in a single small change: name the category, then add one fixture (with two cases) that exercises both ambiguity rules against the named category.
Detailed design¶
Pipeline-utilities §10.10: add the canonical category¶
Insert the following entry into §10.10's category list (alphabetic order places it adjacent to the two existing migration-related categories):
New canonical configuration-time category:
checkpoint_state_migration_chain_ambiguous— raised when the registered migration set contains an ambiguity that prevents the engine from picking a unique chain. Two ambiguity cases trigger this category:
- At registration (per §10.12.1). Two migrations registered with the same
from_versionAND the sameto_version. The engine MUST raise this category at registration time (or at compile time when migrations are bound to the compiled graph, per the host language's binding semantics) so the configuration error surfaces before any resume attempt.- At chain resolution (per §10.12.2). A request to resolve a chain from
from_versionA toto_versionB finds two or more distinct shortest paths (same edge count, different edge sequences). Implementations SHOULD detect this at compile time when feasible by scanning the registered migration graph; load-time detection is acceptable when compile-time analysis is not.Non-transient. The error MUST identify the offending
(from_version, to_version)pair (for the registration case) or the source / target version pair and a description of the conflicting paths (for the resolution case) in a form appropriate to the host language. The four migration-related categories —checkpoint_record_invalid,checkpoint_state_migration_missing,checkpoint_state_migration_failed, andcheckpoint_state_migration_chain_ambiguous— are mutually exclusive on any given resume: chain-ambiguous fires at build or load time before either migration runs or post-migration deserialization is attempted, so it cannot co-occur with migration-failed or record-invalid.
Update the §10.10 final paragraph (the migration-categories mutual-exclusion paragraph) to list the new category alongside the existing three migration-related categories.
Pipeline-utilities §10.12.1: name the category¶
Replace this sentence in §10.12.1:
Two migrations with the same
from_versionand sameto_versionMUST raise a configuration-time error (the chain is ambiguous).
With:
Two migrations with the same
from_versionand sameto_versionMUST raisecheckpoint_state_migration_chain_ambiguous(per §10.10) at registration or compile time, before any resume attempt.
Pipeline-utilities §10.12.2: name the category¶
Replace this clause in §10.12.2 step 2:
When multiple distinct shortest paths exist (same edge count, different edge sequences), this is an ambiguous chain and the engine MUST raise a configuration-time error — the same category §10.12.1 raises for duplicate
(from_version, to_version)pairs.
With:
When multiple distinct shortest paths exist (same edge count, different edge sequences), this is an ambiguous chain and the engine MUST raise
checkpoint_state_migration_chain_ambiguous(per §10.10).
The "Implementations SHOULD detect ambiguity at compile time when feasible" guidance immediately following remains unchanged.
New fixture: 047-state-migration-chain-ambiguous¶
Two cases under one fixture:
duplicate_pair_at_registration— register two migrations with the same(from_version, to_version)pair ((v1, v2, fn_a)and(v1, v2, fn_b)). The named category MUST surface with the newexpected_chain_ambiguity_errorprimitive (defined below).ambiguous_shortest_paths_at_resolution— register a diamond migration graph:v1→v2,v2→v4,v1→v3,v3→v4. State at v4, seeded record at v1. The named category MUST surface with the newexpected_chain_ambiguity_errorprimitive.
New harness primitive: expected_chain_ambiguity_error¶
This proposal also introduces a new conformance harness primitive,
expected_chain_ambiguity_error: <category>, that asserts the
named category surfaces at either build time or during resume.
This preserves §10.12.2's carve-out that compile-time detection is
SHOULD-not-MUST: an implementation that detects the ambiguity at
build/compile time satisfies the assertion via the build-step
exception; an implementation that defers detection to load time
satisfies the assertion via the resume-step exception. Both paths
are spec-conformant under §10.12.2; the harness accepts either.
Existing primitives expected_compile_error (graph-engine fixture
007) and expected_error (resume blocks, e.g. fixture 045) commit
the assertion to one timing or the other; neither is the right
shape for an error category whose timing is implementation-defined
per the spec.
The harness primitive's semantics are part of what acceptance ships, alongside the category name and the fixture YAMLs.
The companion fixture .md cross-references §10.12.1 and §10.12.2
and documents the OR-acceptance shape so reviewers can see how the
fixture preserves spec flexibility.
Conformance test impact¶
- New fixture
spec/pipeline-utilities/conformance/047-state-migration-chain-ambiguous.{yaml,md}with two cases. - New harness primitive
expected_chain_ambiguity_errorrecognized by the conformance harness, accepting the named category at either build or resume time. - Implementations that currently silently pick a path (or silently use registration order) when faced with multi-shortest-path ambiguity will fail the resolution case until they add detection at either build or load time.
- Implementations that currently silently overwrite on duplicate
(from, to)registration will fail the registration case until they add detection.
The reference implementation in openarmature-python is expected
to surface ambiguity at build time as part of its 0014
implementation; this fixture exercises that path. A future
implementation that defers detection to load time will pass the
same fixture via the alternate timing leg of
expected_chain_ambiguity_error.
Alternatives considered¶
- Leave the category implementation-defined. Each implementation
picks its own error class. Rejected: prevents cross-implementation
conformance and forces the fixture to assert against a per-language
shape (
ValueErrorvsErrorvs custom). The spec's "the same category §10.12.1 raises" wording already implies a shared identifier; this proposal makes that identifier concrete. - Two separate categories — one for registration-time ambiguity, one for resolution-time. Rejected: the root cause is the same (ambiguous chain), and §10.12.2 already says they share the category. Splitting would require a §10.12 rewrite and add an unnecessary distinction for callers.
- Fixture asserts via
expected_compile_erroronly. Rejected: spec §10.12.2 explicitly accepts load-time detection ("load-time detection is acceptable when compile-time analysis is not"). A compile-time-only fixture would make a spec-conformant load-time-detecting implementation fail conformance — fixture and spec disagreeing. The newexpected_chain_ambiguity_errorprimitive accepts either timing leg, preserving §10.12.2's carve-out. - Tighten spec to MUST compile-time detection. Rejected: removes the §10.12.2 carve-out for implementations whose binding semantics or graph-construction model doesn't support compile-time scanning. The carve-out exists for a reason; conformance coverage shouldn't strip it.
- Skip the fixture; trust implementations. Rejected: the gap is real (no current fixture exercises either §10.12.1 ambiguity or §10.12.2 ambiguity); without conformance coverage, drift is inevitable.
- Do nothing. Rejected: the unnamed category leaves implementations and harnesses without a shared identifier; the fixture coverage gap is unaddressed.
Open questions¶
None.