Summary
Multiple diagnostic and result fields are declared as bare str even though they only ever take a small closed set of values, and one of those constants is duplicated in two source files with no shared source of truth. Static type checkers, IDEs, and consumers cannot rely on the closed-set property today.
Concrete instances
A. Diagnostic.reason is bare str
src/benchflow/diagnostics.py:89, 115, 142 — IdleTimeoutDiagnostic.reason: str = "idle_timeout", SandboxStartupDiagnostic.reason: str = "sandbox_startup_failed", TransportClosedDiagnostic.reason: str = "transport_closed".
These are dataclass-class invariants — there is exactly one valid value per class. Should be Literal["idle_timeout"] etc., or a shared DiagnosticReason = Literal["idle_timeout", "sandbox_startup_failed", "transport_closed", …] re-exported from the diagnostic module.
Today, IdleTimeoutDiagnostic(reason="not_actually_a_timeout") type-checks. That's a regression vector.
B. IDLE_TIMEOUT constant duplicated
src/benchflow/_utils/scoring.py:10 declares IDLE_TIMEOUT = "idle_timeout". src/benchflow/diagnostics.py:89 hardcodes the same string as the diagnostic's default. Renaming one side does not error.
C. usage_source / price_source are bare str
src/benchflow/metrics.py:47, models.py:138, rollout.py:452: usage_source: str = "unavailable". The only two values ever assigned in v0.5 are "provider_response" and "unavailable". Compare to TrajectorySource = Literal["acp", ...] in models.py:15 — same project, different rigor.
price_source is null today but typed the same way; tighten when its enum is decided.
D. Inconsistent to_dict() Nones-policy
TransportClosedDiagnostic.to_dict() (diagnostics.py:170-194) drops keys with None values. IdleTimeoutDiagnostic.to_dict() does NOT drop Nones — uses bare asdict(). Same registry, different serialization shape. Hides "field is absent" vs "field is null" inconsistently.
Severity
P2.
No current incorrect-output bug — these are type-safety / API-stability hygiene issues. But:
- (A) and (B) together mean a one-line rename of
"idle_timeout" in scoring.py will silently misclassify every future idle-timeout incident.
- (C) means consumers cannot exhaustively pattern-match on these fields.
- (D) means
transport_error_info and idle_timeout_info look different to JSON-schema-driven consumers for no documented reason.
Proposed fix
-
Introduce DiagnosticReason (or per-class Literal) and replace the bare-str defaults. Move IDLE_TIMEOUT / TRANSPORT_CLOSED / SANDBOX_STARTUP_FAILED constants into diagnostics.py and re-export from _utils/scoring.py.
-
Typedef UsageSource = Literal["provider_response", "unavailable"] in metrics.py; apply to all three sites.
-
Decide the to_dict() Nones policy (drop-or-keep) once and apply uniformly. Lean toward "drop None" to match TransportClosedDiagnostic since it's already in production.
-
Add a unit test that asserts to_dict() outputs are JSON-schema-stable across all Diagnostic subclasses (no surprise key presence/absence).
Found via
v0.5 e2e validation §2, §3, §4+§5 at 5a7a326. Independently noted by three subagents inspecting result.json and diagnostics.py.
Summary
Multiple diagnostic and result fields are declared as bare
streven though they only ever take a small closed set of values, and one of those constants is duplicated in two source files with no shared source of truth. Static type checkers, IDEs, and consumers cannot rely on the closed-set property today.Concrete instances
A.
Diagnostic.reasonis barestrsrc/benchflow/diagnostics.py:89, 115, 142—IdleTimeoutDiagnostic.reason: str = "idle_timeout",SandboxStartupDiagnostic.reason: str = "sandbox_startup_failed",TransportClosedDiagnostic.reason: str = "transport_closed".These are dataclass-class invariants — there is exactly one valid value per class. Should be
Literal["idle_timeout"]etc., or a sharedDiagnosticReason = Literal["idle_timeout", "sandbox_startup_failed", "transport_closed", …]re-exported from the diagnostic module.Today,
IdleTimeoutDiagnostic(reason="not_actually_a_timeout")type-checks. That's a regression vector.B.
IDLE_TIMEOUTconstant duplicatedsrc/benchflow/_utils/scoring.py:10declaresIDLE_TIMEOUT = "idle_timeout".src/benchflow/diagnostics.py:89hardcodes the same string as the diagnostic's default. Renaming one side does not error.C.
usage_source/price_sourceare barestrsrc/benchflow/metrics.py:47,models.py:138,rollout.py:452:usage_source: str = "unavailable". The only two values ever assigned in v0.5 are"provider_response"and"unavailable". Compare toTrajectorySource = Literal["acp", ...]inmodels.py:15— same project, different rigor.price_sourceis null today but typed the same way; tighten when its enum is decided.D. Inconsistent
to_dict()Nones-policyTransportClosedDiagnostic.to_dict()(diagnostics.py:170-194) drops keys withNonevalues.IdleTimeoutDiagnostic.to_dict()does NOT drop Nones — uses bareasdict(). Same registry, different serialization shape. Hides "field is absent" vs "field is null" inconsistently.Severity
P2.
No current incorrect-output bug — these are type-safety / API-stability hygiene issues. But:
"idle_timeout"inscoring.pywill silently misclassify every future idle-timeout incident.transport_error_infoandidle_timeout_infolook different to JSON-schema-driven consumers for no documented reason.Proposed fix
Introduce
DiagnosticReason(or per-classLiteral) and replace the bare-str defaults. MoveIDLE_TIMEOUT/TRANSPORT_CLOSED/SANDBOX_STARTUP_FAILEDconstants intodiagnostics.pyand re-export from_utils/scoring.py.Typedef
UsageSource = Literal["provider_response", "unavailable"]inmetrics.py; apply to all three sites.Decide the
to_dict()Nones policy (drop-or-keep) once and apply uniformly. Lean toward "drop None" to matchTransportClosedDiagnosticsince it's already in production.Add a unit test that asserts
to_dict()outputs are JSON-schema-stable across allDiagnosticsubclasses (no surprise key presence/absence).Found via
v0.5 e2e validation §2, §3, §4+§5 at
5a7a326. Independently noted by three subagents inspectingresult.jsonanddiagnostics.py.