1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
//! Unified error family for the solver's public API surface.
//!
//! Reconciled from grand-audit Pass 2 prototype 14 (`api-error-taxonomy`)
//! onto the Pass-3 composed tree (Pass 3 `py-module-reconciliation`, T9).
//!
//! Before this, the crate had two independent, incomplete error shapes:
//! `Unsatisfiable` (a bare unit struct, `lib.rs`) and `AssignmentError`
//! (`builder/assignment.rs`, five variants scoped to the assignment-problem
//! builder only, and — per the Pass-1 `rust-cop-builder` finding —
//! conflating budget-exhaustion with genuine infeasibility). Every
//! downstream binding (`py/`, wasm's `isomorphic.rs`) re-derived its own ad
//! hoc mapping of "something went wrong" onto a bare
//! `PyRuntimeError`/`JsError` string, which is exactly the "silent/conflated
//! handling" the audit's fail-explicit precept bans (Pass-1 ledger R15, B0).
//!
//! `CspError` is the one family every layer should map 1:1 rather than
//! reinvent: PyO3 via `create_exception!` (`py/errors.rs`) and wasm via a
//! typed `WasmCspError` that stamps a `.code` onto a genuine `Error` instance
//! (`wasm/src/errors.rs`, not reconciled in this pass — see report). `code()`
//! is the one string all downstream layers agree on.
//!
//! Tests: `tests/error.rs`.
use fmt;
/// Unified error family for `Csp`/Sudoku/Futoshiki operations exposed
/// across the PyO3 and wasm boundaries.
///
/// Deliberately flat (no nested `Box<dyn Error>` source chaining) — every
/// downstream binding needs to pattern-match this by value to pick an
/// exception class / HTTP status, and a flat enum is the cheapest thing
/// that supports that without reflection.
/// The propagation layer's existing marker type converts losslessly — every
/// current `?`-using call site (`Csp::propagate`, `Csp::propagate_with`)
/// keeps compiling unchanged; only the bindings need to switch their
/// `map_err` target.
/// `AssignmentBuilder`'s error family converts too, so `py/`/wasm call
/// sites that touch the assignment/COP path get the same typed exceptions
/// as the Sudoku path for free. Note this conversion is itself evidence of
/// Pass-1 finding R8: `Infeasible` collapses onto `Unsatisfiable` here
/// because `AssignmentError` has no distinct budget-exhaustion variant yet
/// — fixing that is a separate, already ledgered item, not silently patched
/// over by this mapping.