eef2fdc5df
The pool exhaustion in issue #15 was fatal only because min_size=10 forced a transient error to surface at pool creation, and that step was hardcoded to permanent death. Step is the wrong axis: it conflates "the DSN cannot be parsed" with "someone else holds all the connections right now". Failures are now classified by two rules. Fatal means the cause lies entirely inside this process and cannot change, which only the construction-time DSN satisfies. Everything else splits on whether the failure has anything to do with this row's data: row-level failures drop one row and keep trying, environment-level failures cool down for 60s and then get exactly one retry, so a restarted database or a DBA creating the table heals on its own. 42703 (missing column) is the single named exception and stays row-level even though every row fails alike: issue #13 promised that the manual mode trims the INSERT and exposes drift per row, and that promise outranks the rule. Any future exception owes the same argument. The _failed boolean is gone; the tracker is the only degradation state, because two copies of the same fact drift apart. Closing stays outside that state: it is the caller's own decision, not an anomaly to recover from, so the snapshot reports it through dropped_rows and the drop reason instead of raising the degraded flag on every clean shutdown.