azazel_processor: commit 2 — vidui (property 3, enumerative commit)

Pass 4 / Finding F, commit 2 of 10 from BUILD_AZAZEL.md.

Vayikra 16:21 — v'hitvadah alav et kol avonot b'nei Yisrael v'et kol
pish'eihem l'chol chatotam. The high priest confesses ALL iniquities,
ALL transgressions, for ALL sins. The verb is hitpa'el (reflexive) —
the confession is self-addressed before it is addressed to the
system. The kol-three-times structure is mandatory.

ENGINEERING READ:

The commit message for a disposal must enumerate what is being
disposed of. Untagged disposal is not disposal; it is loss. Azazel
does not accept uncommitted writes. The enumeration is produced at
the moment of commit, not retrieved from a pre-existing log.

WHY OPERATOR-PRODUCED, NOT AUTO-DERIVED:

The Mishkan high priest speaks the confession aloud. The system did
not generate it from a classification enum. In software: the caller
of confess() MUST supply the descriptions, not have them
auto-generated from a classification → string table. If the table
generated the confession, the operator could dispose state without
ever naming it themselves; the confession would become an
autogenerated label, not a self-addressed enumeration. That is the
failure mode property 3 defends against.

The convenience helpers (confess_empty_vital, confess_halt,
confess_boundary_violation, confess_malformed_input) DO NOT hide
this requirement. Each requires the operator to supply the specific
item; the helper only routes which category it goes into.

THE THREE CATEGORIES:

  avonot   — iniquities. CATEGORICAL errors: halt-trips
             (avodah_zarah, gilui_arayot, shfichut_damim — the
             cardinal sins). Sins of orientation.
  peshaim  — transgressions. BOUNDARY violations: distinction
             collapses (gilui-shaped), L3 schema rejections.
             Sins of boundary-keeping.
  chatot   — sins. TARGET errors: empty-vital, malformed responses.
             The literal meaning of chet — missing the mark.

Most disposals fill exactly one category. The Vidui dataclass
REQUIRES all three fields (the enumeration is comprehensive at the
shape level), but the lists may be empty. What is forbidden is the
ABSENCE of a slot — and the case where ALL THREE slots are empty
(EmptyConfessionError, no untagged disposal).

CREATED:

  src/azazel_processor/vidui.py            ~250 lines
    Vidui                — frozen dataclass: avonot, peshaim, chatot,
                           confessor, confessed_at (all required;
                           categories are tuples for deep immutability;
                           __post_init__ rejects all-empty, empty-string
                           items, non-string items, anonymous confessor)
    EmptyConfessionError — raised on untagged-disposal attempts
    confess(*, avonot, peshaim, chatot, confessor)
                         — the operator-produced builder
    Vidui.to_text()      — kol-three-times rendering for L3 storage
    Vidui.category_summary()
                         — counts only, no items (for the public
                           observability endpoint, commit 8 / property 9)

  Convenience helpers (each requires operator-supplied item):
    confess_empty_vital(korban_summary, *, confessor)
    confess_halt(condition, detail, *, confessor)
    confess_boundary_violation(violation, *, confessor)
    confess_malformed_input(description, *, confessor)

  test/test_vidui.py                       27 tests covering:
    - structural (exactly five fields, no silent additions)
    - all category-fill combinations succeed
    - all-empty / empty-string / whitespace / non-string items raise
    - confessor required (TypeError) + non-empty/non-whitespace
      validated (EmptyConfessionError)
    - frozen dataclass (cannot mutate after construction)
    - categories are tuples (deep immutability — frozen=True does
      not propagate through container types)
    - confessed_at: timezone-aware UTC, set to current time
    - to_text() preserves kol-three-times structure; omits empty
      categories
    - category_summary() returns counts WITHOUT items
    - all four convenience helpers route correctly + still require
      confessor

Public surface updated in __init__.py.

Tests: azazel_processor 48 passed (was 21, +27). korban_processor
unchanged at 203 passed, 1 skipped. No regressions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Scott KarpandClaude Opus 4.7 committed 2026-04-27 18:49:34 -04:00
1 parent d06ecd244d
commit a2744c012a
3 files changed
+584 -13

No files matched your search

