Skip to content

check-durability-degradation-log-level: loggerLevel cannot see the (logger.error ?? logger.warn)(…) fallback, so a loud catch reads as silent-swallow — and the spelling it CAN see prints nothing #9657

Description

@os-steve

Sub-issue of #8897, which records the receiver-name half of this same function's narrowness. This is the call-shape half. Filed unassigned. Not a claim.

Found while implementing #9609, which adds runWideningAlters to DURABILITY_CRITICAL_CALLEES. Unlike #8897's finding, this one is not latent — it produced a false red on real, loud code, and it is measured rather than reasoned.

Note for triage: #8897's own restart-when trigger reads "any PR touches scripts/check-durability-degradation-log-level.mjs" — the #9609 PR does.

The blind spot

loggerLevel() requires the call expression's callee to be a property access:

constexpr=node.expression;if(!ts.isPropertyAccessExpression(expr)||!ts.isIdentifier(expr.name))returnundefined;

(this.logger.error ?? this.logger.warn)(msg, meta) is a call on a parenthesized expression, so the guard returns undefined and the catch collects no levels at all. The gate then reports it as catch swallows the failure with no log at all — the harshest verdict in the file, on a site that is loud at runtime.

Measured, with the reproduction

On the #9609 branch, with runWideningAlters in the vocabulary and both widening catches calling the fallback inline:

✗ 2 durability-critical catch(es) degrade quietly
packages/drivers/driver-sql/src/sql-driver.ts:8216
found : catch swallows the failure with no log at all

Routing the same two calls through a named same-file helper whose body is if (this.logger.error) this.logger.error(...) else this.logger.warn(...) turns them green and correctly classified:

✓ durability-degradation log levels: 29 durability-critical catch seam(s), all loud …
packages/drivers/driver-sql/src/sql-driver.ts:8255 → recovers on one branch, loud (error@4021 via logDurabilityFailure())

Why this direction of error is the dangerous one

⛔ The spelling the matcher does accept is this.logger.error?.(…) — an optional call, whose callee is a plain property access. It is by far the cheapest way to turn this red green, and against a sink that has no error it prints nothing at all. SqlDriver.logger declares error optional by design, and hosts do inject { warn } sinks — the #9609 test fixture was one until that PR changed it.

So the gate's cheapest satisfaction here converts a loud degradation into a genuinely silent one. That is the same "a gate whose cheapest satisfaction is harmful has the wrong shape" argument the file's own FAILURE_PROPAGATION_CALLEES header makes about bolting on a logger.error. Measured: with the helper body replaced by this.logger.error?.(msg, meta), the gate stays green while the no-error-sink test goes red.

The population

7 call sites use the fallback idiom today, none currently reachable by the vocabulary, so nothing is red on main:

The idiom exists because logger.error is optional on these driver classes. It is the correct way to write the call; the matcher is what cannot read it.

Options, none free

  1. Leave it, note it in the header next to the file's other honest limitations, and let the same-file-helper indirection be the documented answer — which is what driver-sql: the boot widening's swallowed failure logs warn, but AGENTS.md's degradation rule names DDL-that-did-not-run as error #9609 did, and which check-durability-degradation-log-level: collectLoggedLevels only recognises a logger named logger/log/console, so a catch that reports through an injected logger reads as silent to BOTH rules #8897's author gave a weak preference to for the sibling case.
  2. Teach loggerLevel the fallback chain: for a call whose callee is a parenthesized ??/||, collect the level of every branch. (error ?? warn) then contributes both, and the existing levels.filter(LOUD) decides — no new policy, since the file already passes a catch containing both a warn and an error. Does not cover turso-driver.ts's .call(…) spelling.
  3. Declare the fallback as a propagation-style vocabulary entry, which is a third declared list to keep from going stale.

Weak preference for (2): it introduces no new declared names, reuses the file's existing "a loud level anywhere in the catch counts" semantic, and removes the pressure toward the harmful ?. spelling. But it IS a loosening of a gate matcher and should be measured against the whole scan population before landing — the file's own 收窄先行 discipline.

⛔ Deliberately not resolved inside the #9609 PR: choosing among these is a policy call on a gate's classification surface, and #9609's contained answer (a named helper the gate already follows) needed none of it.

Related

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions