21The channel error taxonomy — each failure speaks its true gRPC code
Recipe:register_channel · add_channel_columns · list_channels (the evidence-channel registry; chapter 14 exercises the happy path) · Theory: a typed error vocabulary on the wire — a failure with a known cause carries the gRPC status code that names it (The gRPC Authors 2024), so a caller can branch on the cause instead of pattern-matching a free-text Internal message · Rail: measurement (every (failure mode → status code) cell asserted against a frozen golden) + the cross-transport parity contract (the same failure raises the same normalized class on both transports).
The evidence-channel registry verbs are on both the embedded jammi.EmbeddedBackend and the remote jammi.RemoteDatabase. Chapter 14 measures the happy path — register, append columns, list, the tenant-isolation property. This chapter measures the failure surface: each way a channel operation can fail, and the typed gRPC status code it maps to on the wire.
The engine change §3.8 (#193) typed that taxonomy. Before it, a channel failure tended to collapse to a single catch-all Internal status — the caller saw “something went wrong” and had to scrape the message to learn what. After it, each failure speaks its true gRPC code: a duplicate registration is ALREADY_EXISTS, an operation on a channel that was never registered is NOT_FOUND, a column-redeclare conflict is FAILED_PRECONDITION, a malformed argument is INVALID_ARGUMENT. This chapter is the engine↔︎cookbook validator that measures that mapping.
21.1 What this chapter validates — and the honesty contract
Read the framing plainly:
The status codes only exist on the wire. A gRPC StatusCode is what the grpc:// transport puts on an RPC’s terminating status; the in-process embedded engine raises a native Python exception (a RuntimeError for a catalog error, a ValueError for a client-side argument guard) with no status code. So the taxonomy is measured on the grpc:// transport, where the codes live, with the embedded error class as the cross-transport companion.
The honest cross-transport observable is the normalized error CLASS. The two transports raise different Python exception types by construction. So for each failure mode the cookbook derives one normalized class from each native error (ALREADY_EXISTS / “already exists” → duplicate; NOT_FOUND / “not registered” → unknown; FAILED_PRECONDITION / “cannot redeclare” → conflict; INVALID_ARGUMENT / “is required” → bad_argument) and asserts the two transports agree on the class.
INTERNAL is the documented RESIDUAL, not a fabricated cell. The whole point of #193 is that a failure with a known cause no longer collapses to Internal. There is no honest, hermetic way to induce a genuine storage/DB fault from the public channel surface, so the cookbook does not manufacture one — INTERNAL stays the bucket for a true unexpected fault, recorded as the documented residual.
The cache is emitted once by scripts/build_channels_taxonomy_cache.py, which drives each failure mode on both the embedded engine and a live remote grpc://jammi-server, asserts each maps to its #193-intended wire code and that remote == embedded on the class, and freezes the matrix to artifacts/channels/. This chapter reads that cache on CPU and asserts every cell against the frozen golden — it never re-drives.
transports: embedded (file://, in-process — the error-class companion) | remote (grpc://, live jammi-server — the typed wire codes)
parity observable: the NORMALIZED channel-error CLASS (embedded RuntimeError/ValueError message / wire StatusCode → duplicate | unknown | conflict | bad_argument)
21.3 The taxonomy — each failure mode → its typed gRPC code
The matrix has four headline cells. Each is a real failure driven on the grpc:// transport; the wire_code is the gRPC StatusCode the client raised, and the embedded error_class is the same normalized class on the in-process engine.
modes =list(matrix["modes"])remote = matrix["remote"]embedded = matrix["embedded"]expected = matrix["expected_wire"]label = {"duplicate": "duplicate registration (channel id already exists)","unknown": "operation on a channel never registered","column_conflict": "redeclare a column with a different dtype","bad_argument": "an empty channel id",}print(f"{'failure mode':40s}{'gRPC status code':22s}{'embedded class':14s} match")print("-"*92)for mode in modes: wire = remote[mode]["wire_code"] cls = embedded[mode]["error_class"] match ="yes"if wire == expected[mode] elsef"NO (intended {expected[mode]})"print(f"{label[mode]:40s}{wire:22s}{cls:14s}{match}")
failure mode gRPC status code embedded class match
--------------------------------------------------------------------------------------------
duplicate registration (channel id already exists) ALREADY_EXISTS duplicate yes
operation on a channel never registered NOT_FOUND unknown yes
redeclare a column with a different dtype FAILED_PRECONDITION conflict yes
an empty channel id INVALID_ARGUMENT bad_argument yes
every failure mode maps to its #193-intended typed gRPC code
21.4 The #193 guarantee — not Internal-for-everything
The headline property is that none of these typed failures collapses to INTERNAL (or UNKNOWN) on the wire. Each speaks its true cause, so a caller can branch — retry an ALREADY_EXISTS as idempotent, surface a NOT_FOUND as a client mistake, treat a FAILED_PRECONDITION as a schema conflict to resolve — without scraping a free-text message.
for mode in modes:assert remote[mode]["wire_code"] notin (None, "INTERNAL", "UNKNOWN"), modecontracts.assert_close("channels.taxonomy.all_modes_typed_not_internal", 1.0)print("no failure mode collapses to INTERNAL / UNKNOWN — each speaks its true gRPC code")# INTERNAL is the documented residual, not a fabricated cell.assert"residual"in record["internal_residual"].lower()print("\nINTERNAL residual:", record["internal_residual"].split(":")[0])
no failure mode collapses to INTERNAL / UNKNOWN — each speaks its true gRPC code
INTERNAL residual: INTERNAL is the documented residual of the taxonomy
21.4.1 The client-side argument guard — validation that never hits the wire
One nuance the matrix records honestly: an invalid dtype string (a column typed "NotAType") is rejected client-side on both transports — a ValueError that never reaches the wire, so it carries no status code at all. The honest wire INVALID_ARGUMENT cell is therefore an empty channel id, which the server rejects. The dtype guard is a distinct, measured property: validation caught before the RPC is even sent.
guard = matrix["client_side_dtype_guard"]for arm in ("embedded", "remote"):assert guard[arm]["raised"] isTrueassert guard[arm]["wire_code"] isNone# never reaches the wireassert guard[arm]["native_type"] =="ValueError"assert guard[arm]["error_class"] =="bad_argument"contracts.assert_close("channels.taxonomy.client_dtype_guard_client_side", 1.0)print("invalid dtype string → a client-side ValueError on both transports ""(no StatusCode — caught before the RPC)")
invalid dtype string → a client-side ValueError on both transports (no StatusCode — caught before the RPC)
21.5 The cross-transport parity contract — remote == embedded class
The engine’s spine is that the channel verbs carry the same call surface and the same observable behaviour on both transports. For a failure, the observable is the normalized error class — because the two transports raise different Python exception types (embedded a RuntimeError/ValueError, the gRPC client a grpc.RpcError carrying a StatusCode). The emit drove every failure mode on both and asserted remote == embedded on the class live.
parity = record["parity"]n =len(parity)all_equal =all(p["class_equal"] for p in parity)print(f"remote == embedded class: {sum(p['class_equal'] for p in parity)}/{n} modes agree")for p in parity:print(f" {p['mode']:16s} embedded({p['embedded_native']}) == "f"remote({p['remote_native']}, {p['wire_code']}) → class {p['embedded_class']!r}")print("verdict:", record["parity_verdict"])assert all_equal and n ==4contracts.assert_close("channels.parity.all_modes_class_equal", 1.0)
remote == embedded class: 4/4 modes agree
duplicate embedded(RuntimeError) == remote(_InactiveRpcError, ALREADY_EXISTS) → class 'duplicate'
unknown embedded(RuntimeError) == remote(_InactiveRpcError, NOT_FOUND) → class 'unknown'
column_conflict embedded(RuntimeError) == remote(_InactiveRpcError, FAILED_PRECONDITION) → class 'conflict'
bad_argument embedded(ValueError) == remote(_InactiveRpcError, INVALID_ARGUMENT) → class 'bad_argument'
verdict: remote == embedded class for every channel failure mode
1.0
21.6 The measured taxonomy, at a glance
print("measured (mode → wire code), versus #193 intent:")for mode in modes:print(f" {mode:16s} → {record['taxonomy'][mode]:20s} "f"(intended {record['expected_taxonomy'][mode]})")assert record["taxonomy"] == record["expected_taxonomy"], "every mode must map as #193 intended"assert record["deviations"] == [], record["deviations"]print("\ndeviations from #193:", record["deviations"] or"none — every mode maps as intended")
measured (mode → wire code), versus #193 intent:
duplicate → ALREADY_EXISTS (intended ALREADY_EXISTS)
unknown → NOT_FOUND (intended NOT_FOUND)
column_conflict → FAILED_PRECONDITION (intended FAILED_PRECONDITION)
bad_argument → INVALID_ARGUMENT (intended INVALID_ARGUMENT)
deviations from #193: none — every mode maps as intended
The channel error taxonomy is a real, asserted property of the engine: each evidence-channel failure mode maps to the gRPC status code that names its cause (The gRPC Authors 2024) — ALREADY_EXISTS, NOT_FOUND, FAILED_PRECONDITION, INVALID_ARGUMENT — and the same normalized class on the embedded engine, with INTERNAL reserved as the documented residual for a genuine fault. Every cell holds identically across the embedded and remote transports — the cross-transport contract for a typed failure, measured.