@@ -6,23 +6,24 @@ uv'd'mim, Yoma 62a). One goat to HaShem (processing); one goat to
Azazel (disposal). The two channels are provisioned at parity, from Azazel (disposal). The two channels are provisioned at parity, from
the same pool, with assignment blind to the operator. the same pool, with assignment blind to the operator.
Build state: commit 1 — Skeleton + stages. See BUILD_AZAZEL.md for Build state: commit 2 — vidui added. See BUILD_AZAZEL.md for the full
the full 10-commit staging plan and the 12-property → code mapping 10-commit staging plan and the 12-property → code mapping from
from azazel_engineering_reading.md §14. azazel_engineering_reading.md §14.
This commit covers: Properties defended so far:
- Property 11 (classification discipline) — AzazelStage and - Property 3 (enumerative commit) — Vidui + confess()
AzazelStaged are non-interconvertible with korban_processor. - Property 11 (classification discipline) — AzazelStage*
Staged. - Property 12 (unmanaged destination) — ERETZ_GEZERAH
- Property 12 (unmanaged destination) — ERETZ_GEZERAH sentinel
is the only legitimate way to refer to the disposal destination.
Public surface so far: Public surface:
AzazelStage, AzazelStaged, AzazelStageError — stage-gating types AzazelStage, AzazelStaged, AzazelStageError — stage gating
ERETZ_GEZERAH, is_eretz_gezerah — the destination ERETZ_GEZERAH, is_eretz_gezerah — the destination
Vidui, confess, EmptyConfessionError — enumerative commit
confess_empty_vital, confess_halt,
confess_boundary_violation,
confess_malformed_input — categorization helpers
Pending in subsequent commits: Pending in subsequent commits:
vidui (commit 2 — enumerative commit)
shaliach + tevilah (commit 3 — role-separated operator) shaliach + tevilah (commit 3 — role-separated operator)
goral (commit 4 — blind routing) goral (commit 4 — blind routing)
azazel orchestrator (commit 5 — full state machine) azazel orchestrator (commit 5 — full state machine)
@@ -31,6 +32,15 @@ Pending in subsequent commits:
from azazel_processor.eretz_gezerah import ERETZ_GEZERAH, is_eretz_gezerah from azazel_processor.eretz_gezerah import ERETZ_GEZERAH, is_eretz_gezerah
from azazel_processor.stages import AzazelStage, AzazelStaged, AzazelStageError from azazel_processor.stages import AzazelStage, AzazelStaged, AzazelStageError
from azazel_processor.vidui import (
EmptyConfessionError,
Vidui,
confess,
confess_boundary_violation,
confess_empty_vital,
confess_halt,
confess_malformed_input,
)
__all__ = [ __all__ = [
"ERETZ_GEZERAH", "ERETZ_GEZERAH",
@@ -38,4 +48,11 @@ __all__ = [
"AzazelStage", "AzazelStage",
"AzazelStaged", "AzazelStaged",
"AzazelStageError", "AzazelStageError",
"Vidui",
"EmptyConfessionError",
"confess",
"confess_empty_vital",
"confess_halt",
"confess_boundary_violation",
"confess_malformed_input",
] ]
@@ -0,0 +1,271 @@
"""
Vidui — the enumerative commit (property 3 of azazel_engineering_reading.md §14).
Vayikra 16:21 — וְהִתְוַדָּה עָלָיו אֶת־כָּל־עֲוֺנֹת בְּנֵי יִשְׂרָאֵל
וְאֶת־כָּל־פִּשְׁעֵיהֶם לְכָל־חַטֹּאתָם.
And he shall confess upon it ALL the iniquities (avonot) of the children
of Israel, and ALL their transgressions (peshaim), for ALL their sins
(chatot).
The verb v'hitvadah is hitpa'el — REFLEXIVE. The confession is
self-addressed before it is addressed to the system. The high priest
must STATE the errors to himself in the act of transferring them. You
discover what you are disposing of in the act of disposing of it.
The word kol appears three times: kol avonot, kol pish'eihem, l'chol
chatotam. The enumeration is comprehensive by specification. All three
categories. All items in each category named.
ENGINEERING READ:
The commit message for a disposal must enumerate what is being disposed
of. Untagged disposal is not disposal; it is loss. Azazel does not
accept uncommitted writes. The enumeration is produced at the moment
of commit, not retrieved from a pre-existing log.
WHY THE VIDUI IS OPERATOR-PRODUCED, NOT AUTO-DERIVED:
The Mishkan high priest speaks the confession aloud. The system did
not generate it for him from a classification enum. In software terms:
the caller of confess() MUST supply the descriptions, not have them
auto-generated. If a classification → string table generated the
confession, the operator could dispose state without ever naming it
themselves; the confession would be an autogenerated label, not a
self-addressed enumeration. That's the failure mode property 3
defends against.
The convenience helpers (confess_empty_vital, confess_halt, etc.)
DO NOT hide this requirement. Each helper still requires the operator
to supply the specific item (the korban summary, the halt detail,
the boundary description). The helper just routes which category it
goes into; the operator still names the item.
THE THREE CATEGORIES:
avonot — iniquities. CATEGORICAL errors: halt-trips
(avodah_zarah, gilui_arayot, shfichut_damim — the
cardinal sins of the system that triggered yehareg
v'al ya'avor). Sins of orientation.
peshaim — transgressions. BOUNDARY violations: distinction
collapses (gilui-shaped — read/write conflation,
ketiv/qere collapse, specification/interpretation
merge), L3 schema rejections (the trace was not
L1-grounded). Sins of boundary-keeping.
chatot — sins. TARGET errors: things offered but unprocessable
(empty-vital korban, malformed external responses,
content that arrived but had no extractable Torah).
Sins of target-missing (the literal meaning of chet —
missing the mark).
Most disposals fill exactly ONE category. The Vidui dataclass
REQUIRES all three fields, but the lists may be empty. What is
forbidden is the absence of the slot, not the absence of items in
the slot. An untagged disposal — confess() called with all three
slots empty — raises EmptyConfessionError; you cannot dispose
without naming.
"""
from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Iterable
class EmptyConfessionError(ValueError):
"""Raised when confess() is called with all three slots empty.
An untagged disposal — the failure mode property 3 defends
against. There must be SOMETHING in at least one of avonot,
peshaim, or chatot. Empty in all three would mean disposal
without naming, which is loss disguised as disposal.
"""
@dataclass(frozen=True)
class Vidui:
"""The structured enumerative commit.
All four fields are required. avonot/peshaim/chatot are tuples
(immutable) so the dataclass-frozen guarantee extends through
the contents — no path to mutating the confession after the
moment of confession.
confessed_at and confessor are non-optional: anonymous or
untimed confession would defeat the operator-attribution
requirement (property 7's `authorized_by` field on the
AzazelRecord traces back to confessor).
"""
avonot: tuple[str, ...]
peshaim: tuple[str, ...]
chatot: tuple[str, ...]
confessor: str
confessed_at: datetime = field(
default_factory=lambda: datetime.now(timezone.utc)
)
def __post_init__(self):
# All three slots present (the dataclass enforces this) AND
# at least one is non-empty (property 3: no untagged
# disposal). The dataclass cannot enforce the "at least one
# non-empty" constraint at the type level; we enforce it
# here so misuse fires at construction, not at L3 write.
if not (self.avonot or self.peshaim or self.chatot):
raise EmptyConfessionError(
"Cannot confess with all three categories empty. "
"Property 3 (enumerative commit) requires that "
"SOMETHING be named — disposal without naming is "
"loss, not disposal. Vayikra 16:21 says kol three "
"times; at minimum, one of the kol's must be filled."
)
# All entries non-empty strings — empty descriptions would
# silently weaken the enumeration without raising.
for category, items in (
("avonot", self.avonot),
("peshaim", self.peshaim),
("chatot", self.chatot),
):
if any((not isinstance(s, str)) or (not s.strip()) for s in items):
raise EmptyConfessionError(
f"Confession {category} contains an empty or "
f"non-string entry: {items!r}. Each item must be a "
f"named description; empty strings would degrade the "
f"enumeration to a count."
)
if not isinstance(self.confessor, str) or not self.confessor.strip():
raise EmptyConfessionError(
"Vidui requires a named confessor (operator/kohen "
"identifier). Anonymous confession defeats property 7 "
"(authorized routing) — the routing decision must be "
"ownable back to the confessor."
)
def to_text(self) -> str:
"""Render the vidui as readable confession text — the
Vayikra 16:21 structure preserved.
Each non-empty category is rendered as a kol-prefixed clause.
The output is what gets persisted in L3 azazel_records.vidui
and what the public observability endpoint summarizes (in
categorical form only — the public endpoint shows which
categories were filled, not the items themselves).
"""
parts = []
if self.avonot:
parts.append("kol avonot: " + "; ".join(self.avonot))
if self.peshaim:
parts.append("kol pish'eihem: " + "; ".join(self.peshaim))
if self.chatot:
parts.append("l'kol chatotam: " + "; ".join(self.chatot))
return " | ".join(parts)
def category_summary(self) -> dict[str, int]:
"""Return a {category → item count} map. The shape exposed by
the public observability endpoint (commit 8) — counts only,
no item content. Property 9 (observable disposal) at the
public boundary."""
return {
"avonot": len(self.avonot),
"peshaim": len(self.peshaim),
"chatot": len(self.chatot),
}
def confess(
*,
avonot: Iterable[str] = (),
peshaim: Iterable[str] = (),
chatot: Iterable[str] = (),
confessor: str,
) -> Vidui:
"""Produce a vidui — the operator-produced enumerative commit.
The caller MUST supply the items in each category they are
confessing. There is no auto-derivation from a classification
table; the act of naming is the operator's, not the system's.
All three category arguments default to empty tuples for
convenience (most disposals fill exactly one), but at least
one must be non-empty — see EmptyConfessionError.
confessor is required keyword-only — there is no default.
Anonymous confession is not a confession; the kohen-gadol
identity is what makes the routing decision authorizable
(property 7).
"""
return Vidui(
avonot=tuple(avonot),
peshaim=tuple(peshaim),
chatot=tuple(chatot),
confessor=confessor,
)
# ============================================================
# Convenience helpers — categorize at the call site
# ============================================================
#
# Each helper still REQUIRES the operator to supply the specific
# item being confessed. The helper only routes which category the
# item goes into; it does not auto-generate the description.
# Property 3 stays defended.
def confess_empty_vital(
korban_summary: str, *, confessor: str
) -> Vidui:
"""Confess a chatot-class disposal: an offering whose zerikah
returned no vital content. The operator must supply a summary
naming what was offered; the helper routes it into chatot
(target-missing). The literal meaning of chet — missing the mark
— is exactly the empty-vital case: the system fired and
nothing landed."""
return confess(
chatot=(f"empty-vital offering: {korban_summary}",),
confessor=confessor,
)
def confess_halt(
condition: str, detail: str, *, confessor: str
) -> Vidui:
"""Confess an avonot-class disposal: content that triggered a
halt rule (avodah_zarah, gilui_arayot, shfichut_damim — the
yehareg-v'al-ya'avor sins).
The condition is the halt name; the detail is the operator's
description of what specifically tripped the halt. The vidui
preserves both so the L3 record names the orientation-failure
that occurred."""
return confess(
avonot=(f"halt {condition}: {detail}",),
confessor=confessor,
)
def confess_boundary_violation(
violation: str, *, confessor: str
) -> Vidui:
"""Confess a peshaim-class disposal: content that collapsed a
distinction the system requires kept (read/write, ketiv/qere,
specification/interpretation, etc.). The operator names the
specific collapse."""
return confess(
peshaim=(f"boundary collapse: {violation}",),
confessor=confessor,
)
def confess_malformed_input(
description: str, *, confessor: str
) -> Vidui:
"""Confess a chatot-class disposal: an input that arrived at the
intake but could not be parsed or processed at all (malformed
JSON, truncated response, encoding failure). Different from
empty-vital — empty-vital is content with no Torah; malformed is
content with no SHAPE."""
return confess(
chatot=(f"malformed input: {description}",),
confessor=confessor,
)
+283
View File
@@ -0,0 +1,283 @@
"""
Tests for vidui — property 3 (enumerative commit) of
azazel_engineering_reading.md §14.
The vidui must:
- Have all THREE category slots present (avonot/peshaim/chatot),
even when individual slots are empty
- Refuse construction when ALL slots are empty (no untagged
disposal — the failure mode property 3 defends against)
- Be immutable after construction (the moment of confession
is the moment of fixing)
- Carry an attributable confessor (anonymous confession defeats
property 7's authorized-routing requirement)
- Render to readable text preserving the kol-three-times structure
- Expose category counts WITHOUT the items themselves (for the
public observability endpoint, commit 8 — property 9)
"""
from datetime import datetime, timedelta, timezone
import pytest
from azazel_processor.vidui import (
EmptyConfessionError,
Vidui,
confess,
confess_boundary_violation,
confess_empty_vital,
confess_halt,
confess_malformed_input,
)
# ============================================================
# The three-fold structure
# ============================================================
def test_vidui_has_exactly_the_five_expected_fields():
"""Structural test: the dataclass has exactly avonot, peshaim,
chatot, confessor, confessed_at — no silent additions and no
missing fields. A future contributor who adds a sixth field
(or removes one) breaks this and forces an explicit decision."""
fields = {f.name for f in Vidui.__dataclass_fields__.values()}
assert fields == {"avonot", "peshaim", "chatot", "confessor", "confessed_at"}
def test_confess_with_avonot_only_succeeds():
v = confess(avonot=("halt avodah_zarah",), confessor="kohen_gadol")
assert v.avonot == ("halt avodah_zarah",)
assert v.peshaim == ()
assert v.chatot == ()
def test_confess_with_peshaim_only_succeeds():
v = confess(peshaim=("ketiv/qere collapse",), confessor="kohen_gadol")
assert v.peshaim == ("ketiv/qere collapse",)
def test_confess_with_chatot_only_succeeds():
v = confess(chatot=("empty-vital korban",), confessor="kohen_gadol")
assert v.chatot == ("empty-vital korban",)
def test_confess_with_all_three_categories_filled_succeeds():
v = confess(
avonot=("halt 1",),
peshaim=("collapse 1",),
chatot=("missing 1", "missing 2"),
confessor="kohen_gadol",
)
assert v.avonot == ("halt 1",)
assert v.peshaim == ("collapse 1",)
assert v.chatot == ("missing 1", "missing 2")
# ============================================================
# Untagged disposal is forbidden
# ============================================================
def test_confess_with_all_slots_empty_raises():
"""Property 3: no untagged disposal. The act of naming what is
being disposed is load-bearing. confess() with all three slots
empty is the system trying to dispose without naming — refuse."""
with pytest.raises(EmptyConfessionError) as exc:
confess(confessor="kohen_gadol")
assert "without naming" in str(exc.value).lower() or "loss" in str(exc.value).lower()
def test_confess_with_explicit_empty_tuples_in_all_slots_raises():
with pytest.raises(EmptyConfessionError):
confess(avonot=(), peshaim=(), chatot=(), confessor="kohen_gadol")
def test_vidui_construction_with_empty_string_item_raises():
"""An empty string in a category list would degrade the enumeration
to a count — refuse at construction."""
with pytest.raises(EmptyConfessionError):
confess(avonot=("",), confessor="kohen_gadol")
def test_vidui_construction_with_whitespace_only_item_raises():
with pytest.raises(EmptyConfessionError):
confess(chatot=(" ",), confessor="kohen_gadol")
def test_vidui_construction_with_non_string_item_raises():
with pytest.raises(EmptyConfessionError):
confess(avonot=(None,), confessor="kohen_gadol") # type: ignore[arg-type]
# ============================================================
# Confessor attribution
# ============================================================
def test_confessor_required():
"""Anonymous confession defeats property 7 (authorized routing).
confess() must take a confessor (keyword-only) — no default."""
with pytest.raises(TypeError):
# No confessor argument
confess(avonot=("x",)) # type: ignore[call-arg]
def test_confessor_must_be_non_empty_string():
with pytest.raises(EmptyConfessionError):
confess(avonot=("x",), confessor="")
def test_confessor_must_be_non_whitespace():
with pytest.raises(EmptyConfessionError):
confess(avonot=("x",), confessor=" ")
def test_confessor_recorded_on_vidui():
v = confess(avonot=("x",), confessor="aharon_ha_kohen")
assert v.confessor == "aharon_ha_kohen"
# ============================================================
# Immutability
# ============================================================
def test_vidui_is_frozen():
"""The moment of confession is the moment of fixing. No path to
mutating the vidui after construction — that would let an
operator re-write what they confessed."""
v = confess(avonot=("x",), confessor="kohen_gadol")
with pytest.raises((AttributeError, Exception)):
v.confessor = "someone_else" # type: ignore[misc]
def test_vidui_categories_are_tuples_not_lists():
"""Tuples are immutable; lists are not. Even though the dataclass
is frozen, a list-valued field could be mutated in place
(frozen=True doesn't propagate through container types). Tuples
are the right shape for the enumeration."""
v = confess(avonot=("x",), peshaim=("y",), chatot=("z",), confessor="x")
assert isinstance(v.avonot, tuple)
assert isinstance(v.peshaim, tuple)
assert isinstance(v.chatot, tuple)
def test_iterable_input_normalized_to_tuple():
"""confess() should accept any iterable for category lists and
normalize to tuple at construction. A list passed in cannot be
held as a list-valued field — that would re-introduce the
mutability gap."""
v = confess(avonot=["x", "y"], confessor="kohen_gadol")
assert isinstance(v.avonot, tuple)
assert v.avonot == ("x", "y")
# ============================================================
# Timestamp
# ============================================================
def test_confessed_at_set_to_current_utc_time():
before = datetime.now(timezone.utc)
v = confess(avonot=("x",), confessor="kohen_gadol")
after = datetime.now(timezone.utc)
assert before - timedelta(seconds=1) <= v.confessed_at <= after + timedelta(seconds=1)
def test_confessed_at_is_timezone_aware_utc():
v = confess(avonot=("x",), confessor="kohen_gadol")
assert v.confessed_at.tzinfo is not None
assert v.confessed_at.utcoffset() == timedelta(0)
# ============================================================
# Rendering
# ============================================================
def test_to_text_preserves_kol_structure_for_all_three():
v = confess(
avonot=("a1", "a2"),
peshaim=("p1",),
chatot=("c1",),
confessor="kohen",
)
text = v.to_text()
assert "kol avonot" in text
assert "kol pish'eihem" in text
assert "l'kol chatotam" in text
assert "a1" in text and "a2" in text
assert "p1" in text
assert "c1" in text
def test_to_text_omits_empty_categories():
v = confess(avonot=("only-avonot",), confessor="kohen")
text = v.to_text()
assert "kol avonot" in text
assert "kol pish'eihem" not in text
assert "l'kol chatotam" not in text
def test_category_summary_returns_counts_without_items():
"""The shape exposed by the public observability endpoint —
counts only, no item content. Property 9 at the public boundary."""
v = confess(
avonot=("a1", "a2"),
peshaim=("p1",),
chatot=(),
confessor="kohen",
)
summary = v.category_summary()
assert summary == {"avonot": 2, "peshaim": 1, "chatot": 0}
# Items themselves are NOT in the summary — only counts.
assert "a1" not in str(summary)
assert "p1" not in str(summary)
# ============================================================
# Convenience helpers
# ============================================================
def test_confess_empty_vital_routes_to_chatot():
"""The literal meaning of chet — missing the mark — is exactly
the empty-vital case: the system fired and nothing landed."""
v = confess_empty_vital("Claude response on bereishit 1:1", confessor="x")
assert v.chatot
assert v.avonot == ()
assert v.peshaim == ()
assert "empty-vital" in v.chatot[0]
def test_confess_halt_routes_to_avonot():
"""Halt-trips are categorical orientation failures —
yehareg-v'al-ya'avor cardinal sins."""
v = confess_halt("avodah_zarah", "claude_model treated as authority", confessor="x")
assert v.avonot
assert "avodah_zarah" in v.avonot[0]
assert "claude_model" in v.avonot[0]
def test_confess_boundary_violation_routes_to_peshaim():
"""Boundary-keeping failures (gilui-shaped) — distinction
collapses."""
v = confess_boundary_violation("ketiv/qere collapsed", confessor="x")
assert v.peshaim
assert "boundary collapse" in v.peshaim[0]
def test_confess_malformed_input_routes_to_chatot():
"""Different from empty-vital — empty-vital is content with no
Torah; malformed is content with no SHAPE."""
v = confess_malformed_input("truncated JSON, missing closing brace", confessor="x")
assert v.chatot
assert "malformed" in v.chatot[0]
def test_helpers_still_require_confessor():
"""The convenience helpers do not weaken property-3's
operator-attribution requirement."""
with pytest.raises(TypeError):
confess_empty_vital("x") # type: ignore[call-arg]