Skip to main content

xlog_solve/
production.rs

1//! Production GPU solver adapter for epistemic callers.
2//!
3//! This module is intentionally thin: it routes accepted solver work into the
4//! existing GPU CDCL verifier instead of using the bounded CPU semantic-oracle
5//! facade in [`crate::SolverService`].
6
7use std::collections::{BTreeMap, BTreeSet};
8use std::sync::Arc;
9
10use xlog_core::{Result, XlogError};
11use xlog_cuda::memory::TrackedCudaSlice;
12use xlog_cuda::{CudaKernelProvider, DeviceSlice};
13use xlog_runtime::{
14    EpistemicGpuBatchExecutionResult, EpistemicGpuExecutionResult, EpistemicGpuKernelTimingTrace,
15    EpistemicGpuProviderIdentity,
16};
17
18use crate::{GpuCdclConfig, GpuCdclSolver, GpuCdclWorkspace, GpuCnf, Objective, SolveInstance};
19
20const PRODUCTION_SOLVER_REQUIRED_CAPABILITY_COUNT: u64 = 5;
21const PRODUCTION_SOLVER_REQUIRED_STATUS_COUNT: u64 = 4;
22const MAX_WEIGHTED_MAXSAT_FRONTIER_COMPLETION_CANDIDATES: u64 = 64;
23
24macro_rules! checked_solver_trace_counter_inc {
25    ($adapter:ident, $field:ident) => {{
26        $adapter.trace.$field = GpuSolverProductionAdapter::checked_trace_counter_add(
27            $adapter.trace.$field,
28            1,
29            stringify!($field),
30        )?;
31    }};
32}
33
34macro_rules! checked_solver_report_counter_inc {
35    ($report:ident, $field:ident) => {{
36        $report.$field = GpuSolverProductionAdapter::checked_report_counter_add(
37            $report.$field,
38            1,
39            stringify!($field),
40        )?;
41    }};
42}
43
44macro_rules! checked_solver_report_counter_add {
45    ($report:ident, $field:ident, $delta:expr) => {{
46        $report.$field = GpuSolverProductionAdapter::checked_report_counter_add(
47            $report.$field,
48            $delta,
49            stringify!($field),
50        )?;
51    }};
52}
53
54/// Production capability status for solver paths required by production metric gates.
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum GpuSolverProductionCapabilityStatus {
57    /// Existing GPU-native production path is available.
58    Available,
59    /// Required GPU-native production path is not implemented.
60    Blocked,
61}
62
63/// Backend policy for solver executions admitted to production metrics.
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum GpuSolverProductionMetricBackend {
66    /// Only executions proved by retained GPU solver events are eligible.
67    GpuOnly,
68}
69
70/// Capability report for the solver production adapter.
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub struct GpuSolverProductionCapabilities {
73    /// Complete SAT/UNSAT execution through the existing GPU CDCL verifier.
74    pub gpu_cdcl_sat_unsat: GpuSolverProductionCapabilityStatus,
75    /// GPU-native MaxSAT production execution.
76    pub gpu_maxsat: GpuSolverProductionCapabilityStatus,
77    /// GPU SAT/MaxSAT/status-aware portfolio production execution.
78    pub gpu_portfolio_sat_maxsat: GpuSolverProductionCapabilityStatus,
79    /// Backend policy enforced for production-metric eligibility.
80    pub production_metric_backend: GpuSolverProductionMetricBackend,
81    /// Blocker reason for GPU-native MaxSAT, or empty when available.
82    pub gpu_maxsat_blocker: &'static str,
83    /// Blocker reason for GPU SAT/MaxSAT/status-aware portfolio execution.
84    pub gpu_portfolio_blocker: &'static str,
85}
86
87/// Solver-facing state derived from an accepted GPU epistemic execution result.
88///
89/// This is the production boundary between the epistemic candidate state machine
90/// and solver services. It is built only after the GPU semantic trace, final
91/// output, and hot-path counters have passed accepted-path validation.
92#[derive(Debug, Clone, PartialEq, Eq)]
93pub struct GpuSolverAcceptedCandidateState {
94    /// Number of accepted GPU execution records represented by this state.
95    pub evidence_records: u64,
96    /// Device candidate indices accepted by world-view validation.
97    pub accepted_candidate_indices: Vec<usize>,
98    /// Number of accepted candidate states entering solver services.
99    pub accepted_candidates: u64,
100    /// Number of accepted world views represented by accepted candidates.
101    pub accepted_world_views: u64,
102    /// Logical final-output rows materialized by the accepted GPU execution.
103    pub final_output_rows: u64,
104    /// Epistemic literal count in the accepted GPU plan.
105    pub epistemic_literals: u64,
106    /// Tuple-membership bindings consumed by the accepted GPU plan.
107    pub tuple_membership_bindings: u64,
108    /// Solver assumption bindings exported by the accepted semantic plan.
109    pub solver_assumption_bindings: u64,
110    /// Solver production capabilities required by the accepted semantic plan.
111    pub solver_required_capabilities: u64,
112    /// Solver statuses that must cross the accepted semantic boundary distinctly.
113    pub solver_required_statuses: u64,
114    /// Whether the accepted evidence came from Gelfond-1991 compatibility mode.
115    pub g91_mode: bool,
116    /// Whether the accepted evidence came from FAEEL mode.
117    pub faeel_mode: bool,
118    /// Whether accepted evidence contains `know` operators.
119    pub has_know_operator: bool,
120    /// Whether accepted evidence contains `possible` operators.
121    pub has_possible_operator: bool,
122    /// Whether accepted evidence contains `not possible` operators.
123    pub has_not_possible_operator: bool,
124    /// Whether accepted evidence contains `not know` operators.
125    pub has_not_know_operator: bool,
126    /// Tuple-key column reads performed while staging accepted tuple evidence.
127    pub tuple_key_column_reads: u64,
128    /// Whether accepted evidence includes nonzero-arity tuple keys.
129    pub has_nonzero_arity_tuple_keys: bool,
130    /// GPU final-tuple row filters used to materialize variable-bound evidence.
131    pub final_tuple_row_filters: u64,
132    /// Negated GPU final-tuple row filters used to materialize variable-bound evidence.
133    pub final_tuple_negated_row_filters: u64,
134    /// Final-output row capacity checked against row-specific GPU model slots.
135    pub row_specific_membership_row_capacity: u64,
136    /// Final-output row capacity checked by fallback GPU row filters outside model slots.
137    pub row_filter_fallback_row_capacity: u64,
138    /// Reduced integrity-constraint relations checked before entering solver services.
139    pub checked_constraint_relations: u64,
140    /// Constraint row-count metadata reads used before entering solver services.
141    pub constraint_row_count_device_reads: u64,
142}
143
144impl GpuSolverAcceptedCandidateState {
145    fn from_validated_result(
146        result: &EpistemicGpuExecutionResult,
147        final_output_rows: usize,
148    ) -> Self {
149        let preflight = &result.prepared.preflight;
150        let tuple_key_column_reads = result.model_membership.tuple_source_key_column_device_reads;
151        Self {
152            evidence_records: 1,
153            accepted_candidate_indices: result.semantic_trace.accepted_candidate_indices.clone(),
154            accepted_candidates: result.semantic_trace.accepted_candidates as u64,
155            accepted_world_views: result.semantic_trace.accepted_world_views as u64,
156            final_output_rows: final_output_rows as u64,
157            epistemic_literals: result.candidate_generation.literal_count as u64,
158            tuple_membership_bindings: preflight.tuple_membership_binding_count as u64,
159            solver_assumption_bindings: preflight.solver_assumption_binding_count as u64,
160            solver_required_capabilities: preflight.solver_required_capability_count as u64,
161            solver_required_statuses: preflight.solver_required_status_count as u64,
162            g91_mode: preflight.is_g91_mode(),
163            faeel_mode: preflight.is_faeel_mode(),
164            has_know_operator: preflight.know_operator_count > 0,
165            has_possible_operator: preflight.possible_operator_count > 0,
166            has_not_possible_operator: preflight.not_possible_operator_count > 0,
167            has_not_know_operator: preflight.not_know_operator_count > 0,
168            tuple_key_column_reads: tuple_key_column_reads as u64,
169            has_nonzero_arity_tuple_keys: tuple_key_column_reads > 0,
170            final_tuple_row_filters: result.final_tuple_materialization.row_filter_count as u64,
171            final_tuple_negated_row_filters: result
172                .final_tuple_materialization
173                .negated_row_filter_count as u64,
174            row_specific_membership_row_capacity: result
175                .final_tuple_materialization
176                .row_specific_membership_row_capacity
177                as u64,
178            row_filter_fallback_row_capacity: result
179                .final_tuple_materialization
180                .row_filter_row_capacity_outside_model_slot_window
181                as u64,
182            checked_constraint_relations: result.constraint_validation.checked_constraint_relations
183                as u64,
184            constraint_row_count_device_reads: result.constraint_validation.row_count_device_reads
185                as u64,
186        }
187    }
188}
189
190/// Expected GPU CDCL result for one production lifecycle step.
191#[derive(Debug, Clone, Copy, PartialEq, Eq)]
192pub enum GpuSolverProductionExpectation {
193    /// The step must be SAT under the currently pushed assumptions.
194    Sat,
195    /// The step must be UNSAT under the currently pushed assumptions.
196    Unsat,
197    /// The accepted lifecycle step ended without a determined SAT/UNSAT status.
198    Unknown {
199        /// Diagnostic reason reported by the GPU-backed lifecycle scheduler.
200        reason: &'static str,
201    },
202    /// The accepted lifecycle step exhausted its GPU-backed budget.
203    Timeout {
204        /// Nonzero timeout budget observed by the lifecycle scheduler.
205        budget_micros: u64,
206    },
207}
208
209/// One accepted solver lifecycle step backed by existing GPU CDCL inputs.
210#[derive(Clone, Copy)]
211pub struct GpuSolverProductionLifecycleStep<'a> {
212    /// Device-resident CNF for this step, including any assumption clauses.
213    pub cnf: &'a GpuCnf,
214    /// Device-resident branch limit passed to the GPU CDCL solver.
215    pub branch_var_limit: &'a TrackedCudaSlice<u32>,
216    /// Expected SAT/UNSAT status for the step.
217    pub expectation: GpuSolverProductionExpectation,
218}
219
220/// Summary of an accepted solver lifecycle run.
221#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
222pub struct GpuSolverProductionLifecycleReport {
223    /// Number of accepted GPU epistemic candidate evidence records consumed.
224    pub candidate_evidence_records: u64,
225    /// Number of lifecycle steps executed.
226    pub steps: u64,
227    /// Number of lifecycle steps that reached SAT through GPU CDCL.
228    pub sat_steps: u64,
229    /// Number of lifecycle steps that reached UNSAT through GPU CDCL.
230    pub unsat_steps: u64,
231    /// Number of assumption pushes recorded before GPU solves.
232    pub assumption_pushes: u64,
233    /// Number of assumption retractions recorded after GPU solves.
234    pub assumption_retractions: u64,
235    /// Number of UNSAT steps that reused the provided GPU CDCL workspace allocation.
236    pub workspace_reuses: u64,
237    /// Number of lifecycle steps that propagated UNKNOWN without CPU search.
238    pub unknown_steps: u64,
239    /// Number of lifecycle steps that propagated TIMEOUT without CPU search.
240    pub timeout_steps: u64,
241}
242
243/// Accepted split/batch GPU epistemic evidence for solver production reuse.
244#[derive(Clone, Copy)]
245pub struct GpuSolverProductionBatchExecutionEvidence<'a> {
246    /// Results plus aggregate trace from the split/batch GPU execution adapter.
247    pub batch: &'a EpistemicGpuBatchExecutionResult,
248}
249
250/// Summary of a GPU CDCL learned-clause arena publication.
251#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
252pub struct GpuSolverProductionLearnedClauseArenaReport {
253    /// Number of UNSAT solves used to populate the learned-clause/proof arena.
254    pub unsat_solves: u64,
255    /// Number of learned-clause arenas published from device buffers.
256    pub gpu_learned_clause_arena_publications: u64,
257    /// Number of learned-count device buffers published with the arena.
258    pub gpu_learned_count_buffer_publications: u64,
259}
260
261/// Summary of a bounded GPU CDCL learned-clause reuse run.
262#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
263pub struct GpuSolverProductionLearnedClauseReuseReport {
264    /// Number of accepted GPU epistemic candidate evidence records consumed.
265    pub candidate_evidence_records: u64,
266    /// Number of accepted candidate solves represented by this bounded reuse run.
267    pub candidates: u64,
268    /// Number of UNSAT solves executed through the reusable GPU CDCL workspace.
269    pub unsat_solves: u64,
270    /// Number of learned-clause arenas published from device buffers.
271    pub gpu_learned_clause_arena_publications: u64,
272    /// Number of learned-clause arenas imported from device buffers.
273    pub gpu_learned_clause_imports: u64,
274    /// Number of UNSAT solves that reused imported GPU learned clauses.
275    pub gpu_learned_clause_reused_solves: u64,
276}
277
278/// One GPU-CDCL-backed candidate for bounded weighted MaxSAT production solving.
279///
280/// The candidate CNF should encode the hard clauses plus the soft-clause subset
281/// represented by `score`. The adapter certifies each provided candidate through
282/// the existing GPU CDCL SAT path; it does not enumerate assignments on CPU.
283#[derive(Clone, Copy)]
284pub struct GpuSolverProductionMaxSatCandidate<'a> {
285    /// Candidate MaxSAT score represented by this satisfiable CNF.
286    pub score: u64,
287    /// Device-resident CNF for this MaxSAT candidate.
288    pub cnf: &'a GpuCnf,
289    /// Device-resident branch limit passed to the GPU CDCL solver.
290    pub branch_var_limit: &'a TrackedCudaSlice<u32>,
291}
292
293/// Expected GPU-CDCL status for a bounded MaxSAT search candidate.
294#[derive(Debug, Clone, Copy, PartialEq, Eq)]
295pub enum GpuSolverProductionMaxSatSearchStatus {
296    /// The candidate should be satisfiable and eligible for optimum scoring.
297    Satisfiable,
298    /// The candidate should be unsatisfiable and pruned by GPU CDCL.
299    Unsatisfiable,
300}
301
302/// One GPU-CDCL-backed candidate in a bounded weighted MaxSAT search.
303#[derive(Clone, Copy)]
304pub struct GpuSolverProductionMaxSatSearchCandidate<'a> {
305    /// Candidate MaxSAT score represented when the CNF is satisfiable.
306    pub score: u64,
307    /// Device-resident CNF for this MaxSAT candidate.
308    pub cnf: &'a GpuCnf,
309    /// Device-resident branch limit passed to the GPU CDCL solver.
310    pub branch_var_limit: &'a TrackedCudaSlice<u32>,
311    /// Expected candidate status certified by GPU CDCL.
312    pub status: GpuSolverProductionMaxSatSearchStatus,
313}
314
315/// One caller-declared weighted soft-clause selection for GPU MaxSAT search encoding.
316///
317/// The adapter treats `soft_clause_indices` as seed soft clauses for a bounded
318/// search candidate, completes any upper-bound boundary candidates implied by
319/// UNSAT seeds, uploads each candidate with the existing GPU CNF layout, and
320/// certifies `status` through GPU CDCL. It does not enumerate assignments on CPU.
321#[derive(Clone, Copy)]
322pub struct GpuSolverProductionWeightedMaxSatSelection<'a> {
323    /// Indices of weighted soft clauses selected for this bounded candidate.
324    pub soft_clause_indices: &'a [usize],
325    /// Expected GPU-CDCL status for the encoded selected-clause CNF.
326    pub status: GpuSolverProductionMaxSatSearchStatus,
327}
328
329struct GpuSolverProductionEncodedMaxSatSearchCandidate {
330    score: u64,
331    cnf: GpuCnf,
332    status: GpuSolverProductionMaxSatSearchStatus,
333}
334
335struct GpuSolverProductionCompletedWeightedMaxSatFrontier {
336    selections: Vec<GpuSolverProductionOwnedWeightedMaxSatSelection>,
337    completion_candidate_count: u64,
338}
339
340#[derive(Clone)]
341struct GpuSolverProductionOwnedWeightedMaxSatSelection {
342    soft_clause_indices: Vec<usize>,
343    status: GpuSolverProductionMaxSatSearchStatus,
344}
345
346struct GpuSolverProductionUnsatFrontierCertificate {
347    indices: Vec<usize>,
348    min_weight: u64,
349    min_weight_indices: Vec<usize>,
350}
351
352/// Summary of one bounded GPU-backed MaxSAT production adapter run.
353#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
354pub struct GpuSolverProductionMaxSatReport {
355    /// Number of accepted GPU epistemic candidate evidence records consumed.
356    pub candidate_evidence_records: u64,
357    /// Best score among GPU-certified satisfiable candidates.
358    pub optimum_score: u64,
359    /// Number of candidate CNFs checked.
360    pub candidates_checked: u64,
361    /// Number of GPU-certified satisfiable candidates eligible for scoring.
362    pub satisfiable_candidates: u64,
363    /// Number of GPU-certified UNSAT candidates pruned from scoring.
364    pub unsat_candidates_pruned: u64,
365    /// Number of weighted MaxSAT selections encoded into GPU CNF candidates.
366    pub gpu_cdcl_candidate_encodes: u64,
367    /// Number of candidate solves dispatched through GPU CDCL.
368    pub gpu_cdcl_candidate_solves: u64,
369    /// Number of encoded weighted frontiers with a certified optimum upper bound.
370    pub frontier_upper_bound_certificates: u64,
371}
372
373/// Summary of a combined accepted solver lifecycle plus MaxSAT run.
374#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
375pub struct GpuSolverProductionMaxSatLifecycleReport {
376    /// Number of accepted GPU epistemic candidate evidence records consumed.
377    pub candidate_evidence_records: u64,
378    /// Push/solve/retract lifecycle report for the accepted GPU evidence.
379    pub lifecycle: GpuSolverProductionLifecycleReport,
380    /// Bounded MaxSAT candidate report for the same accepted GPU evidence.
381    pub maxsat: GpuSolverProductionMaxSatReport,
382}
383
384/// One job in an accepted GPU-backed MaxSAT scheduler batch.
385#[derive(Clone, Copy)]
386pub enum GpuSolverProductionMaxSatScheduleJob<'a> {
387    /// Certify a caller-provided weighted candidate set through GPU CDCL SAT.
388    CandidateSet {
389        /// Candidate set to certify.
390        candidates: &'a [GpuSolverProductionMaxSatCandidate<'a>],
391    },
392    /// Certify and prune a caller-provided weighted MaxSAT search frontier.
393    Search {
394        /// Search candidates to certify or prune.
395        candidates: &'a [GpuSolverProductionMaxSatSearchCandidate<'a>],
396    },
397    /// Encode weighted soft-clause selections into GPU CNF candidates before search.
398    EncodedSearch {
399        /// Weighted MaxSAT instance whose soft clauses define the schedule.
400        weighted: &'a SolveInstance,
401        /// Device-resident branch limit passed to the GPU CDCL solver.
402        branch_var_limit: &'a TrackedCudaSlice<u32>,
403        /// Soft-clause selections to encode and certify.
404        selections: &'a [GpuSolverProductionWeightedMaxSatSelection<'a>],
405    },
406    /// A scheduled MaxSAT batch whose GPU-backed budget ended inconclusively.
407    Unknown {
408        /// Diagnostic reason recorded by the accepted scheduler.
409        reason: &'static str,
410    },
411    /// A scheduled MaxSAT batch whose accepted GPU-backed budget timed out.
412    Timeout {
413        /// Timeout budget observed by the accepted scheduler.
414        budget_micros: u64,
415    },
416}
417
418/// Summary of a heterogeneous GPU-backed MaxSAT scheduler batch.
419#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
420pub struct GpuSolverProductionMaxSatScheduleReport {
421    /// Number of accepted GPU epistemic candidate evidence records consumed.
422    pub candidate_evidence_records: u64,
423    /// Number of scheduled jobs executed.
424    pub jobs: u64,
425    /// Number of weighted candidate-set jobs.
426    pub candidate_set_jobs: u64,
427    /// Number of search-pruning jobs.
428    pub search_jobs: u64,
429    /// Number of weighted soft-clause encoding plus search jobs.
430    pub encoded_search_jobs: u64,
431    /// Number of UNKNOWN statuses propagated without CPU search.
432    pub unknown_jobs: u64,
433    /// Number of TIMEOUT statuses propagated without CPU search.
434    pub timeout_jobs: u64,
435    /// Best optimum score observed across all GPU-certified scheduled MaxSAT jobs.
436    pub optimum_score: u64,
437    /// Number of candidate CNFs checked across scheduled MaxSAT jobs.
438    pub candidates_checked: u64,
439    /// Number of GPU-certified satisfiable candidates eligible for scoring.
440    pub satisfiable_candidates: u64,
441    /// Number of GPU-certified UNSAT candidates pruned from scoring.
442    pub unsat_candidates_pruned: u64,
443    /// Number of weighted MaxSAT selections encoded into GPU CNF candidates.
444    pub gpu_cdcl_candidate_encodes: u64,
445    /// Number of candidate solves dispatched through GPU CDCL.
446    pub gpu_cdcl_candidate_solves: u64,
447    /// Number of encoded weighted frontiers with a certified optimum upper bound.
448    pub frontier_upper_bound_certificates: u64,
449}
450
451/// One job in a bounded GPU solver portfolio.
452#[derive(Clone, Copy)]
453pub enum GpuSolverProductionPortfolioJob<'a> {
454    /// A SAT job dispatched through GPU CDCL.
455    Sat {
456        /// Device-resident CNF for this SAT job.
457        cnf: &'a GpuCnf,
458        /// Device-resident branch limit passed to the GPU CDCL solver.
459        branch_var_limit: &'a TrackedCudaSlice<u32>,
460    },
461    /// A bounded MaxSAT job dispatched through GPU CDCL candidate checks.
462    MaxSat {
463        /// Candidate set to certify.
464        candidates: &'a [GpuSolverProductionMaxSatCandidate<'a>],
465    },
466    /// A weighted MaxSAT job encoded into GPU CNF candidates with an optimum
467    /// upper-bound certificate.
468    EncodedMaxSat {
469        /// Weighted MaxSAT instance whose soft clauses define the encoded candidates.
470        weighted: &'a SolveInstance,
471        /// Device-resident branch limit passed to the GPU CDCL solver.
472        branch_var_limit: &'a TrackedCudaSlice<u32>,
473        /// Soft-clause selections to encode and certify.
474        selections: &'a [GpuSolverProductionWeightedMaxSatSelection<'a>],
475    },
476    /// A status-aware job whose GPU-backed portfolio budget ended inconclusively.
477    Unknown {
478        /// Diagnostic reason recorded by the accepted portfolio scheduler.
479        reason: &'static str,
480    },
481    /// A status-aware job whose accepted portfolio budget timed out.
482    Timeout {
483        /// Timeout budget observed by the accepted portfolio scheduler.
484        budget_micros: u64,
485    },
486}
487
488/// Summary of one bounded GPU SAT/MaxSAT/status-aware portfolio run.
489#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
490pub struct GpuSolverProductionPortfolioReport {
491    /// Number of accepted GPU epistemic candidate evidence records consumed.
492    pub candidate_evidence_records: u64,
493    /// Number of portfolio jobs executed.
494    pub jobs: u64,
495    /// Number of SAT jobs executed.
496    pub sat_jobs: u64,
497    /// Number of MaxSAT jobs executed.
498    pub maxsat_jobs: u64,
499    /// Number of portfolio jobs that propagated UNKNOWN without CPU search.
500    pub unknown_jobs: u64,
501    /// Number of portfolio jobs that propagated TIMEOUT without CPU search.
502    pub timeout_jobs: u64,
503    /// Sum of best MaxSAT scores returned by MaxSAT jobs.
504    pub maxsat_optimum_scores: u64,
505    /// Number of MaxSAT candidate CNFs checked by portfolio jobs.
506    pub maxsat_candidates_checked: u64,
507    /// Number of satisfiable MaxSAT candidate CNFs scored by portfolio jobs.
508    pub maxsat_satisfiable_candidates: u64,
509    /// Number of unsatisfiable MaxSAT candidate CNFs pruned by portfolio jobs.
510    pub maxsat_unsat_candidates_pruned: u64,
511    /// Number of weighted MaxSAT selections encoded into GPU CNF candidates by portfolio
512    /// jobs.
513    pub maxsat_gpu_cdcl_candidate_encodes: u64,
514    /// Number of MaxSAT candidate solves dispatched through GPU CDCL by portfolio jobs.
515    pub maxsat_gpu_cdcl_candidate_solves: u64,
516    /// Number of encoded weighted frontiers with a certified optimum upper bound.
517    pub maxsat_frontier_upper_bound_certificates: u64,
518}
519
520/// Return the current production solver capability report.
521pub fn production_capabilities() -> GpuSolverProductionCapabilities {
522    GpuSolverProductionCapabilities {
523        gpu_cdcl_sat_unsat: GpuSolverProductionCapabilityStatus::Available,
524        gpu_maxsat: GpuSolverProductionCapabilityStatus::Available,
525        gpu_portfolio_sat_maxsat: GpuSolverProductionCapabilityStatus::Available,
526        production_metric_backend: GpuSolverProductionMetricBackend::GpuOnly,
527        gpu_maxsat_blocker: "",
528        gpu_portfolio_blocker: "",
529    }
530}
531
532/// Trace counters proving the production adapter stayed on the GPU CDCL path.
533#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
534pub struct GpuSolverProductionTrace {
535    /// Number of accepted GPU epistemic candidate evidence records consumed.
536    pub accepted_gpu_candidate_evidence_consumed: u64,
537    /// Number of accepted GPU candidate states passed into solver services.
538    pub accepted_gpu_candidate_state_transitions: u64,
539    /// Number of accepted GPU world-view states passed into solver services.
540    pub accepted_gpu_world_view_state_transitions: u64,
541    /// Logical final-output rows represented by accepted solver evidence.
542    pub accepted_gpu_candidate_final_output_rows_consumed: u64,
543    /// Number of accepted split/batch GPU epistemic candidate evidence records consumed.
544    pub accepted_gpu_batch_candidate_evidence_consumed: u64,
545    /// Number of accepted split/batch GPU epistemic component evidence records consumed.
546    pub accepted_gpu_batch_candidate_component_evidence_consumed: u64,
547    /// Number of accepted Gelfond-1991 compatibility GPU epistemic candidate evidence records consumed.
548    pub accepted_g91_gpu_candidate_evidence_consumed: u64,
549    /// Number of accepted FAEEL GPU epistemic candidate evidence records consumed.
550    pub accepted_faeel_gpu_candidate_evidence_consumed: u64,
551    /// Number of accepted GPU candidate evidence records containing `know` operators.
552    pub accepted_know_gpu_candidate_evidence_consumed: u64,
553    /// Number of accepted GPU candidate evidence records containing `possible` operators.
554    pub accepted_possible_gpu_candidate_evidence_consumed: u64,
555    /// Number of accepted GPU candidate evidence records containing `not possible` operators.
556    pub accepted_not_possible_gpu_candidate_evidence_consumed: u64,
557    /// Number of accepted GPU candidate evidence records containing `not know` operators.
558    pub accepted_not_know_gpu_candidate_evidence_consumed: u64,
559    /// Number of accepted GPU candidate evidence records backed by nonzero-arity tuple keys.
560    pub accepted_nonzero_arity_gpu_candidate_evidence_consumed: u64,
561    /// Aggregate tuple-key column reads consumed from accepted GPU candidate evidence.
562    pub accepted_gpu_candidate_tuple_key_column_reads_consumed: u64,
563    /// Planner-exported solver assumption bindings consumed from accepted GPU solver evidence.
564    pub accepted_solver_assumption_bindings_consumed: u64,
565    /// Required solver capabilities consumed from accepted GPU solver evidence.
566    pub accepted_solver_required_capabilities_consumed: u64,
567    /// Required solver statuses consumed from accepted GPU solver evidence.
568    pub accepted_solver_required_statuses_consumed: u64,
569    /// GPU final-tuple row filters consumed from accepted GPU solver evidence.
570    pub accepted_gpu_final_tuple_row_filters_consumed: u64,
571    /// Negated GPU final-tuple row filters consumed from accepted GPU solver evidence.
572    pub accepted_gpu_final_tuple_negated_row_filters_consumed: u64,
573    /// Row-specific GPU model-slot capacity consumed from accepted GPU solver evidence.
574    pub accepted_gpu_row_specific_membership_row_capacity_consumed: u64,
575    /// Fallback GPU row-filter capacity consumed outside bounded model-slot windows.
576    pub accepted_gpu_row_filter_fallback_row_capacity_consumed: u64,
577    /// Reduced integrity-constraint relations checked before accepted solver work.
578    pub accepted_gpu_constraint_relations_checked_consumed: u64,
579    /// Constraint row-count metadata reads consumed before accepted solver work.
580    pub accepted_gpu_constraint_row_count_device_reads_consumed: u64,
581    /// GPU solver production/status events that occurred inside accepted epistemic evidence gates.
582    pub accepted_gpu_solver_production_path_events: u64,
583    /// Number of SAT expectations dispatched through `GpuCdclSolver`.
584    pub gpu_cdcl_sat_solves: u64,
585    /// Number of UNSAT expectations dispatched through `GpuCdclSolver`.
586    pub gpu_cdcl_unsat_solves: u64,
587    /// Number of UNSAT expectations dispatched with a reusable GPU workspace.
588    pub gpu_cdcl_workspace_unsat_solves: u64,
589    /// Number of assumption pushes recorded for accepted lifecycle steps.
590    pub gpu_assumption_pushes: u64,
591    /// Number of assumption retractions recorded for accepted lifecycle steps.
592    pub gpu_assumption_retractions: u64,
593    /// Number of lifecycle UNSAT steps that reused the same GPU CDCL workspace.
594    pub gpu_lifecycle_workspace_reuses: u64,
595    /// Number of lifecycle UNKNOWN statuses propagated without CPU search.
596    pub gpu_lifecycle_unknown_status_steps: u64,
597    /// Number of lifecycle TIMEOUT statuses propagated without CPU search.
598    pub gpu_lifecycle_timeout_status_steps: u64,
599    /// Number of device learned-clause arenas published by accepted GPU CDCL solves.
600    pub gpu_learned_clause_arena_publications: u64,
601    /// Number of device learned-count buffers published with learned-clause arenas.
602    pub gpu_learned_count_buffer_publications: u64,
603    /// Number of device learned-clause arenas imported into later GPU CDCL solves.
604    pub gpu_learned_clause_imports: u64,
605    /// Number of GPU CDCL solves that reused imported learned clauses.
606    pub gpu_learned_clause_reused_solves: u64,
607    /// Number of learned-clause imports rejected because candidate CNFs differ.
608    pub gpu_learned_clause_reuse_rejections: u64,
609    /// Number of bounded MaxSAT candidate CNFs dispatched through GPU CDCL.
610    pub gpu_maxsat_candidate_solves: u64,
611    /// Number of MaxSAT candidate solves covered by an encoded frontier upper-bound certificate.
612    pub gpu_maxsat_frontier_certified_candidate_solves: u64,
613    /// Number of weighted MaxSAT selections encoded into GPU CNF candidates.
614    pub gpu_maxsat_candidate_encodes: u64,
615    /// Data-plane host-to-device calls used while uploading encoded MaxSAT CNF candidates.
616    pub gpu_maxsat_candidate_cnf_data_plane_htod_calls: u64,
617    /// Data-plane host-to-device bytes used while uploading encoded MaxSAT CNF candidates.
618    pub gpu_maxsat_candidate_cnf_data_plane_htod_bytes: u64,
619    /// Data-plane device-to-host calls observed while uploading encoded MaxSAT CNF candidates.
620    pub gpu_maxsat_candidate_cnf_data_plane_dtoh_calls: u64,
621    /// Data-plane device-to-host bytes observed while uploading encoded MaxSAT CNF candidates.
622    pub gpu_maxsat_candidate_cnf_data_plane_dtoh_bytes: u64,
623    /// Launch-metadata host-to-device calls used while uploading encoded MaxSAT CNF candidates.
624    pub gpu_maxsat_candidate_cnf_launch_metadata_htod_calls: u64,
625    /// Launch-metadata host-to-device bytes used while uploading encoded MaxSAT CNF candidates.
626    pub gpu_maxsat_candidate_cnf_launch_metadata_htod_bytes: u64,
627    /// Number of upper-bound frontier candidates derived before GPU CDCL verification.
628    pub gpu_maxsat_frontier_completion_candidate_encodes: u64,
629    /// Number of encoded weighted MaxSAT frontiers with a certified optimum upper bound.
630    pub gpu_maxsat_frontier_upper_bound_certificates: u64,
631    /// Number of heterogeneous MaxSAT scheduler jobs dispatched.
632    pub gpu_maxsat_scheduler_jobs: u64,
633    /// Number of scheduler candidate-set jobs dispatched.
634    pub gpu_maxsat_scheduler_candidate_set_jobs: u64,
635    /// Number of scheduler search-pruning jobs dispatched.
636    pub gpu_maxsat_scheduler_search_jobs: u64,
637    /// Number of scheduler encoded-search jobs dispatched.
638    pub gpu_maxsat_scheduler_encoded_search_jobs: u64,
639    /// Number of scheduler UNKNOWN statuses propagated without CPU search.
640    pub gpu_maxsat_scheduler_unknown_status_jobs: u64,
641    /// Number of scheduler TIMEOUT statuses propagated without CPU search.
642    pub gpu_maxsat_scheduler_timeout_status_jobs: u64,
643    /// Number of bounded MaxSAT search candidates pruned as UNSAT by GPU CDCL.
644    pub gpu_maxsat_unsat_candidate_prunes: u64,
645    /// Number of bounded MaxSAT optima certified by GPU CDCL candidate solves.
646    pub gpu_maxsat_optima: u64,
647    /// Number of portfolio jobs dispatched by the production adapter.
648    pub gpu_portfolio_jobs: u64,
649    /// Number of SAT jobs dispatched through the portfolio adapter.
650    pub gpu_portfolio_sat_jobs: u64,
651    /// Number of MaxSAT jobs dispatched through the portfolio adapter.
652    pub gpu_portfolio_maxsat_jobs: u64,
653    /// Number of accepted portfolio UNKNOWN statuses propagated without CPU search.
654    pub gpu_portfolio_unknown_status_jobs: u64,
655    /// Number of accepted portfolio TIMEOUT statuses propagated without CPU search.
656    pub gpu_portfolio_timeout_status_jobs: u64,
657}
658
659#[derive(Debug, Clone, Copy)]
660struct GpuSolverAcceptedPathEventSnapshot {
661    production: u64,
662    status: u64,
663}
664
665impl GpuSolverProductionTrace {
666    fn checked_gpu_solver_production_path_events(&self) -> Result<u64> {
667        Self::checked_production_event_sum(
668            "gpu_solver_production_path_events",
669            &[
670                self.gpu_cdcl_sat_solves,
671                self.gpu_cdcl_unsat_solves,
672                self.gpu_cdcl_workspace_unsat_solves,
673                self.gpu_learned_clause_arena_publications,
674                self.gpu_learned_count_buffer_publications,
675                self.gpu_learned_clause_imports,
676                self.gpu_learned_clause_reused_solves,
677                self.gpu_maxsat_candidate_solves,
678                self.gpu_maxsat_candidate_encodes,
679                self.gpu_maxsat_scheduler_candidate_set_jobs,
680                self.gpu_maxsat_scheduler_search_jobs,
681                self.gpu_maxsat_scheduler_encoded_search_jobs,
682                self.gpu_maxsat_unsat_candidate_prunes,
683                self.gpu_portfolio_sat_jobs,
684                self.gpu_portfolio_maxsat_jobs,
685            ],
686        )
687    }
688
689    fn checked_gpu_solver_status_path_events(&self) -> Result<u64> {
690        Self::checked_production_event_sum(
691            "gpu_solver_status_path_events",
692            &[
693                self.gpu_lifecycle_unknown_status_steps,
694                self.gpu_lifecycle_timeout_status_steps,
695                self.gpu_maxsat_scheduler_unknown_status_jobs,
696                self.gpu_maxsat_scheduler_timeout_status_jobs,
697                self.gpu_portfolio_unknown_status_jobs,
698                self.gpu_portfolio_timeout_status_jobs,
699            ],
700        )
701    }
702
703    fn accepted_path_event_snapshot(&self) -> Result<GpuSolverAcceptedPathEventSnapshot> {
704        Ok(GpuSolverAcceptedPathEventSnapshot {
705            production: self.checked_gpu_solver_production_path_events()?,
706            status: self.checked_gpu_solver_status_path_events()?,
707        })
708    }
709
710    fn checked_production_event_sum(counter: &str, values: &[u64]) -> Result<u64> {
711        values.iter().try_fold(0u64, |acc, value| {
712            acc.checked_add(*value)
713                .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
714                    construct: "GPU solver production trace accounting".to_string(),
715                    context: format!(
716                        "GPU solver production counter {counter} overflowed while adding \
717                         {value} to {acc}"
718                    ),
719                })
720        })
721    }
722
723    fn checked_maxsat_production_metric_events(&self) -> Result<u64> {
724        Self::checked_production_event_sum(
725            "gpu_solver_maxsat_metric_events",
726            &[
727                self.gpu_maxsat_candidate_solves,
728                self.gpu_maxsat_frontier_certified_candidate_solves,
729                self.gpu_maxsat_candidate_encodes,
730                self.gpu_maxsat_frontier_upper_bound_certificates,
731                self.gpu_maxsat_scheduler_candidate_set_jobs,
732                self.gpu_maxsat_scheduler_search_jobs,
733                self.gpu_maxsat_scheduler_encoded_search_jobs,
734                self.gpu_maxsat_unsat_candidate_prunes,
735                self.gpu_maxsat_optima,
736                self.gpu_portfolio_maxsat_jobs,
737            ],
738        )
739    }
740
741    fn checked_portfolio_job_kind_events(&self) -> Result<u64> {
742        Self::checked_production_event_sum(
743            "gpu_solver_portfolio_job_kind_events",
744            &[
745                self.gpu_portfolio_sat_jobs,
746                self.gpu_portfolio_maxsat_jobs,
747                self.gpu_portfolio_unknown_status_jobs,
748                self.gpu_portfolio_timeout_status_jobs,
749            ],
750        )
751    }
752
753    fn checked_maxsat_scheduler_job_kind_events(&self) -> Result<u64> {
754        Self::checked_production_event_sum(
755            "gpu_solver_maxsat_scheduler_job_kind_events",
756            &[
757                self.gpu_maxsat_scheduler_candidate_set_jobs,
758                self.gpu_maxsat_scheduler_search_jobs,
759                self.gpu_maxsat_scheduler_encoded_search_jobs,
760                self.gpu_maxsat_scheduler_unknown_status_jobs,
761                self.gpu_maxsat_scheduler_timeout_status_jobs,
762            ],
763        )
764    }
765
766    fn require_maxsat_scheduler_job_accounting(&self) -> Result<()> {
767        let job_kind_events = self.checked_maxsat_scheduler_job_kind_events()?;
768        if self.gpu_maxsat_scheduler_jobs != job_kind_events {
769            return Err(XlogError::UnsupportedEpistemicConstruct {
770                construct: "GPU solver production metric gate".to_string(),
771                context: format!(
772                    "MaxSAT scheduler job accounting must match aggregate jobs to job-kind/status counters, \
773                     got jobs={} candidate_set={} search={} encoded_search={} unknown={} timeout={}",
774                    self.gpu_maxsat_scheduler_jobs,
775                    self.gpu_maxsat_scheduler_candidate_set_jobs,
776                    self.gpu_maxsat_scheduler_search_jobs,
777                    self.gpu_maxsat_scheduler_encoded_search_jobs,
778                    self.gpu_maxsat_scheduler_unknown_status_jobs,
779                    self.gpu_maxsat_scheduler_timeout_status_jobs
780                ),
781            });
782        }
783        Ok(())
784    }
785
786    fn require_portfolio_job_accounting(&self) -> Result<()> {
787        let job_kind_events = self.checked_portfolio_job_kind_events()?;
788        if self.gpu_portfolio_jobs != job_kind_events {
789            return Err(XlogError::UnsupportedEpistemicConstruct {
790                construct: "GPU solver production metric gate".to_string(),
791                context: format!(
792                    "portfolio job accounting must match aggregate jobs to job-kind counters, \
793                     got jobs={} sat={} maxsat={} unknown={} timeout={}",
794                    self.gpu_portfolio_jobs,
795                    self.gpu_portfolio_sat_jobs,
796                    self.gpu_portfolio_maxsat_jobs,
797                    self.gpu_portfolio_unknown_status_jobs,
798                    self.gpu_portfolio_timeout_status_jobs
799                ),
800            });
801        }
802        Ok(())
803    }
804
805    fn require_encoded_maxsat_upload_transfer_accounting(&self) -> Result<()> {
806        if (self.gpu_maxsat_candidate_cnf_data_plane_htod_bytes != 0
807            && self.gpu_maxsat_candidate_cnf_data_plane_htod_calls == 0)
808            || (self.gpu_maxsat_candidate_cnf_launch_metadata_htod_bytes != 0
809                && self.gpu_maxsat_candidate_cnf_launch_metadata_htod_calls == 0)
810        {
811            return Err(XlogError::UnsupportedEpistemicConstruct {
812                construct: "GPU solver production metric gate".to_string(),
813                context: format!(
814                    "encoded MaxSAT candidate CNF upload bytes require matching host-to-device calls, \
815                     got data_plane_calls={} data_plane_bytes={} launch_metadata_calls={} \
816                     launch_metadata_bytes={}",
817                    self.gpu_maxsat_candidate_cnf_data_plane_htod_calls,
818                    self.gpu_maxsat_candidate_cnf_data_plane_htod_bytes,
819                    self.gpu_maxsat_candidate_cnf_launch_metadata_htod_calls,
820                    self.gpu_maxsat_candidate_cnf_launch_metadata_htod_bytes
821                ),
822            });
823        }
824        if (self.gpu_maxsat_candidate_cnf_data_plane_htod_calls != 0
825            && self.gpu_maxsat_candidate_cnf_data_plane_htod_bytes == 0)
826            || (self.gpu_maxsat_candidate_cnf_launch_metadata_htod_calls != 0
827                && self.gpu_maxsat_candidate_cnf_launch_metadata_htod_bytes == 0)
828        {
829            return Err(XlogError::UnsupportedEpistemicConstruct {
830                construct: "GPU solver production metric gate".to_string(),
831                context: format!(
832                    "encoded MaxSAT candidate CNF upload calls require matching host-to-device bytes, \
833                     got data_plane_calls={} data_plane_bytes={} launch_metadata_calls={} \
834                     launch_metadata_bytes={}",
835                    self.gpu_maxsat_candidate_cnf_data_plane_htod_calls,
836                    self.gpu_maxsat_candidate_cnf_data_plane_htod_bytes,
837                    self.gpu_maxsat_candidate_cnf_launch_metadata_htod_calls,
838                    self.gpu_maxsat_candidate_cnf_launch_metadata_htod_bytes
839                ),
840            });
841        }
842        Ok(())
843    }
844
845    /// Require internally consistent GPU tuple-membership evidence counters.
846    pub fn require_accepted_gpu_tuple_membership_trace(&self) -> Result<()> {
847        if self.accepted_nonzero_arity_gpu_candidate_evidence_consumed == 0
848            && self.accepted_gpu_candidate_tuple_key_column_reads_consumed != 0
849        {
850            return Err(XlogError::UnsupportedEpistemicConstruct {
851                construct: "GPU solver production metric gate".to_string(),
852                context: format!(
853                    "accepted tuple-key reads require accepted nonzero-arity GPU evidence, got \
854                     nonzero_evidence=0 tuple_key_reads={}",
855                    self.accepted_gpu_candidate_tuple_key_column_reads_consumed
856                ),
857            });
858        }
859        if self.accepted_nonzero_arity_gpu_candidate_evidence_consumed > 0
860            && self.accepted_gpu_candidate_tuple_key_column_reads_consumed == 0
861        {
862            return Err(XlogError::UnsupportedEpistemicConstruct {
863                construct: "GPU solver production metric gate".to_string(),
864                context: format!(
865                    "accepted nonzero-arity GPU solver evidence requires tuple-key device column \
866                     reads, got nonzero_evidence={} tuple_key_reads=0",
867                    self.accepted_nonzero_arity_gpu_candidate_evidence_consumed
868                ),
869            });
870        }
871        if self.accepted_gpu_final_tuple_negated_row_filters_consumed
872            > self.accepted_gpu_final_tuple_row_filters_consumed
873        {
874            return Err(XlogError::UnsupportedEpistemicConstruct {
875                construct: "GPU solver production metric gate".to_string(),
876                context: format!(
877                    "accepted negated final-tuple row filters cannot exceed total row filters: \
878                     negated={} total={}",
879                    self.accepted_gpu_final_tuple_negated_row_filters_consumed,
880                    self.accepted_gpu_final_tuple_row_filters_consumed
881                ),
882            });
883        }
884        if self.accepted_gpu_final_tuple_row_filters_consumed == 0
885            && (self.accepted_gpu_row_specific_membership_row_capacity_consumed != 0
886                || self.accepted_gpu_row_filter_fallback_row_capacity_consumed != 0)
887        {
888            return Err(XlogError::UnsupportedEpistemicConstruct {
889                construct: "GPU solver production metric gate".to_string(),
890                context: format!(
891                    "accepted row-specific/fallback tuple capacity requires accepted GPU row \
892                     filters, got row_filters=0 row_specific_capacity={} fallback_capacity={}",
893                    self.accepted_gpu_row_specific_membership_row_capacity_consumed,
894                    self.accepted_gpu_row_filter_fallback_row_capacity_consumed
895                ),
896            });
897        }
898        if self.accepted_gpu_final_tuple_row_filters_consumed > 0
899            && self.accepted_gpu_row_specific_membership_row_capacity_consumed == 0
900        {
901            return Err(XlogError::UnsupportedEpistemicConstruct {
902                construct: "GPU solver production metric gate".to_string(),
903                context: format!(
904                    "accepted GPU final-tuple row filters require row-specific model-slot \
905                     capacity, got row_filters={} row_specific_capacity=0",
906                    self.accepted_gpu_final_tuple_row_filters_consumed
907                ),
908            });
909        }
910        if self.accepted_gpu_constraint_row_count_device_reads_consumed
911            > self.accepted_gpu_constraint_relations_checked_consumed
912        {
913            return Err(XlogError::UnsupportedEpistemicConstruct {
914                construct: "GPU solver production metric gate".to_string(),
915                context: format!(
916                    "accepted constraint row-count device reads cannot exceed checked reduced \
917                     constraint relations, got reads={} checked={}",
918                    self.accepted_gpu_constraint_row_count_device_reads_consumed,
919                    self.accepted_gpu_constraint_relations_checked_consumed
920                ),
921            });
922        }
923        Ok(())
924    }
925
926    /// Require internally consistent accepted GPU solver evidence counters.
927    pub fn require_accepted_gpu_candidate_evidence_trace(&self) -> Result<()> {
928        let mode_count = self
929            .accepted_g91_gpu_candidate_evidence_consumed
930            .checked_add(self.accepted_faeel_gpu_candidate_evidence_consumed)
931            .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
932                construct: "GPU solver production metric gate".to_string(),
933                context: "accepted GPU solver mode counters overflowed".to_string(),
934            })?;
935        if self.accepted_gpu_candidate_evidence_consumed != 0
936            && mode_count != self.accepted_gpu_candidate_evidence_consumed
937        {
938            return Err(XlogError::UnsupportedEpistemicConstruct {
939                construct: "GPU solver production metric gate".to_string(),
940                context: format!(
941                    "accepted GPU solver evidence must be classified by epistemic mode, got \
942                     evidence={} g91={} faeel={}",
943                    self.accepted_gpu_candidate_evidence_consumed,
944                    self.accepted_g91_gpu_candidate_evidence_consumed,
945                    self.accepted_faeel_gpu_candidate_evidence_consumed
946                ),
947            });
948        }
949        if self.accepted_know_gpu_candidate_evidence_consumed
950            > self.accepted_gpu_candidate_evidence_consumed
951            || self.accepted_possible_gpu_candidate_evidence_consumed
952                > self.accepted_gpu_candidate_evidence_consumed
953            || self.accepted_not_possible_gpu_candidate_evidence_consumed
954                > self.accepted_gpu_candidate_evidence_consumed
955            || self.accepted_not_know_gpu_candidate_evidence_consumed
956                > self.accepted_gpu_candidate_evidence_consumed
957        {
958            return Err(XlogError::UnsupportedEpistemicConstruct {
959                construct: "GPU solver production metric gate".to_string(),
960                context: format!(
961                    "accepted GPU solver operator evidence counters cannot exceed accepted \
962                     evidence records, got evidence={} know={} possible={} not_possible={} \
963                     not_know={}",
964                    self.accepted_gpu_candidate_evidence_consumed,
965                    self.accepted_know_gpu_candidate_evidence_consumed,
966                    self.accepted_possible_gpu_candidate_evidence_consumed,
967                    self.accepted_not_possible_gpu_candidate_evidence_consumed,
968                    self.accepted_not_know_gpu_candidate_evidence_consumed
969                ),
970            });
971        }
972        if self.accepted_gpu_candidate_evidence_consumed != 0 {
973            if self.accepted_gpu_candidate_state_transitions
974                != self.accepted_gpu_world_view_state_transitions
975            {
976                return Err(XlogError::UnsupportedEpistemicConstruct {
977                    construct: "GPU solver production metric gate".to_string(),
978                    context: format!(
979                        "accepted GPU candidate/world-view state transitions must match, got \
980                         candidates={} world_views={}",
981                        self.accepted_gpu_candidate_state_transitions,
982                        self.accepted_gpu_world_view_state_transitions
983                    ),
984                });
985            }
986            if self.accepted_gpu_candidate_state_transitions == 0
987                || self.accepted_gpu_world_view_state_transitions == 0
988                || self.accepted_gpu_candidate_final_output_rows_consumed == 0
989            {
990                return Err(XlogError::UnsupportedEpistemicConstruct {
991                    construct: "GPU solver production metric gate".to_string(),
992                    context: format!(
993                        "accepted GPU solver detailed evidence requires accepted \
994                         candidate/world-view states and non-empty final output rows, got \
995                         evidence={} candidate_states={} world_view_states={} final_rows={}",
996                        self.accepted_gpu_candidate_evidence_consumed,
997                        self.accepted_gpu_candidate_state_transitions,
998                        self.accepted_gpu_world_view_state_transitions,
999                        self.accepted_gpu_candidate_final_output_rows_consumed
1000                    ),
1001                });
1002            }
1003            if self.accepted_solver_assumption_bindings_consumed
1004                < self.accepted_gpu_candidate_evidence_consumed
1005            {
1006                return Err(XlogError::UnsupportedEpistemicConstruct {
1007                    construct: "GPU solver production metric gate".to_string(),
1008                    context: format!(
1009                        "accepted GPU solver evidence requires planner-exported assumption \
1010                         bindings, got evidence={} assumption_bindings={}",
1011                        self.accepted_gpu_candidate_evidence_consumed,
1012                        self.accepted_solver_assumption_bindings_consumed
1013                    ),
1014                });
1015            }
1016            let required_capability_floor = self
1017                .accepted_gpu_candidate_evidence_consumed
1018                .checked_mul(PRODUCTION_SOLVER_REQUIRED_CAPABILITY_COUNT)
1019                .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
1020                    construct: "GPU solver production metric gate".to_string(),
1021                    context: "accepted GPU solver capability floor overflowed".to_string(),
1022                })?;
1023            let required_status_floor = self
1024                .accepted_gpu_candidate_evidence_consumed
1025                .checked_mul(PRODUCTION_SOLVER_REQUIRED_STATUS_COUNT)
1026                .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
1027                    construct: "GPU solver production metric gate".to_string(),
1028                    context: "accepted GPU solver status floor overflowed".to_string(),
1029                })?;
1030            if self.accepted_solver_required_capabilities_consumed < required_capability_floor
1031                || self.accepted_solver_required_statuses_consumed < required_status_floor
1032            {
1033                return Err(XlogError::UnsupportedEpistemicConstruct {
1034                    construct: "GPU solver production metric gate".to_string(),
1035                    context: format!(
1036                        "accepted GPU solver evidence requires the production \
1037                         capability/status contract, got evidence={} capabilities={} statuses={}",
1038                        self.accepted_gpu_candidate_evidence_consumed,
1039                        self.accepted_solver_required_capabilities_consumed,
1040                        self.accepted_solver_required_statuses_consumed
1041                    ),
1042                });
1043            }
1044            if self.accepted_gpu_candidate_state_transitions
1045                < self.accepted_gpu_candidate_evidence_consumed
1046                || self.accepted_gpu_world_view_state_transitions
1047                    < self.accepted_gpu_candidate_evidence_consumed
1048                || self.accepted_gpu_candidate_final_output_rows_consumed
1049                    < self.accepted_gpu_candidate_evidence_consumed
1050            {
1051                return Err(XlogError::UnsupportedEpistemicConstruct {
1052                    construct: "GPU solver production metric gate".to_string(),
1053                    context: format!(
1054                        "accepted GPU solver state counters must cover each accepted evidence \
1055                         record, got evidence={} candidate_states={} world_view_states={} \
1056                         final_rows={}",
1057                        self.accepted_gpu_candidate_evidence_consumed,
1058                        self.accepted_gpu_candidate_state_transitions,
1059                        self.accepted_gpu_world_view_state_transitions,
1060                        self.accepted_gpu_candidate_final_output_rows_consumed
1061                    ),
1062                });
1063            }
1064        }
1065        if self.accepted_nonzero_arity_gpu_candidate_evidence_consumed
1066            > self.accepted_gpu_candidate_evidence_consumed
1067        {
1068            return Err(XlogError::UnsupportedEpistemicConstruct {
1069                construct: "GPU solver production metric gate".to_string(),
1070                context: format!(
1071                    "accepted nonzero-arity GPU solver evidence cannot exceed accepted evidence \
1072                     records, got nonzero={} evidence={}",
1073                    self.accepted_nonzero_arity_gpu_candidate_evidence_consumed,
1074                    self.accepted_gpu_candidate_evidence_consumed
1075                ),
1076            });
1077        }
1078        if self.accepted_gpu_batch_candidate_component_evidence_consumed
1079            < self.accepted_gpu_batch_candidate_evidence_consumed
1080        {
1081            return Err(XlogError::UnsupportedEpistemicConstruct {
1082                construct: "GPU solver production metric gate".to_string(),
1083                context: format!(
1084                    "accepted GPU batch component evidence must cover accepted batch evidence, \
1085                     got batches={} components={}",
1086                    self.accepted_gpu_batch_candidate_evidence_consumed,
1087                    self.accepted_gpu_batch_candidate_component_evidence_consumed
1088                ),
1089            });
1090        }
1091        if self.accepted_gpu_batch_candidate_component_evidence_consumed
1092            > self.accepted_gpu_candidate_evidence_consumed
1093        {
1094            return Err(XlogError::UnsupportedEpistemicConstruct {
1095                construct: "GPU solver production metric gate".to_string(),
1096                context: format!(
1097                    "accepted GPU batch component evidence cannot exceed accepted candidate \
1098                     evidence, got components={} evidence={}",
1099                    self.accepted_gpu_batch_candidate_component_evidence_consumed,
1100                    self.accepted_gpu_candidate_evidence_consumed
1101                ),
1102            });
1103        }
1104        Ok(())
1105    }
1106
1107    /// Require that this trace is eligible for production solver metrics.
1108    ///
1109    /// This is an accepted-path containment gate, not a release-close claim:
1110    /// the CPU semantic-oracle facade may still exist for fixtures, but it
1111    /// cannot satisfy production metric evidence.
1112    pub fn require_production_metric_eligibility(&self) -> Result<()> {
1113        let capabilities = production_capabilities();
1114        match capabilities.production_metric_backend {
1115            GpuSolverProductionMetricBackend::GpuOnly => {}
1116        }
1117        if capabilities.gpu_cdcl_sat_unsat != GpuSolverProductionCapabilityStatus::Available {
1118            return Err(XlogError::UnsupportedEpistemicConstruct {
1119                construct: "GPU solver production metric gate".to_string(),
1120                context: "GPU CDCL SAT/UNSAT production capability is not available".to_string(),
1121            });
1122        }
1123        if capabilities.gpu_maxsat != GpuSolverProductionCapabilityStatus::Available {
1124            return Err(XlogError::UnsupportedEpistemicConstruct {
1125                construct: "GPU solver production metric gate".to_string(),
1126                context: capabilities.gpu_maxsat_blocker.to_string(),
1127            });
1128        }
1129        if capabilities.gpu_portfolio_sat_maxsat != GpuSolverProductionCapabilityStatus::Available {
1130            return Err(XlogError::UnsupportedEpistemicConstruct {
1131                construct: "GPU solver production metric gate".to_string(),
1132                context: capabilities.gpu_portfolio_blocker.to_string(),
1133            });
1134        }
1135        if self.accepted_gpu_candidate_evidence_consumed == 0 {
1136            return Err(XlogError::UnsupportedEpistemicConstruct {
1137                construct: "GPU solver production metric gate".to_string(),
1138                context: "production solver metrics require accepted GPU candidate evidence"
1139                    .to_string(),
1140            });
1141        }
1142        let gpu_solver_production_path_events = self.checked_gpu_solver_production_path_events()?;
1143        let gpu_solver_status_path_events = self.checked_gpu_solver_status_path_events()?;
1144        let gpu_solver_path_events = Self::checked_production_event_sum(
1145            "gpu_solver_path_events",
1146            &[
1147                gpu_solver_production_path_events,
1148                gpu_solver_status_path_events,
1149            ],
1150        )?;
1151        if gpu_solver_path_events == 0 {
1152            return Err(XlogError::UnsupportedEpistemicConstruct {
1153                construct: "GPU solver production metric gate".to_string(),
1154                context:
1155                    "production solver metrics require an existing GPU CDCL/MaxSAT/scheduler/portfolio/status counter"
1156                        .to_string(),
1157            });
1158        }
1159        if self.accepted_gpu_solver_production_path_events == 0 {
1160            return Err(XlogError::UnsupportedEpistemicConstruct {
1161                construct: "GPU solver production metric gate".to_string(),
1162                context: "production solver metrics require GPU solver production/status work inside an accepted epistemic evidence gate"
1163                    .to_string(),
1164            });
1165        }
1166        if self.accepted_gpu_solver_production_path_events > gpu_solver_path_events {
1167            return Err(XlogError::UnsupportedEpistemicConstruct {
1168                construct: "GPU solver production metric gate".to_string(),
1169                context: format!(
1170                    "accepted GPU solver production/status events cannot exceed total GPU solver production/status events: accepted={} total={}",
1171                    self.accepted_gpu_solver_production_path_events, gpu_solver_path_events
1172                ),
1173            });
1174        }
1175        if self.accepted_gpu_solver_production_path_events
1176            < self.accepted_gpu_candidate_state_transitions
1177        {
1178            return Err(XlogError::UnsupportedEpistemicConstruct {
1179                construct: "GPU solver production metric gate".to_string(),
1180                context: format!(
1181                    "accepted GPU solver production events must cover each accepted candidate \
1182                     state transition, got accepted_events={} candidate_states={}",
1183                    self.accepted_gpu_solver_production_path_events,
1184                    self.accepted_gpu_candidate_state_transitions
1185                ),
1186            });
1187        }
1188        self.require_maxsat_scheduler_job_accounting()?;
1189        self.require_portfolio_job_accounting()?;
1190        let maxsat_metric_events = self.checked_maxsat_production_metric_events()?;
1191        if maxsat_metric_events != 0 {
1192            if self.gpu_maxsat_candidate_solves == 0 {
1193                return Err(XlogError::UnsupportedEpistemicConstruct {
1194                    construct: "GPU solver production metric gate".to_string(),
1195                    context: "MaxSAT production metrics require GPU CDCL candidate solves"
1196                        .to_string(),
1197                });
1198            }
1199            if self.gpu_maxsat_frontier_certified_candidate_solves
1200                != self.gpu_maxsat_candidate_solves
1201            {
1202                return Err(XlogError::UnsupportedEpistemicConstruct {
1203                    construct: "GPU solver production metric gate".to_string(),
1204                    context: format!(
1205                        "MaxSAT production metrics require every candidate solve to be covered by \
1206                         an encoded weighted MaxSAT upper-bound certificate, got certified_solves={} \
1207                         candidate_solves={}",
1208                        self.gpu_maxsat_frontier_certified_candidate_solves,
1209                        self.gpu_maxsat_candidate_solves
1210                    ),
1211                });
1212            }
1213            if self.gpu_maxsat_candidate_encodes > self.gpu_maxsat_candidate_solves {
1214                return Err(XlogError::UnsupportedEpistemicConstruct {
1215                    construct: "GPU solver production metric gate".to_string(),
1216                    context: format!(
1217                        "encoded MaxSAT candidates cannot exceed GPU CDCL candidate solves, \
1218                         got encodes={} solves={}",
1219                        self.gpu_maxsat_candidate_encodes, self.gpu_maxsat_candidate_solves
1220                    ),
1221                });
1222            }
1223            if self.gpu_maxsat_unsat_candidate_prunes > self.gpu_maxsat_candidate_solves {
1224                return Err(XlogError::UnsupportedEpistemicConstruct {
1225                    construct: "GPU solver production metric gate".to_string(),
1226                    context: format!(
1227                        "MaxSAT UNSAT candidate prunes cannot exceed GPU CDCL candidate solves, \
1228                         got prunes={} solves={}",
1229                        self.gpu_maxsat_unsat_candidate_prunes, self.gpu_maxsat_candidate_solves
1230                    ),
1231                });
1232            }
1233            if self.gpu_maxsat_optima > self.gpu_maxsat_candidate_solves {
1234                return Err(XlogError::UnsupportedEpistemicConstruct {
1235                    construct: "GPU solver production metric gate".to_string(),
1236                    context: format!(
1237                        "MaxSAT optima cannot exceed GPU CDCL candidate solves, got optima={} \
1238                         solves={}",
1239                        self.gpu_maxsat_optima, self.gpu_maxsat_candidate_solves
1240                    ),
1241                });
1242            }
1243            let encoded_maxsat_metric_events = Self::checked_production_event_sum(
1244                "gpu_solver_encoded_maxsat_metric_events",
1245                &[
1246                    self.gpu_maxsat_frontier_certified_candidate_solves,
1247                    self.gpu_maxsat_candidate_encodes,
1248                    self.gpu_maxsat_frontier_upper_bound_certificates,
1249                    self.gpu_maxsat_frontier_completion_candidate_encodes,
1250                    self.gpu_maxsat_scheduler_encoded_search_jobs,
1251                ],
1252            )?;
1253            let encoded_maxsat_upload_metrics = self.gpu_maxsat_candidate_cnf_data_plane_htod_calls
1254                != 0
1255                || self.gpu_maxsat_candidate_cnf_data_plane_htod_bytes != 0
1256                || self.gpu_maxsat_candidate_cnf_data_plane_dtoh_calls != 0
1257                || self.gpu_maxsat_candidate_cnf_data_plane_dtoh_bytes != 0
1258                || self.gpu_maxsat_candidate_cnf_launch_metadata_htod_calls != 0
1259                || self.gpu_maxsat_candidate_cnf_launch_metadata_htod_bytes != 0;
1260            if encoded_maxsat_metric_events != 0 || encoded_maxsat_upload_metrics {
1261                if self.gpu_maxsat_frontier_upper_bound_certificates == 0 {
1262                    return Err(XlogError::UnsupportedEpistemicConstruct {
1263                        construct: "GPU solver production metric gate".to_string(),
1264                        context: "encoded MaxSAT production metrics require a weighted MaxSAT upper-bound certificate"
1265                            .to_string(),
1266                    });
1267                }
1268                if self.gpu_maxsat_candidate_encodes == 0 {
1269                    return Err(XlogError::UnsupportedEpistemicConstruct {
1270                        construct: "GPU solver production metric gate".to_string(),
1271                        context:
1272                            "MaxSAT upper-bound certificates require encoded weighted MaxSAT candidates"
1273                                .to_string(),
1274                    });
1275                }
1276                let max_data_plane_htod_calls = self
1277                    .gpu_maxsat_candidate_encodes
1278                    .checked_mul(2)
1279                    .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
1280                        construct: "GPU solver production metric gate".to_string(),
1281                        context:
1282                            "MaxSAT candidate data-plane host-to-device call budget overflowed"
1283                                .to_string(),
1284                    })?;
1285                let max_launch_metadata_htod_calls = self
1286                    .gpu_maxsat_candidate_encodes
1287                    .checked_mul(3)
1288                    .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
1289                        construct: "GPU solver production metric gate".to_string(),
1290                        context:
1291                            "MaxSAT candidate launch-metadata host-to-device call budget overflowed"
1292                                .to_string(),
1293                    })?;
1294                if self.gpu_maxsat_candidate_cnf_data_plane_htod_calls > max_data_plane_htod_calls
1295                    || self.gpu_maxsat_candidate_cnf_launch_metadata_htod_calls
1296                        > max_launch_metadata_htod_calls
1297                {
1298                    return Err(XlogError::UnsupportedEpistemicConstruct {
1299                        construct: "GPU solver production metric gate".to_string(),
1300                        context: format!(
1301                            "encoded MaxSAT candidate CNF uploads exceeded bounded host-to-device call budget, \
1302                             encodes={} data_plane_calls={}/{} launch_metadata_calls={}/{}",
1303                            self.gpu_maxsat_candidate_encodes,
1304                            self.gpu_maxsat_candidate_cnf_data_plane_htod_calls,
1305                            max_data_plane_htod_calls,
1306                            self.gpu_maxsat_candidate_cnf_launch_metadata_htod_calls,
1307                            max_launch_metadata_htod_calls
1308                        ),
1309                    });
1310                }
1311                self.require_encoded_maxsat_upload_transfer_accounting()?;
1312                if self.gpu_maxsat_candidate_cnf_data_plane_dtoh_calls != 0
1313                    || self.gpu_maxsat_candidate_cnf_data_plane_dtoh_bytes != 0
1314                {
1315                    return Err(XlogError::UnsupportedEpistemicConstruct {
1316                        construct: "GPU solver production metric gate".to_string(),
1317                        context: format!(
1318                            "encoded MaxSAT candidate CNF uploads must not perform data-plane device-to-host \
1319                             transfers, got calls={} bytes={}",
1320                            self.gpu_maxsat_candidate_cnf_data_plane_dtoh_calls,
1321                            self.gpu_maxsat_candidate_cnf_data_plane_dtoh_bytes
1322                        ),
1323                    });
1324                }
1325                if self.gpu_maxsat_frontier_completion_candidate_encodes
1326                    > self.gpu_maxsat_candidate_encodes
1327                {
1328                    return Err(XlogError::UnsupportedEpistemicConstruct {
1329                        construct: "GPU solver production metric gate".to_string(),
1330                        context: format!(
1331                            "MaxSAT frontier completion candidates cannot exceed encoded candidates, \
1332                             got completion_encodes={} total_encodes={}",
1333                            self.gpu_maxsat_frontier_completion_candidate_encodes,
1334                            self.gpu_maxsat_candidate_encodes
1335                        ),
1336                    });
1337                }
1338                if self.gpu_maxsat_frontier_certified_candidate_solves
1339                    != self.gpu_maxsat_candidate_encodes
1340                {
1341                    return Err(XlogError::UnsupportedEpistemicConstruct {
1342                        construct: "GPU solver production metric gate".to_string(),
1343                        context: format!(
1344                            "encoded MaxSAT production metrics require every encoded candidate solve \
1345                             to be covered by an upper-bound-certified frontier, got certified_solves={} \
1346                             encoded_candidates={}",
1347                            self.gpu_maxsat_frontier_certified_candidate_solves,
1348                            self.gpu_maxsat_candidate_encodes
1349                        ),
1350                    });
1351                }
1352                if self.gpu_maxsat_frontier_upper_bound_certificates
1353                    < self.gpu_maxsat_scheduler_encoded_search_jobs
1354                {
1355                    return Err(XlogError::UnsupportedEpistemicConstruct {
1356                        construct: "GPU solver production metric gate".to_string(),
1357                        context: format!(
1358                            "encoded MaxSAT scheduler jobs require one upper-bound certificate per job, got certificates={} encoded_jobs={}",
1359                            self.gpu_maxsat_frontier_upper_bound_certificates,
1360                            self.gpu_maxsat_scheduler_encoded_search_jobs
1361                        ),
1362                    });
1363                }
1364            }
1365            if self.gpu_maxsat_optima == 0 {
1366                return Err(XlogError::UnsupportedEpistemicConstruct {
1367                    construct: "GPU solver production metric gate".to_string(),
1368                    context: "MaxSAT production metrics require a GPU-certified optimum"
1369                        .to_string(),
1370                });
1371            }
1372        }
1373        self.require_accepted_gpu_candidate_evidence_trace()?;
1374        self.require_accepted_gpu_tuple_membership_trace()
1375    }
1376}
1377
1378/// Thin adapter from epistemic solver work to the existing GPU CDCL verifier.
1379pub struct GpuSolverProductionAdapter {
1380    provider: Arc<CudaKernelProvider>,
1381    solver: GpuCdclSolver,
1382    trace: GpuSolverProductionTrace,
1383}
1384
1385impl GpuSolverProductionAdapter {
1386    /// Create an adapter over the existing GPU CDCL solver implementation.
1387    pub fn new(provider: Arc<CudaKernelProvider>, config: GpuCdclConfig) -> Self {
1388        Self {
1389            solver: GpuCdclSolver::new(Arc::clone(&provider), config),
1390            provider,
1391            trace: GpuSolverProductionTrace::default(),
1392        }
1393    }
1394
1395    /// Return the current production-path trace counters.
1396    pub fn trace(&self) -> GpuSolverProductionTrace {
1397        self.trace
1398    }
1399
1400    fn checked_trace_counter_add(current: u64, delta: u64, counter: &str) -> Result<u64> {
1401        current
1402            .checked_add(delta)
1403            .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
1404                construct: "GPU solver production trace accounting".to_string(),
1405                context: format!(
1406                    "accepted GPU solver trace counter {counter} overflowed while adding {delta} \
1407                     to {current}"
1408                ),
1409            })
1410    }
1411
1412    fn checked_report_counter_add(current: u64, delta: u64, counter: &str) -> Result<u64> {
1413        current
1414            .checked_add(delta)
1415            .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
1416                construct: "GPU solver production report accounting".to_string(),
1417                context: format!(
1418                    "accepted GPU solver report counter {counter} overflowed while adding {delta} \
1419                     to {current}"
1420                ),
1421            })
1422    }
1423
1424    fn checked_report_counter_delta(current: u64, before: u64, counter: &str) -> Result<u64> {
1425        current
1426            .checked_sub(before)
1427            .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
1428                construct: "GPU solver production report accounting".to_string(),
1429                context: format!(
1430                    "accepted GPU solver report counter {counter} decreased from {before} to \
1431                     {current}"
1432                ),
1433            })
1434    }
1435
1436    fn checked_workspace_clause_cap(weighted: &SolveInstance) -> Result<u32> {
1437        u32::try_from(weighted.clauses.len()).map_err(|_| {
1438            XlogError::UnsupportedEpistemicConstruct {
1439                construct: "GPU solver production MaxSAT encoding".to_string(),
1440                context: format!(
1441                    "weighted MaxSAT clause count {} exceeds GPU CDCL workspace capacity range",
1442                    weighted.clauses.len()
1443                ),
1444            }
1445        })
1446    }
1447
1448    fn require_adapter_provider_identity(&self, provider: &CudaKernelProvider) -> Result<()> {
1449        let adapter_identity = EpistemicGpuProviderIdentity::from_provider(&self.provider);
1450        let evidence_identity = EpistemicGpuProviderIdentity::from_provider(provider);
1451        if adapter_identity != evidence_identity {
1452            return Err(XlogError::UnsupportedEpistemicConstruct {
1453                construct: "accepted GPU solver candidate evidence".to_string(),
1454                context: format!(
1455                    "solver adapter provider mismatch: adapter device={} evidence device={} \
1456                     adapter_device_ptr={} evidence_device_ptr={} adapter_memory_ptr={} \
1457                     evidence_memory_ptr={}",
1458                    adapter_identity.device_ordinal,
1459                    evidence_identity.device_ordinal,
1460                    adapter_identity.device_ptr,
1461                    evidence_identity.device_ptr,
1462                    adapter_identity.memory_ptr,
1463                    evidence_identity.memory_ptr
1464                ),
1465            });
1466        }
1467        Ok(())
1468    }
1469
1470    fn require_accepted_gpu_solver_evidence(
1471        &self,
1472        provider: &CudaKernelProvider,
1473        result: &EpistemicGpuExecutionResult,
1474    ) -> Result<GpuSolverAcceptedCandidateState> {
1475        self.require_adapter_provider_identity(provider)?;
1476        require_accepted_gpu_solver_evidence(provider, result)
1477    }
1478
1479    fn require_accepted_gpu_solver_states(
1480        &self,
1481        provider: &CudaKernelProvider,
1482        results: &[&EpistemicGpuExecutionResult],
1483    ) -> Result<Vec<GpuSolverAcceptedCandidateState>> {
1484        self.require_adapter_provider_identity(provider)?;
1485        require_accepted_gpu_solver_states(provider, results)
1486    }
1487
1488    fn require_branch_var_limit_on_adapter_provider(
1489        &self,
1490        branch_var_limit: &TrackedCudaSlice<u32>,
1491        construct: &'static str,
1492    ) -> Result<()> {
1493        if branch_var_limit.len() != 1 {
1494            return Err(XlogError::UnsupportedEpistemicConstruct {
1495                construct: construct.to_string(),
1496                context: format!(
1497                    "solver lifecycle branch_var_limit must have len=1, got {}",
1498                    branch_var_limit.len()
1499                ),
1500            });
1501        }
1502        let expected_memory =
1503            EpistemicGpuProviderIdentity::from_provider(&self.provider).memory_ptr;
1504        let actual_memory = branch_var_limit.memory_manager_ptr_value();
1505        if actual_memory != expected_memory {
1506            return Err(XlogError::UnsupportedEpistemicConstruct {
1507                construct: construct.to_string(),
1508                context: format!(
1509                    "solver lifecycle branch_var_limit belongs to memory manager {actual_memory}, expected {expected_memory}"
1510                ),
1511            });
1512        }
1513        Ok(())
1514    }
1515
1516    fn require_solver_artifact_on_adapter_provider(
1517        &self,
1518        cnf: &GpuCnf,
1519        branch_var_limit: &TrackedCudaSlice<u32>,
1520        construct: &'static str,
1521    ) -> Result<()> {
1522        cnf.require_provider_memory(&self.provider, construct)?;
1523        self.require_branch_var_limit_on_adapter_provider(branch_var_limit, construct)
1524    }
1525
1526    fn require_cnf_on_adapter_provider(&self, cnf: &GpuCnf, construct: &'static str) -> Result<()> {
1527        cnf.require_provider_memory(&self.provider, construct)
1528    }
1529
1530    fn require_workspace_capacity_for_cnf(
1531        &self,
1532        workspace: &GpuCdclWorkspace,
1533        cnf: &GpuCnf,
1534        construct: &'static str,
1535    ) -> Result<()> {
1536        self.solver
1537            .require_workspace_capacity_for_cnf(workspace, cnf.var_cap, cnf.clause_cap)
1538            .map_err(|err| XlogError::UnsupportedEpistemicConstruct {
1539                construct: construct.to_string(),
1540                context: format!("GPU CDCL workspace capacity rejected solver artifact: {err}"),
1541            })
1542    }
1543
1544    fn require_workspace_capacity_for_weighted_maxsat_encoding(
1545        &self,
1546        workspace: &GpuCdclWorkspace,
1547        weighted: &SolveInstance,
1548        construct: &'static str,
1549    ) -> Result<()> {
1550        self.solver
1551            .require_workspace_capacity_for_cnf(
1552                workspace,
1553                weighted.num_vars,
1554                Self::checked_workspace_clause_cap(weighted)?,
1555            )
1556            .map_err(|err| XlogError::UnsupportedEpistemicConstruct {
1557                construct: construct.to_string(),
1558                context: format!("GPU CDCL workspace capacity rejected MaxSAT encoding: {err}"),
1559            })
1560    }
1561
1562    fn require_assumption_lifecycle_step_artifacts(
1563        &self,
1564        workspace: &GpuCdclWorkspace,
1565        steps: &[GpuSolverProductionLifecycleStep<'_>],
1566    ) -> Result<()> {
1567        for step in steps {
1568            self.require_solver_artifact_on_adapter_provider(
1569                step.cnf,
1570                step.branch_var_limit,
1571                "GPU solver production lifecycle",
1572            )?;
1573            if matches!(step.expectation, GpuSolverProductionExpectation::Unsat) {
1574                self.require_workspace_capacity_for_cnf(
1575                    workspace,
1576                    step.cnf,
1577                    "GPU solver production lifecycle",
1578                )?;
1579            }
1580        }
1581        Ok(())
1582    }
1583
1584    fn require_weighted_maxsat_candidate_artifacts(
1585        &self,
1586        candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
1587    ) -> Result<()> {
1588        for candidate in candidates {
1589            self.require_solver_artifact_on_adapter_provider(
1590                candidate.cnf,
1591                candidate.branch_var_limit,
1592                "GPU solver production MaxSAT",
1593            )?;
1594        }
1595        Ok(())
1596    }
1597
1598    fn require_weighted_maxsat_candidates_and_artifacts(
1599        &self,
1600        candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
1601    ) -> Result<()> {
1602        Self::require_weighted_maxsat_candidates(candidates)?;
1603        self.require_weighted_maxsat_candidate_artifacts(candidates)
1604    }
1605
1606    fn require_maxsat_lifecycle_artifacts(
1607        &self,
1608        workspace: &GpuCdclWorkspace,
1609        steps: &[GpuSolverProductionLifecycleStep<'_>],
1610        candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
1611    ) -> Result<()> {
1612        self.require_assumption_lifecycle_step_artifacts(workspace, steps)?;
1613        self.require_weighted_maxsat_candidate_artifacts(candidates)
1614    }
1615
1616    fn require_weighted_maxsat_search_candidate_artifacts(
1617        &self,
1618        candidates: &[GpuSolverProductionMaxSatSearchCandidate<'_>],
1619    ) -> Result<()> {
1620        for candidate in candidates {
1621            self.require_solver_artifact_on_adapter_provider(
1622                candidate.cnf,
1623                candidate.branch_var_limit,
1624                "GPU solver production MaxSAT search",
1625            )?;
1626        }
1627        Ok(())
1628    }
1629
1630    fn require_weighted_maxsat_search_candidates_and_artifacts(
1631        &self,
1632        workspace: &GpuCdclWorkspace,
1633        candidates: &[GpuSolverProductionMaxSatSearchCandidate<'_>],
1634    ) -> Result<()> {
1635        Self::require_weighted_maxsat_search_candidates(candidates)?;
1636        self.require_weighted_maxsat_search_candidate_artifacts(candidates)?;
1637        for candidate in candidates {
1638            if matches!(
1639                candidate.status,
1640                GpuSolverProductionMaxSatSearchStatus::Unsatisfiable
1641            ) {
1642                self.require_workspace_capacity_for_cnf(
1643                    workspace,
1644                    candidate.cnf,
1645                    "GPU solver production MaxSAT search",
1646                )?;
1647            }
1648        }
1649        Ok(())
1650    }
1651
1652    fn require_learned_clause_publication_artifacts(
1653        &self,
1654        workspace: &GpuCdclWorkspace,
1655        cnf: &GpuCnf,
1656        branch_var_limit: &TrackedCudaSlice<u32>,
1657    ) -> Result<()> {
1658        self.require_solver_artifact_on_adapter_provider(
1659            cnf,
1660            branch_var_limit,
1661            "GPU solver learned-clause arena",
1662        )?;
1663        self.require_workspace_capacity_for_cnf(workspace, cnf, "GPU solver learned-clause arena")
1664    }
1665
1666    fn require_learned_clause_reuse_artifacts(
1667        &self,
1668        source_cnf: &GpuCnf,
1669        source_branch_var_limit: &TrackedCudaSlice<u32>,
1670        target_cnf: &GpuCnf,
1671        target_branch_var_limit: &TrackedCudaSlice<u32>,
1672    ) -> Result<()> {
1673        self.require_solver_artifact_on_adapter_provider(
1674            source_cnf,
1675            source_branch_var_limit,
1676            "GPU solver learned-clause reuse",
1677        )?;
1678        self.require_solver_artifact_on_adapter_provider(
1679            target_cnf,
1680            target_branch_var_limit,
1681            "GPU solver learned-clause reuse",
1682        )
1683    }
1684
1685    fn require_learned_clause_reuse_inputs(
1686        &mut self,
1687        workspace: &GpuCdclWorkspace,
1688        source_cnf: &GpuCnf,
1689        source_branch_var_limit: &TrackedCudaSlice<u32>,
1690        target_cnf: &GpuCnf,
1691        target_branch_var_limit: &TrackedCudaSlice<u32>,
1692    ) -> Result<()> {
1693        self.require_learned_clause_reuse_artifacts(
1694            source_cnf,
1695            source_branch_var_limit,
1696            target_cnf,
1697            target_branch_var_limit,
1698        )?;
1699        if let Err(err) = require_same_gpu_cnf_for_learned_clause_reuse(source_cnf, target_cnf) {
1700            checked_solver_trace_counter_inc!(self, gpu_learned_clause_reuse_rejections);
1701            return Err(err);
1702        }
1703        self.require_workspace_capacity_for_cnf(
1704            workspace,
1705            source_cnf,
1706            "GPU solver learned-clause reuse",
1707        )?;
1708        self.require_workspace_capacity_for_cnf(
1709            workspace,
1710            target_cnf,
1711            "GPU solver learned-clause reuse",
1712        )?;
1713        Ok(())
1714    }
1715
1716    fn require_weighted_maxsat_encoded_search_inputs_and_artifacts(
1717        &self,
1718        workspace: &GpuCdclWorkspace,
1719        weighted: &SolveInstance,
1720        branch_var_limit: &TrackedCudaSlice<u32>,
1721        selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
1722    ) -> Result<()> {
1723        Self::require_weighted_maxsat_encoding_inputs(weighted, selections)?;
1724        self.require_workspace_capacity_for_weighted_maxsat_encoding(
1725            workspace,
1726            weighted,
1727            "GPU solver production MaxSAT encoding",
1728        )?;
1729        self.require_branch_var_limit_on_adapter_provider(
1730            branch_var_limit,
1731            "GPU solver production MaxSAT encoding",
1732        )
1733    }
1734
1735    /// Validate accepted GPU epistemic evidence and return the solver-facing candidate state.
1736    pub fn accepted_candidate_state(
1737        &self,
1738        provider: &CudaKernelProvider,
1739        result: &EpistemicGpuExecutionResult,
1740    ) -> Result<GpuSolverAcceptedCandidateState> {
1741        self.require_accepted_gpu_solver_evidence(provider, result)
1742    }
1743
1744    fn accepted_solver_results_from_gpu_batch_execution_evidence<'a>(
1745        &mut self,
1746        provider: &CudaKernelProvider,
1747        evidence: GpuSolverProductionBatchExecutionEvidence<'a>,
1748    ) -> Result<Vec<&'a EpistemicGpuExecutionResult>> {
1749        self.require_adapter_provider_identity(provider)?;
1750        require_accepted_gpu_solver_batch_evidence(provider, evidence.batch)
1751    }
1752
1753    fn record_accepted_gpu_batch_candidate_evidence(
1754        &mut self,
1755        component_count: usize,
1756    ) -> Result<()> {
1757        self.trace.accepted_gpu_batch_candidate_evidence_consumed =
1758            Self::checked_trace_counter_add(
1759                self.trace.accepted_gpu_batch_candidate_evidence_consumed,
1760                1,
1761                "accepted_gpu_batch_candidate_evidence_consumed",
1762            )?;
1763        self.trace
1764            .accepted_gpu_batch_candidate_component_evidence_consumed =
1765            Self::checked_trace_counter_add(
1766                self.trace
1767                    .accepted_gpu_batch_candidate_component_evidence_consumed,
1768                component_count as u64,
1769                "accepted_gpu_batch_candidate_component_evidence_consumed",
1770            )?;
1771        Ok(())
1772    }
1773
1774    fn with_trace_rollback<T>(&mut self, action: impl FnOnce(&mut Self) -> Result<T>) -> Result<T> {
1775        let trace_before = self.trace;
1776        match action(self) {
1777            Ok(value) => Ok(value),
1778            Err(err) => {
1779                self.trace = trace_before;
1780                Err(err)
1781            }
1782        }
1783    }
1784
1785    fn with_trace_rollback_preserving_reuse_rejections<T>(
1786        &mut self,
1787        action: impl FnOnce(&mut Self) -> Result<T>,
1788    ) -> Result<T> {
1789        let trace_before = self.trace;
1790        match action(self) {
1791            Ok(value) => Ok(value),
1792            Err(err) => {
1793                let reuse_rejections_after = self.trace.gpu_learned_clause_reuse_rejections;
1794                let reuse_rejections_before = trace_before.gpu_learned_clause_reuse_rejections;
1795                self.trace = trace_before;
1796                let reuse_rejection_delta = Self::checked_report_counter_delta(
1797                    reuse_rejections_after,
1798                    reuse_rejections_before,
1799                    "gpu_learned_clause_reuse_rejections",
1800                )?;
1801                self.trace.gpu_learned_clause_reuse_rejections = Self::checked_trace_counter_add(
1802                    self.trace.gpu_learned_clause_reuse_rejections,
1803                    reuse_rejection_delta,
1804                    "gpu_learned_clause_reuse_rejections",
1805                )?;
1806                Err(err)
1807            }
1808        }
1809    }
1810
1811    /// Allocate a reusable GPU CDCL workspace through the existing solver.
1812    pub fn new_workspace(&self, max_var_cap: u32, max_clause_cap: u32) -> Result<GpuCdclWorkspace> {
1813        self.solver.new_workspace(max_var_cap, max_clause_cap)
1814    }
1815
1816    fn require_workspace_on_adapter_provider(&self, workspace: &GpuCdclWorkspace) -> Result<()> {
1817        self.solver.require_workspace_on_provider(workspace)
1818    }
1819
1820    fn record_accepted_gpu_candidate_state(
1821        &mut self,
1822        state: &GpuSolverAcceptedCandidateState,
1823    ) -> Result<()> {
1824        self.trace.accepted_gpu_candidate_evidence_consumed = Self::checked_trace_counter_add(
1825            self.trace.accepted_gpu_candidate_evidence_consumed,
1826            state.evidence_records,
1827            "accepted_gpu_candidate_evidence_consumed",
1828        )?;
1829        self.trace.accepted_gpu_candidate_state_transitions = Self::checked_trace_counter_add(
1830            self.trace.accepted_gpu_candidate_state_transitions,
1831            state.accepted_candidates,
1832            "accepted_gpu_candidate_state_transitions",
1833        )?;
1834        self.trace.accepted_gpu_world_view_state_transitions = Self::checked_trace_counter_add(
1835            self.trace.accepted_gpu_world_view_state_transitions,
1836            state.accepted_world_views,
1837            "accepted_gpu_world_view_state_transitions",
1838        )?;
1839        self.trace.accepted_gpu_candidate_final_output_rows_consumed =
1840            Self::checked_trace_counter_add(
1841                self.trace.accepted_gpu_candidate_final_output_rows_consumed,
1842                state.final_output_rows,
1843                "accepted_gpu_candidate_final_output_rows_consumed",
1844            )?;
1845        if state.g91_mode {
1846            self.trace.accepted_g91_gpu_candidate_evidence_consumed =
1847                Self::checked_trace_counter_add(
1848                    self.trace.accepted_g91_gpu_candidate_evidence_consumed,
1849                    1,
1850                    "accepted_g91_gpu_candidate_evidence_consumed",
1851                )?;
1852        }
1853        if state.faeel_mode {
1854            self.trace.accepted_faeel_gpu_candidate_evidence_consumed =
1855                Self::checked_trace_counter_add(
1856                    self.trace.accepted_faeel_gpu_candidate_evidence_consumed,
1857                    1,
1858                    "accepted_faeel_gpu_candidate_evidence_consumed",
1859                )?;
1860        }
1861        if state.has_know_operator {
1862            self.trace.accepted_know_gpu_candidate_evidence_consumed =
1863                Self::checked_trace_counter_add(
1864                    self.trace.accepted_know_gpu_candidate_evidence_consumed,
1865                    1,
1866                    "accepted_know_gpu_candidate_evidence_consumed",
1867                )?;
1868        }
1869        if state.has_possible_operator {
1870            self.trace.accepted_possible_gpu_candidate_evidence_consumed =
1871                Self::checked_trace_counter_add(
1872                    self.trace.accepted_possible_gpu_candidate_evidence_consumed,
1873                    1,
1874                    "accepted_possible_gpu_candidate_evidence_consumed",
1875                )?;
1876        }
1877        if state.has_not_possible_operator {
1878            self.trace
1879                .accepted_not_possible_gpu_candidate_evidence_consumed =
1880                Self::checked_trace_counter_add(
1881                    self.trace
1882                        .accepted_not_possible_gpu_candidate_evidence_consumed,
1883                    1,
1884                    "accepted_not_possible_gpu_candidate_evidence_consumed",
1885                )?;
1886        }
1887        if state.has_not_know_operator {
1888            self.trace.accepted_not_know_gpu_candidate_evidence_consumed =
1889                Self::checked_trace_counter_add(
1890                    self.trace.accepted_not_know_gpu_candidate_evidence_consumed,
1891                    1,
1892                    "accepted_not_know_gpu_candidate_evidence_consumed",
1893                )?;
1894        }
1895        if state.has_nonzero_arity_tuple_keys {
1896            self.trace
1897                .accepted_nonzero_arity_gpu_candidate_evidence_consumed =
1898                Self::checked_trace_counter_add(
1899                    self.trace
1900                        .accepted_nonzero_arity_gpu_candidate_evidence_consumed,
1901                    1,
1902                    "accepted_nonzero_arity_gpu_candidate_evidence_consumed",
1903                )?;
1904        }
1905        self.trace
1906            .accepted_gpu_candidate_tuple_key_column_reads_consumed =
1907            Self::checked_trace_counter_add(
1908                self.trace
1909                    .accepted_gpu_candidate_tuple_key_column_reads_consumed,
1910                state.tuple_key_column_reads,
1911                "accepted_gpu_candidate_tuple_key_column_reads_consumed",
1912            )?;
1913        self.trace.accepted_solver_assumption_bindings_consumed = Self::checked_trace_counter_add(
1914            self.trace.accepted_solver_assumption_bindings_consumed,
1915            state.solver_assumption_bindings,
1916            "accepted_solver_assumption_bindings_consumed",
1917        )?;
1918        self.trace.accepted_solver_required_capabilities_consumed =
1919            Self::checked_trace_counter_add(
1920                self.trace.accepted_solver_required_capabilities_consumed,
1921                state.solver_required_capabilities,
1922                "accepted_solver_required_capabilities_consumed",
1923            )?;
1924        self.trace.accepted_solver_required_statuses_consumed = Self::checked_trace_counter_add(
1925            self.trace.accepted_solver_required_statuses_consumed,
1926            state.solver_required_statuses,
1927            "accepted_solver_required_statuses_consumed",
1928        )?;
1929        self.trace.accepted_gpu_final_tuple_row_filters_consumed = Self::checked_trace_counter_add(
1930            self.trace.accepted_gpu_final_tuple_row_filters_consumed,
1931            state.final_tuple_row_filters,
1932            "accepted_gpu_final_tuple_row_filters_consumed",
1933        )?;
1934        self.trace
1935            .accepted_gpu_final_tuple_negated_row_filters_consumed =
1936            Self::checked_trace_counter_add(
1937                self.trace
1938                    .accepted_gpu_final_tuple_negated_row_filters_consumed,
1939                state.final_tuple_negated_row_filters,
1940                "accepted_gpu_final_tuple_negated_row_filters_consumed",
1941            )?;
1942        self.trace
1943            .accepted_gpu_row_specific_membership_row_capacity_consumed =
1944            Self::checked_trace_counter_add(
1945                self.trace
1946                    .accepted_gpu_row_specific_membership_row_capacity_consumed,
1947                state.row_specific_membership_row_capacity,
1948                "accepted_gpu_row_specific_membership_row_capacity_consumed",
1949            )?;
1950        self.trace
1951            .accepted_gpu_row_filter_fallback_row_capacity_consumed =
1952            Self::checked_trace_counter_add(
1953                self.trace
1954                    .accepted_gpu_row_filter_fallback_row_capacity_consumed,
1955                state.row_filter_fallback_row_capacity,
1956                "accepted_gpu_row_filter_fallback_row_capacity_consumed",
1957            )?;
1958        self.trace
1959            .accepted_gpu_constraint_relations_checked_consumed = Self::checked_trace_counter_add(
1960            self.trace
1961                .accepted_gpu_constraint_relations_checked_consumed,
1962            state.checked_constraint_relations,
1963            "accepted_gpu_constraint_relations_checked_consumed",
1964        )?;
1965        self.trace
1966            .accepted_gpu_constraint_row_count_device_reads_consumed =
1967            Self::checked_trace_counter_add(
1968                self.trace
1969                    .accepted_gpu_constraint_row_count_device_reads_consumed,
1970                state.constraint_row_count_device_reads,
1971                "accepted_gpu_constraint_row_count_device_reads_consumed",
1972            )?;
1973        Ok(())
1974    }
1975
1976    fn record_accepted_gpu_solver_production_path_events_since(
1977        &mut self,
1978        events_before: GpuSolverAcceptedPathEventSnapshot,
1979        state: &GpuSolverAcceptedCandidateState,
1980    ) -> Result<()> {
1981        let events_after = self.trace.accepted_path_event_snapshot()?;
1982        let production_delta = events_after
1983            .production
1984            .checked_sub(events_before.production)
1985            .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
1986                construct: "GPU solver production trace accounting".to_string(),
1987                context: format!(
1988                    "accepted GPU solver production events decreased from {} to {}",
1989                    events_before.production, events_after.production
1990                ),
1991            })?;
1992        let status_delta = events_after
1993            .status
1994            .checked_sub(events_before.status)
1995            .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
1996                construct: "GPU solver production trace accounting".to_string(),
1997                context: format!(
1998                    "accepted GPU solver status events decreased from {} to {}",
1999                    events_before.status, events_after.status
2000                ),
2001            })?;
2002        let accepted_delta = Self::checked_report_counter_add(
2003            production_delta,
2004            status_delta,
2005            "accepted_gpu_solver_path_events",
2006        )?;
2007        if accepted_delta < state.accepted_candidates {
2008            return Err(XlogError::UnsupportedEpistemicConstruct {
2009                construct: "GPU solver production trace accounting".to_string(),
2010                context: format!(
2011                    "accepted GPU solver production/status work must cover every accepted \
2012                     candidate state before evidence is recorded, got production_events={} \
2013                     status_events={} candidate_states={}",
2014                    production_delta, status_delta, state.accepted_candidates
2015                ),
2016            });
2017        }
2018        self.trace.accepted_gpu_solver_production_path_events = Self::checked_trace_counter_add(
2019            self.trace.accepted_gpu_solver_production_path_events,
2020            accepted_delta,
2021            "accepted_gpu_solver_production_path_events",
2022        )?;
2023        Ok(())
2024    }
2025
2026    /// Solve and enforce SAT entirely on GPU.
2027    pub fn solve_expect_sat(&mut self, cnf: &GpuCnf) -> Result<TrackedCudaSlice<i8>> {
2028        self.require_cnf_on_adapter_provider(cnf, "GPU solver production SAT")?;
2029        let assignment = self.solver.solve_expect_sat(cnf)?;
2030        checked_solver_trace_counter_inc!(self, gpu_cdcl_sat_solves);
2031        Ok(assignment)
2032    }
2033
2034    /// Solve SAT through GPU CDCL after an accepted GPU epistemic execution result.
2035    pub fn solve_expect_sat_with_gpu_execution_result(
2036        &mut self,
2037        provider: &CudaKernelProvider,
2038        result: &EpistemicGpuExecutionResult,
2039        cnf: &GpuCnf,
2040    ) -> Result<TrackedCudaSlice<i8>> {
2041        self.with_trace_rollback(|this| {
2042            this.require_cnf_on_adapter_provider(cnf, "GPU solver production SAT")?;
2043            let state = this.require_accepted_gpu_solver_evidence(provider, result)?;
2044            let events_before = this.trace.accepted_path_event_snapshot()?;
2045            let assignment = this.solve_expect_sat(cnf)?;
2046            this.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
2047            this.record_accepted_gpu_candidate_state(&state)?;
2048            Ok(assignment)
2049        })
2050    }
2051
2052    /// Solve UNSAT through GPU CDCL after an accepted GPU epistemic execution result.
2053    pub fn solve_expect_unsat_with_gpu_execution_result(
2054        &mut self,
2055        provider: &CudaKernelProvider,
2056        result: &EpistemicGpuExecutionResult,
2057        cnf: &GpuCnf,
2058    ) -> Result<()> {
2059        self.with_trace_rollback(|this| {
2060            this.require_cnf_on_adapter_provider(cnf, "GPU solver production UNSAT")?;
2061            let state = this.require_accepted_gpu_solver_evidence(provider, result)?;
2062            let events_before = this.trace.accepted_path_event_snapshot()?;
2063            this.solve_expect_unsat(cnf)?;
2064            this.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
2065            this.record_accepted_gpu_candidate_state(&state)?;
2066            Ok(())
2067        })
2068    }
2069
2070    /// Solve and enforce UNSAT entirely on GPU.
2071    pub fn solve_expect_unsat(&mut self, cnf: &GpuCnf) -> Result<()> {
2072        self.require_cnf_on_adapter_provider(cnf, "GPU solver production UNSAT")?;
2073        self.solver.solve_expect_unsat(cnf)?;
2074        checked_solver_trace_counter_inc!(self, gpu_cdcl_unsat_solves);
2075        Ok(())
2076    }
2077
2078    /// Solve and enforce UNSAT entirely on GPU using a reusable workspace.
2079    pub fn solve_expect_unsat_with_branch_limit_ws(
2080        &mut self,
2081        workspace: &mut GpuCdclWorkspace,
2082        cnf: &GpuCnf,
2083        branch_var_limit: &TrackedCudaSlice<u32>,
2084    ) -> Result<()> {
2085        self.require_workspace_on_adapter_provider(workspace)?;
2086        self.require_solver_artifact_on_adapter_provider(
2087            cnf,
2088            branch_var_limit,
2089            "GPU solver production workspace UNSAT",
2090        )?;
2091        self.require_workspace_capacity_for_cnf(
2092            workspace,
2093            cnf,
2094            "GPU solver production workspace UNSAT",
2095        )?;
2096        self.solver
2097            .solve_expect_unsat_with_branch_limit_ws(workspace, cnf, branch_var_limit)?;
2098        checked_solver_trace_counter_inc!(self, gpu_cdcl_workspace_unsat_solves);
2099        Ok(())
2100    }
2101
2102    /// Solve workspace-backed UNSAT through GPU CDCL after accepted GPU epistemic execution.
2103    pub fn solve_expect_unsat_with_branch_limit_ws_with_gpu_execution_result(
2104        &mut self,
2105        provider: &CudaKernelProvider,
2106        result: &EpistemicGpuExecutionResult,
2107        workspace: &mut GpuCdclWorkspace,
2108        cnf: &GpuCnf,
2109        branch_var_limit: &TrackedCudaSlice<u32>,
2110    ) -> Result<()> {
2111        self.with_trace_rollback(|this| {
2112            this.require_workspace_on_adapter_provider(workspace)?;
2113            this.require_solver_artifact_on_adapter_provider(
2114                cnf,
2115                branch_var_limit,
2116                "GPU solver production workspace UNSAT",
2117            )?;
2118            this.require_workspace_capacity_for_cnf(
2119                workspace,
2120                cnf,
2121                "GPU solver production workspace UNSAT",
2122            )?;
2123            let state = this.require_accepted_gpu_solver_evidence(provider, result)?;
2124            let events_before = this.trace.accepted_path_event_snapshot()?;
2125            this.solve_expect_unsat_with_branch_limit_ws(workspace, cnf, branch_var_limit)?;
2126            this.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
2127            this.record_accepted_gpu_candidate_state(&state)?;
2128            Ok(())
2129        })
2130    }
2131
2132    fn solve_assumption_lifecycle_steps(
2133        &mut self,
2134        workspace: &mut GpuCdclWorkspace,
2135        steps: &[GpuSolverProductionLifecycleStep<'_>],
2136    ) -> Result<GpuSolverProductionLifecycleReport> {
2137        self.with_trace_rollback(|this| {
2138            this.solve_assumption_lifecycle_steps_impl(workspace, steps)
2139        })
2140    }
2141
2142    fn solve_assumption_lifecycle_steps_impl(
2143        &mut self,
2144        workspace: &mut GpuCdclWorkspace,
2145        steps: &[GpuSolverProductionLifecycleStep<'_>],
2146    ) -> Result<GpuSolverProductionLifecycleReport> {
2147        Self::require_assumption_lifecycle_steps(steps)?;
2148        self.require_workspace_on_adapter_provider(workspace)?;
2149
2150        let pushes_before = self.trace.gpu_assumption_pushes;
2151        let retractions_before = self.trace.gpu_assumption_retractions;
2152        let workspace_reuses_before = self.trace.gpu_lifecycle_workspace_reuses;
2153        let unknown_steps_before = self.trace.gpu_lifecycle_unknown_status_steps;
2154        let timeout_steps_before = self.trace.gpu_lifecycle_timeout_status_steps;
2155        let mut sat_steps = 0u64;
2156        let mut unsat_steps = 0u64;
2157
2158        self.require_assumption_lifecycle_step_artifacts(workspace, steps)?;
2159
2160        for step in steps {
2161            checked_solver_trace_counter_inc!(self, gpu_assumption_pushes);
2162            match step.expectation {
2163                GpuSolverProductionExpectation::Sat => {
2164                    self.solver
2165                        .solve_expect_sat_with_branch_limit(step.cnf, step.branch_var_limit)?;
2166                    checked_solver_trace_counter_inc!(self, gpu_cdcl_sat_solves);
2167                    sat_steps =
2168                        Self::checked_trace_counter_add(sat_steps, 1, "lifecycle_sat_steps")?;
2169                }
2170                GpuSolverProductionExpectation::Unsat => {
2171                    let assign_ptr_before = workspace.assign_device_ptr();
2172                    self.solve_expect_unsat_with_branch_limit_ws(
2173                        workspace,
2174                        step.cnf,
2175                        step.branch_var_limit,
2176                    )?;
2177                    if workspace.assign_device_ptr() == assign_ptr_before {
2178                        checked_solver_trace_counter_inc!(self, gpu_lifecycle_workspace_reuses);
2179                    }
2180                    unsat_steps =
2181                        Self::checked_trace_counter_add(unsat_steps, 1, "lifecycle_unsat_steps")?;
2182                }
2183                GpuSolverProductionExpectation::Unknown { .. } => {
2184                    checked_solver_trace_counter_inc!(self, gpu_lifecycle_unknown_status_steps);
2185                }
2186                GpuSolverProductionExpectation::Timeout { .. } => {
2187                    checked_solver_trace_counter_inc!(self, gpu_lifecycle_timeout_status_steps);
2188                }
2189            };
2190            checked_solver_trace_counter_inc!(self, gpu_assumption_retractions);
2191        }
2192
2193        let assumption_pushes = Self::checked_report_counter_delta(
2194            self.trace.gpu_assumption_pushes,
2195            pushes_before,
2196            "gpu_assumption_pushes",
2197        )?;
2198        let assumption_retractions = Self::checked_report_counter_delta(
2199            self.trace.gpu_assumption_retractions,
2200            retractions_before,
2201            "gpu_assumption_retractions",
2202        )?;
2203        if assumption_pushes != assumption_retractions {
2204            return Err(XlogError::UnsupportedEpistemicConstruct {
2205                construct: "GPU solver production lifecycle".to_string(),
2206                context: format!(
2207                    "assumption push/retract mismatch: pushes={} retractions={}",
2208                    assumption_pushes, assumption_retractions
2209                ),
2210            });
2211        }
2212        let unknown_steps = Self::checked_report_counter_delta(
2213            self.trace.gpu_lifecycle_unknown_status_steps,
2214            unknown_steps_before,
2215            "gpu_lifecycle_unknown_status_steps",
2216        )?;
2217        let timeout_steps = Self::checked_report_counter_delta(
2218            self.trace.gpu_lifecycle_timeout_status_steps,
2219            timeout_steps_before,
2220            "gpu_lifecycle_timeout_status_steps",
2221        )?;
2222        let accounted_sat_unsat =
2223            Self::checked_report_counter_add(sat_steps, unsat_steps, "lifecycle_status_steps")?;
2224        let accounted_known_unknown = Self::checked_report_counter_add(
2225            accounted_sat_unsat,
2226            unknown_steps,
2227            "lifecycle_status_steps",
2228        )?;
2229        let accounted_status_steps = Self::checked_report_counter_add(
2230            accounted_known_unknown,
2231            timeout_steps,
2232            "lifecycle_status_steps",
2233        )?;
2234        if accounted_status_steps != steps.len() as u64 {
2235            return Err(XlogError::UnsupportedEpistemicConstruct {
2236                construct: "GPU solver production lifecycle".to_string(),
2237                context: format!(
2238                    "lifecycle status accounting mismatch: sat={}, unsat={}, unknown={}, \
2239                     timeout={}, steps={}",
2240                    sat_steps,
2241                    unsat_steps,
2242                    unknown_steps,
2243                    timeout_steps,
2244                    steps.len()
2245                ),
2246            });
2247        }
2248
2249        Ok(GpuSolverProductionLifecycleReport {
2250            candidate_evidence_records: 0,
2251            steps: steps.len() as u64,
2252            sat_steps,
2253            unsat_steps,
2254            assumption_pushes,
2255            assumption_retractions,
2256            workspace_reuses: Self::checked_report_counter_delta(
2257                self.trace.gpu_lifecycle_workspace_reuses,
2258                workspace_reuses_before,
2259                "gpu_lifecycle_workspace_reuses",
2260            )?,
2261            unknown_steps,
2262            timeout_steps,
2263        })
2264    }
2265
2266    fn require_assumption_lifecycle_steps(
2267        steps: &[GpuSolverProductionLifecycleStep<'_>],
2268    ) -> Result<()> {
2269        if steps.is_empty() {
2270            return Err(XlogError::UnsupportedEpistemicConstruct {
2271                construct: "GPU solver production lifecycle".to_string(),
2272                context: "accepted solver lifecycle requires at least one step".to_string(),
2273            });
2274        }
2275
2276        for step in steps {
2277            match step.expectation {
2278                GpuSolverProductionExpectation::Unknown { reason } => {
2279                    if reason.trim().is_empty() {
2280                        return Err(XlogError::UnsupportedEpistemicConstruct {
2281                            construct: "GPU solver production lifecycle".to_string(),
2282                            context: "UNKNOWN lifecycle status requires a diagnostic reason"
2283                                .to_string(),
2284                        });
2285                    }
2286                }
2287                GpuSolverProductionExpectation::Timeout { budget_micros } => {
2288                    if budget_micros == 0 {
2289                        return Err(XlogError::UnsupportedEpistemicConstruct {
2290                            construct: "GPU solver production lifecycle".to_string(),
2291                            context: "TIMEOUT lifecycle status requires a nonzero budget"
2292                                .to_string(),
2293                        });
2294                    }
2295                }
2296                GpuSolverProductionExpectation::Sat | GpuSolverProductionExpectation::Unsat => {}
2297            }
2298        }
2299        Ok(())
2300    }
2301
2302    /// Execute an accepted push/solve/retract lifecycle through existing GPU CDCL calls.
2303    pub fn solve_assumption_lifecycle_with_gpu_execution_result(
2304        &mut self,
2305        provider: &CudaKernelProvider,
2306        result: &EpistemicGpuExecutionResult,
2307        workspace: &mut GpuCdclWorkspace,
2308        steps: &[GpuSolverProductionLifecycleStep<'_>],
2309    ) -> Result<GpuSolverProductionLifecycleReport> {
2310        self.with_trace_rollback(|this| {
2311            Self::require_assumption_lifecycle_steps(steps)?;
2312            this.require_workspace_on_adapter_provider(workspace)?;
2313            this.require_assumption_lifecycle_step_artifacts(workspace, steps)?;
2314            let state = this.require_accepted_gpu_solver_evidence(provider, result)?;
2315            let events_before = this.trace.accepted_path_event_snapshot()?;
2316            let mut report = this.solve_assumption_lifecycle_steps(workspace, steps)?;
2317            report.candidate_evidence_records = 1;
2318            this.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
2319            this.record_accepted_gpu_candidate_state(&state)?;
2320            Ok(report)
2321        })
2322    }
2323
2324    /// Execute accepted push/solve/retract lifecycles for multiple GPU epistemic candidates.
2325    ///
2326    /// Each candidate result is validated against the accepted GPU execution boundary, then
2327    /// the same lifecycle steps are dispatched through the existing GPU CDCL SAT/UNSAT paths.
2328    pub fn solve_multi_candidate_assumption_lifecycle_with_gpu_execution_results(
2329        &mut self,
2330        provider: &CudaKernelProvider,
2331        results: &[&EpistemicGpuExecutionResult],
2332        workspace: &mut GpuCdclWorkspace,
2333        steps: &[GpuSolverProductionLifecycleStep<'_>],
2334    ) -> Result<GpuSolverProductionLifecycleReport> {
2335        self.with_trace_rollback(|this| {
2336            this.solve_multi_candidate_assumption_lifecycle_with_gpu_execution_results_impl(
2337                provider, results, workspace, steps,
2338            )
2339        })
2340    }
2341
2342    fn solve_multi_candidate_assumption_lifecycle_with_gpu_execution_results_impl(
2343        &mut self,
2344        provider: &CudaKernelProvider,
2345        results: &[&EpistemicGpuExecutionResult],
2346        workspace: &mut GpuCdclWorkspace,
2347        steps: &[GpuSolverProductionLifecycleStep<'_>],
2348    ) -> Result<GpuSolverProductionLifecycleReport> {
2349        if results.is_empty() {
2350            return Err(XlogError::UnsupportedEpistemicConstruct {
2351                construct: "GPU solver production lifecycle".to_string(),
2352                context:
2353                    "multi-candidate solver lifecycle requires at least one accepted GPU result"
2354                        .to_string(),
2355            });
2356        }
2357        Self::require_assumption_lifecycle_steps(steps)?;
2358        self.require_workspace_on_adapter_provider(workspace)?;
2359        self.require_assumption_lifecycle_step_artifacts(workspace, steps)?;
2360
2361        let states = self.require_accepted_gpu_solver_states(provider, results)?;
2362
2363        let mut report = GpuSolverProductionLifecycleReport::default();
2364        for state in &states {
2365            let events_before = self.trace.accepted_path_event_snapshot()?;
2366            let step_report = self.solve_assumption_lifecycle_steps(workspace, steps)?;
2367            checked_solver_report_counter_inc!(report, candidate_evidence_records);
2368            checked_solver_report_counter_add!(report, steps, step_report.steps);
2369            checked_solver_report_counter_add!(report, sat_steps, step_report.sat_steps);
2370            checked_solver_report_counter_add!(report, unsat_steps, step_report.unsat_steps);
2371            checked_solver_report_counter_add!(
2372                report,
2373                assumption_pushes,
2374                step_report.assumption_pushes
2375            );
2376            checked_solver_report_counter_add!(
2377                report,
2378                assumption_retractions,
2379                step_report.assumption_retractions
2380            );
2381            checked_solver_report_counter_add!(
2382                report,
2383                workspace_reuses,
2384                step_report.workspace_reuses
2385            );
2386            checked_solver_report_counter_add!(report, unknown_steps, step_report.unknown_steps);
2387            checked_solver_report_counter_add!(report, timeout_steps, step_report.timeout_steps);
2388            self.record_accepted_gpu_solver_production_path_events_since(events_before, state)?;
2389            self.record_accepted_gpu_candidate_state(state)?;
2390        }
2391
2392        Ok(report)
2393    }
2394
2395    /// Execute accepted split/batch push/solve/retract lifecycles through existing GPU CDCL calls.
2396    ///
2397    /// The batch evidence must carry the typed `Gpu`/`RejectUnsupported` policy for every
2398    /// split component, plus observed GPU dispatch and candidate accounting, scoped transfer
2399    /// accounting, and aggregate CUDA-event timing.
2400    pub fn solve_assumption_lifecycle_with_gpu_batch_execution_result(
2401        &mut self,
2402        provider: &CudaKernelProvider,
2403        evidence: GpuSolverProductionBatchExecutionEvidence<'_>,
2404        workspace: &mut GpuCdclWorkspace,
2405        steps: &[GpuSolverProductionLifecycleStep<'_>],
2406    ) -> Result<GpuSolverProductionLifecycleReport> {
2407        self.with_trace_rollback(|this| {
2408            Self::require_assumption_lifecycle_steps(steps)?;
2409            this.require_workspace_on_adapter_provider(workspace)?;
2410            this.require_assumption_lifecycle_step_artifacts(workspace, steps)?;
2411            let results =
2412                this.accepted_solver_results_from_gpu_batch_execution_evidence(provider, evidence)?;
2413            let report = this
2414                .solve_multi_candidate_assumption_lifecycle_with_gpu_execution_results(
2415                    provider, &results, workspace, steps,
2416                )?;
2417            this.record_accepted_gpu_batch_candidate_evidence(results.len())?;
2418            Ok(report)
2419        })
2420    }
2421
2422    /// Populate and publish the existing GPU CDCL learned-clause/proof arena.
2423    ///
2424    /// This records that an accepted epistemic candidate reached the GPU CDCL
2425    /// learned-clause device buffers. Import/reuse is covered by the bounded
2426    /// same-device-CNF reuse API below.
2427    pub fn solve_unsat_and_publish_learned_clause_arena_with_gpu_execution_result(
2428        &mut self,
2429        provider: &CudaKernelProvider,
2430        result: &EpistemicGpuExecutionResult,
2431        workspace: &mut GpuCdclWorkspace,
2432        cnf: &GpuCnf,
2433        branch_var_limit: &TrackedCudaSlice<u32>,
2434    ) -> Result<GpuSolverProductionLearnedClauseArenaReport> {
2435        self.with_trace_rollback(|this| {
2436            this.solve_unsat_and_publish_learned_clause_arena_with_gpu_execution_result_impl(
2437                provider,
2438                result,
2439                workspace,
2440                cnf,
2441                branch_var_limit,
2442            )
2443        })
2444    }
2445
2446    fn solve_unsat_and_publish_learned_clause_arena_with_gpu_execution_result_impl(
2447        &mut self,
2448        provider: &CudaKernelProvider,
2449        result: &EpistemicGpuExecutionResult,
2450        workspace: &mut GpuCdclWorkspace,
2451        cnf: &GpuCnf,
2452        branch_var_limit: &TrackedCudaSlice<u32>,
2453    ) -> Result<GpuSolverProductionLearnedClauseArenaReport> {
2454        self.require_workspace_on_adapter_provider(workspace)?;
2455        self.require_learned_clause_publication_artifacts(workspace, cnf, branch_var_limit)?;
2456        let state = self.require_accepted_gpu_solver_evidence(provider, result)?;
2457        let events_before = self.trace.accepted_path_event_snapshot()?;
2458
2459        let learned_offsets_ptr = workspace.learned_offsets.device_ptr_value();
2460        let learned_lits_ptr = workspace.learned_lits.device_ptr_value();
2461        let proof_offsets_ptr = workspace.proof_offsets.device_ptr_value();
2462        let proof_data_ptr = workspace.proof_data.device_ptr_value();
2463        let learned_count_ptr = workspace.out_learned_count.device_ptr_value();
2464
2465        self.solve_expect_unsat_with_branch_limit_ws(workspace, cnf, branch_var_limit)?;
2466
2467        if learned_offsets_ptr == 0
2468            || learned_lits_ptr == 0
2469            || proof_offsets_ptr == 0
2470            || proof_data_ptr == 0
2471            || learned_count_ptr == 0
2472        {
2473            return Err(XlogError::UnsupportedEpistemicConstruct {
2474                construct: "GPU solver learned-clause arena".to_string(),
2475                context: "learned-clause publication requires non-null GPU arena buffers"
2476                    .to_string(),
2477            });
2478        }
2479        if workspace.learned_offsets.device_ptr_value() != learned_offsets_ptr
2480            || workspace.learned_lits.device_ptr_value() != learned_lits_ptr
2481            || workspace.proof_offsets.device_ptr_value() != proof_offsets_ptr
2482            || workspace.proof_data.device_ptr_value() != proof_data_ptr
2483            || workspace.out_learned_count.device_ptr_value() != learned_count_ptr
2484        {
2485            return Err(XlogError::UnsupportedEpistemicConstruct {
2486                construct: "GPU solver learned-clause arena".to_string(),
2487                context: "learned-clause publication must keep the reusable GPU workspace arena"
2488                    .to_string(),
2489            });
2490        }
2491
2492        checked_solver_trace_counter_inc!(self, gpu_learned_clause_arena_publications);
2493        checked_solver_trace_counter_inc!(self, gpu_learned_count_buffer_publications);
2494        self.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
2495        self.record_accepted_gpu_candidate_state(&state)?;
2496
2497        Ok(GpuSolverProductionLearnedClauseArenaReport {
2498            unsat_solves: 1,
2499            gpu_learned_clause_arena_publications: 1,
2500            gpu_learned_count_buffer_publications: 1,
2501        })
2502    }
2503
2504    fn solve_unsat_then_reuse_learned_clauses(
2505        &mut self,
2506        workspace: &mut GpuCdclWorkspace,
2507        source_cnf: &GpuCnf,
2508        source_branch_var_limit: &TrackedCudaSlice<u32>,
2509        target_cnf: &GpuCnf,
2510        target_branch_var_limit: &TrackedCudaSlice<u32>,
2511    ) -> Result<GpuSolverProductionLearnedClauseReuseReport> {
2512        self.solve_unsat_then_reuse_learned_clauses_impl(
2513            workspace,
2514            source_cnf,
2515            source_branch_var_limit,
2516            target_cnf,
2517            target_branch_var_limit,
2518        )
2519    }
2520
2521    fn solve_unsat_then_reuse_learned_clauses_impl(
2522        &mut self,
2523        workspace: &mut GpuCdclWorkspace,
2524        source_cnf: &GpuCnf,
2525        source_branch_var_limit: &TrackedCudaSlice<u32>,
2526        target_cnf: &GpuCnf,
2527        target_branch_var_limit: &TrackedCudaSlice<u32>,
2528    ) -> Result<GpuSolverProductionLearnedClauseReuseReport> {
2529        self.require_workspace_on_adapter_provider(workspace)?;
2530        self.require_learned_clause_reuse_inputs(
2531            workspace,
2532            source_cnf,
2533            source_branch_var_limit,
2534            target_cnf,
2535            target_branch_var_limit,
2536        )?;
2537
2538        let learned_offsets_ptr = workspace.learned_offsets.device_ptr_value();
2539        let learned_lits_ptr = workspace.learned_lits.device_ptr_value();
2540        let proof_offsets_ptr = workspace.proof_offsets.device_ptr_value();
2541        let proof_data_ptr = workspace.proof_data.device_ptr_value();
2542        let learned_count_ptr = workspace.out_learned_count.device_ptr_value();
2543        if learned_offsets_ptr == 0
2544            || learned_lits_ptr == 0
2545            || proof_offsets_ptr == 0
2546            || proof_data_ptr == 0
2547            || learned_count_ptr == 0
2548        {
2549            return Err(XlogError::UnsupportedEpistemicConstruct {
2550                construct: "GPU solver learned-clause reuse".to_string(),
2551                context: "learned-clause reuse requires non-null GPU arena buffers".to_string(),
2552            });
2553        }
2554
2555        self.solve_expect_unsat_with_branch_limit_ws(
2556            workspace,
2557            source_cnf,
2558            source_branch_var_limit,
2559        )?;
2560        require_stable_learned_clause_arena(
2561            "publication",
2562            workspace,
2563            learned_offsets_ptr,
2564            learned_lits_ptr,
2565            proof_offsets_ptr,
2566            proof_data_ptr,
2567            learned_count_ptr,
2568        )?;
2569
2570        checked_solver_trace_counter_inc!(self, gpu_learned_clause_arena_publications);
2571        checked_solver_trace_counter_inc!(self, gpu_learned_count_buffer_publications);
2572
2573        self.solver
2574            .solve_expect_unsat_with_branch_limit_ws_importing_learned(
2575                workspace,
2576                target_cnf,
2577                target_branch_var_limit,
2578            )?;
2579        checked_solver_trace_counter_inc!(self, gpu_cdcl_workspace_unsat_solves);
2580        require_stable_learned_clause_arena(
2581            "import",
2582            workspace,
2583            learned_offsets_ptr,
2584            learned_lits_ptr,
2585            proof_offsets_ptr,
2586            proof_data_ptr,
2587            learned_count_ptr,
2588        )?;
2589
2590        checked_solver_trace_counter_inc!(self, gpu_learned_clause_imports);
2591        checked_solver_trace_counter_inc!(self, gpu_learned_clause_reused_solves);
2592
2593        Ok(GpuSolverProductionLearnedClauseReuseReport {
2594            candidate_evidence_records: 0,
2595            candidates: 2,
2596            unsat_solves: 2,
2597            gpu_learned_clause_arena_publications: 1,
2598            gpu_learned_clause_imports: 1,
2599            gpu_learned_clause_reused_solves: 1,
2600        })
2601    }
2602
2603    /// Publish learned clauses from one accepted GPU UNSAT solve and import them into another.
2604    ///
2605    /// This is deliberately bounded to same-device-CNF reuse. The existing GPU proof trace is
2606    /// valid for the imported solve only when the base CNF buffers are the same.
2607    #[allow(clippy::too_many_arguments)]
2608    pub fn solve_unsat_then_reuse_learned_clauses_with_gpu_execution_result(
2609        &mut self,
2610        provider: &CudaKernelProvider,
2611        result: &EpistemicGpuExecutionResult,
2612        workspace: &mut GpuCdclWorkspace,
2613        source_cnf: &GpuCnf,
2614        source_branch_var_limit: &TrackedCudaSlice<u32>,
2615        target_cnf: &GpuCnf,
2616        target_branch_var_limit: &TrackedCudaSlice<u32>,
2617    ) -> Result<GpuSolverProductionLearnedClauseReuseReport> {
2618        self.with_trace_rollback_preserving_reuse_rejections(|this| {
2619            this.require_workspace_on_adapter_provider(workspace)?;
2620            this.require_learned_clause_reuse_inputs(
2621                workspace,
2622                source_cnf,
2623                source_branch_var_limit,
2624                target_cnf,
2625                target_branch_var_limit,
2626            )?;
2627            let state = this.require_accepted_gpu_solver_evidence(provider, result)?;
2628            let events_before = this.trace.accepted_path_event_snapshot()?;
2629            let mut report = this.solve_unsat_then_reuse_learned_clauses(
2630                workspace,
2631                source_cnf,
2632                source_branch_var_limit,
2633                target_cnf,
2634                target_branch_var_limit,
2635            )?;
2636            report.candidate_evidence_records = 1;
2637            this.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
2638            this.record_accepted_gpu_candidate_state(&state)?;
2639            Ok(report)
2640        })
2641    }
2642
2643    /// Publish and reuse learned clauses once per accepted GPU epistemic candidate.
2644    #[allow(clippy::too_many_arguments)]
2645    pub fn solve_multi_candidate_learned_clause_reuse_with_gpu_execution_results(
2646        &mut self,
2647        provider: &CudaKernelProvider,
2648        results: &[&EpistemicGpuExecutionResult],
2649        workspace: &mut GpuCdclWorkspace,
2650        source_cnf: &GpuCnf,
2651        source_branch_var_limit: &TrackedCudaSlice<u32>,
2652        target_cnf: &GpuCnf,
2653        target_branch_var_limit: &TrackedCudaSlice<u32>,
2654    ) -> Result<GpuSolverProductionLearnedClauseReuseReport> {
2655        self.with_trace_rollback_preserving_reuse_rejections(|this| {
2656            this.solve_multi_candidate_learned_clause_reuse_with_gpu_execution_results_impl(
2657                provider,
2658                results,
2659                workspace,
2660                source_cnf,
2661                source_branch_var_limit,
2662                target_cnf,
2663                target_branch_var_limit,
2664            )
2665        })
2666    }
2667
2668    #[allow(clippy::too_many_arguments)]
2669    fn solve_multi_candidate_learned_clause_reuse_with_gpu_execution_results_impl(
2670        &mut self,
2671        provider: &CudaKernelProvider,
2672        results: &[&EpistemicGpuExecutionResult],
2673        workspace: &mut GpuCdclWorkspace,
2674        source_cnf: &GpuCnf,
2675        source_branch_var_limit: &TrackedCudaSlice<u32>,
2676        target_cnf: &GpuCnf,
2677        target_branch_var_limit: &TrackedCudaSlice<u32>,
2678    ) -> Result<GpuSolverProductionLearnedClauseReuseReport> {
2679        if results.is_empty() {
2680            return Err(XlogError::UnsupportedEpistemicConstruct {
2681                construct: "GPU solver learned-clause reuse".to_string(),
2682                context:
2683                    "multi-candidate learned-clause reuse requires at least one accepted GPU result"
2684                        .to_string(),
2685            });
2686        }
2687        self.require_workspace_on_adapter_provider(workspace)?;
2688        self.require_learned_clause_reuse_inputs(
2689            workspace,
2690            source_cnf,
2691            source_branch_var_limit,
2692            target_cnf,
2693            target_branch_var_limit,
2694        )?;
2695        let states = self.require_accepted_gpu_solver_states(provider, results)?;
2696
2697        let mut report = GpuSolverProductionLearnedClauseReuseReport::default();
2698        for state in &states {
2699            let events_before = self.trace.accepted_path_event_snapshot()?;
2700            let step_report = self.solve_unsat_then_reuse_learned_clauses(
2701                workspace,
2702                source_cnf,
2703                source_branch_var_limit,
2704                target_cnf,
2705                target_branch_var_limit,
2706            )?;
2707            checked_solver_report_counter_inc!(report, candidate_evidence_records);
2708            checked_solver_report_counter_add!(report, candidates, step_report.candidates);
2709            checked_solver_report_counter_add!(report, unsat_solves, step_report.unsat_solves);
2710            checked_solver_report_counter_add!(
2711                report,
2712                gpu_learned_clause_arena_publications,
2713                step_report.gpu_learned_clause_arena_publications
2714            );
2715            checked_solver_report_counter_add!(
2716                report,
2717                gpu_learned_clause_imports,
2718                step_report.gpu_learned_clause_imports
2719            );
2720            checked_solver_report_counter_add!(
2721                report,
2722                gpu_learned_clause_reused_solves,
2723                step_report.gpu_learned_clause_reused_solves
2724            );
2725            self.record_accepted_gpu_solver_production_path_events_since(events_before, state)?;
2726            self.record_accepted_gpu_candidate_state(state)?;
2727        }
2728
2729        Ok(report)
2730    }
2731
2732    /// Publish and reuse learned clauses once per accepted split/batch GPU component.
2733    ///
2734    /// The batch evidence must prove every split component reused the existing
2735    /// single-plan GPU runtime path before each component is delegated to the
2736    /// existing multi-candidate learned-clause reuse adapter.
2737    #[allow(clippy::too_many_arguments)]
2738    pub fn solve_learned_clause_reuse_with_gpu_batch_execution_result(
2739        &mut self,
2740        provider: &CudaKernelProvider,
2741        evidence: GpuSolverProductionBatchExecutionEvidence<'_>,
2742        workspace: &mut GpuCdclWorkspace,
2743        source_cnf: &GpuCnf,
2744        source_branch_var_limit: &TrackedCudaSlice<u32>,
2745        target_cnf: &GpuCnf,
2746        target_branch_var_limit: &TrackedCudaSlice<u32>,
2747    ) -> Result<GpuSolverProductionLearnedClauseReuseReport> {
2748        self.with_trace_rollback_preserving_reuse_rejections(|this| {
2749            this.require_workspace_on_adapter_provider(workspace)?;
2750            this.require_learned_clause_reuse_inputs(
2751                workspace,
2752                source_cnf,
2753                source_branch_var_limit,
2754                target_cnf,
2755                target_branch_var_limit,
2756            )?;
2757            let results =
2758                this.accepted_solver_results_from_gpu_batch_execution_evidence(provider, evidence)?;
2759            let report = this
2760                .solve_multi_candidate_learned_clause_reuse_with_gpu_execution_results(
2761                    provider,
2762                    &results,
2763                    workspace,
2764                    source_cnf,
2765                    source_branch_var_limit,
2766                    target_cnf,
2767                    target_branch_var_limit,
2768                )?;
2769            this.record_accepted_gpu_batch_candidate_evidence(results.len())?;
2770            Ok(report)
2771        })
2772    }
2773
2774    fn solve_weighted_maxsat_candidates(
2775        &mut self,
2776        candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
2777        frontier_upper_bound_certificates: u64,
2778    ) -> Result<GpuSolverProductionMaxSatReport> {
2779        self.with_trace_rollback(|this| {
2780            this.solve_weighted_maxsat_candidates_impl(
2781                candidates,
2782                frontier_upper_bound_certificates,
2783            )
2784        })
2785    }
2786
2787    fn solve_weighted_maxsat_candidates_impl(
2788        &mut self,
2789        candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
2790        frontier_upper_bound_certificates: u64,
2791    ) -> Result<GpuSolverProductionMaxSatReport> {
2792        self.require_weighted_maxsat_candidates_and_artifacts(candidates)?;
2793
2794        let solves_before = self.trace.gpu_maxsat_candidate_solves;
2795        let mut optimum_score = 0u64;
2796        for candidate in candidates {
2797            let _assignment = self
2798                .solver
2799                .solve_expect_sat_with_branch_limit(candidate.cnf, candidate.branch_var_limit)?;
2800            checked_solver_trace_counter_inc!(self, gpu_cdcl_sat_solves);
2801            checked_solver_trace_counter_inc!(self, gpu_maxsat_candidate_solves);
2802            optimum_score = optimum_score.max(candidate.score);
2803        }
2804        checked_solver_trace_counter_inc!(self, gpu_maxsat_optima);
2805        let gpu_cdcl_candidate_solves = Self::checked_report_counter_delta(
2806            self.trace.gpu_maxsat_candidate_solves,
2807            solves_before,
2808            "gpu_maxsat_candidate_solves",
2809        )?;
2810        if frontier_upper_bound_certificates != 0 {
2811            self.trace.gpu_maxsat_frontier_certified_candidate_solves =
2812                Self::checked_trace_counter_add(
2813                    self.trace.gpu_maxsat_frontier_certified_candidate_solves,
2814                    gpu_cdcl_candidate_solves,
2815                    "gpu_maxsat_frontier_certified_candidate_solves",
2816                )?;
2817        }
2818
2819        Ok(GpuSolverProductionMaxSatReport {
2820            candidate_evidence_records: 0,
2821            optimum_score,
2822            candidates_checked: candidates.len() as u64,
2823            satisfiable_candidates: candidates.len() as u64,
2824            unsat_candidates_pruned: 0,
2825            gpu_cdcl_candidate_encodes: 0,
2826            gpu_cdcl_candidate_solves,
2827            frontier_upper_bound_certificates,
2828        })
2829    }
2830
2831    fn require_weighted_maxsat_candidates(
2832        candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
2833    ) -> Result<()> {
2834        if candidates.is_empty() {
2835            return Err(XlogError::UnsupportedEpistemicConstruct {
2836                construct: "GPU solver production MaxSAT".to_string(),
2837                context: "bounded MaxSAT adapter requires at least one candidate CNF".to_string(),
2838            });
2839        }
2840        Ok(())
2841    }
2842
2843    /// Solve a bounded weighted MaxSAT candidate set after accepted GPU epistemic execution.
2844    ///
2845    /// CPU orchestration is limited to launching/checking the provided candidate CNFs and
2846    /// comparing their declared scores. Each candidate is certified by the existing GPU CDCL
2847    /// SAT path; this adapter performs no CPU assignment or MaxSAT enumeration.
2848    pub fn solve_weighted_maxsat_candidates_with_gpu_execution_result(
2849        &mut self,
2850        provider: &CudaKernelProvider,
2851        result: &EpistemicGpuExecutionResult,
2852        candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
2853    ) -> Result<GpuSolverProductionMaxSatReport> {
2854        self.with_trace_rollback(|this| {
2855            this.require_weighted_maxsat_candidates_and_artifacts(candidates)?;
2856            let state = this.require_accepted_gpu_solver_evidence(provider, result)?;
2857            let events_before = this.trace.accepted_path_event_snapshot()?;
2858            let mut report = this.solve_weighted_maxsat_candidates(candidates, 0)?;
2859            report.candidate_evidence_records = 1;
2860            this.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
2861            this.record_accepted_gpu_candidate_state(&state)?;
2862            Ok(report)
2863        })
2864    }
2865
2866    fn require_maxsat_lifecycle_inputs(
2867        steps: &[GpuSolverProductionLifecycleStep<'_>],
2868        candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
2869    ) -> Result<()> {
2870        if steps.is_empty() {
2871            return Err(XlogError::UnsupportedEpistemicConstruct {
2872                construct: "GPU solver production MaxSAT lifecycle".to_string(),
2873                context: "accepted MaxSAT lifecycle requires at least one lifecycle step"
2874                    .to_string(),
2875            });
2876        }
2877        if candidates.is_empty() {
2878            return Err(XlogError::UnsupportedEpistemicConstruct {
2879                construct: "GPU solver production MaxSAT lifecycle".to_string(),
2880                context: "bounded MaxSAT adapter requires at least one candidate CNF".to_string(),
2881            });
2882        }
2883        Self::require_assumption_lifecycle_steps(steps)?;
2884        Ok(())
2885    }
2886
2887    /// Execute an accepted solver lifecycle, then a bounded MaxSAT candidate set.
2888    ///
2889    /// The same accepted GPU epistemic evidence gates both phases. The adapter
2890    /// records that evidence once, while lifecycle and MaxSAT counters prove the
2891    /// existing GPU CDCL paths handled all solver work without CPU search.
2892    pub fn solve_maxsat_lifecycle_with_gpu_execution_result(
2893        &mut self,
2894        provider: &CudaKernelProvider,
2895        result: &EpistemicGpuExecutionResult,
2896        workspace: &mut GpuCdclWorkspace,
2897        steps: &[GpuSolverProductionLifecycleStep<'_>],
2898        candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
2899    ) -> Result<GpuSolverProductionMaxSatLifecycleReport> {
2900        self.with_trace_rollback(|this| {
2901            this.solve_maxsat_lifecycle_with_gpu_execution_result_impl(
2902                provider, result, workspace, steps, candidates,
2903            )
2904        })
2905    }
2906
2907    fn solve_maxsat_lifecycle_with_gpu_execution_result_impl(
2908        &mut self,
2909        provider: &CudaKernelProvider,
2910        result: &EpistemicGpuExecutionResult,
2911        workspace: &mut GpuCdclWorkspace,
2912        steps: &[GpuSolverProductionLifecycleStep<'_>],
2913        candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
2914    ) -> Result<GpuSolverProductionMaxSatLifecycleReport> {
2915        Self::require_maxsat_lifecycle_inputs(steps, candidates)?;
2916        self.require_workspace_on_adapter_provider(workspace)?;
2917        self.require_maxsat_lifecycle_artifacts(workspace, steps, candidates)?;
2918        let state = self.require_accepted_gpu_solver_evidence(provider, result)?;
2919
2920        let events_before = self.trace.accepted_path_event_snapshot()?;
2921        let lifecycle = self.solve_assumption_lifecycle_steps(workspace, steps)?;
2922        let maxsat = self.solve_weighted_maxsat_candidates(candidates, 0)?;
2923        self.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
2924        self.record_accepted_gpu_candidate_state(&state)?;
2925
2926        Ok(GpuSolverProductionMaxSatLifecycleReport {
2927            candidate_evidence_records: 1,
2928            lifecycle,
2929            maxsat,
2930        })
2931    }
2932
2933    fn add_maxsat_lifecycle_step_report(
2934        report: &mut GpuSolverProductionMaxSatLifecycleReport,
2935        step_report: GpuSolverProductionMaxSatLifecycleReport,
2936    ) -> Result<()> {
2937        report.candidate_evidence_records = Self::checked_report_counter_add(
2938            report.candidate_evidence_records,
2939            step_report.candidate_evidence_records,
2940            "candidate_evidence_records",
2941        )?;
2942        report.lifecycle.steps = Self::checked_report_counter_add(
2943            report.lifecycle.steps,
2944            step_report.lifecycle.steps,
2945            "lifecycle.steps",
2946        )?;
2947        report.lifecycle.sat_steps = Self::checked_report_counter_add(
2948            report.lifecycle.sat_steps,
2949            step_report.lifecycle.sat_steps,
2950            "lifecycle.sat_steps",
2951        )?;
2952        report.lifecycle.unsat_steps = Self::checked_report_counter_add(
2953            report.lifecycle.unsat_steps,
2954            step_report.lifecycle.unsat_steps,
2955            "lifecycle.unsat_steps",
2956        )?;
2957        report.lifecycle.assumption_pushes = Self::checked_report_counter_add(
2958            report.lifecycle.assumption_pushes,
2959            step_report.lifecycle.assumption_pushes,
2960            "lifecycle.assumption_pushes",
2961        )?;
2962        report.lifecycle.assumption_retractions = Self::checked_report_counter_add(
2963            report.lifecycle.assumption_retractions,
2964            step_report.lifecycle.assumption_retractions,
2965            "lifecycle.assumption_retractions",
2966        )?;
2967        report.lifecycle.workspace_reuses = Self::checked_report_counter_add(
2968            report.lifecycle.workspace_reuses,
2969            step_report.lifecycle.workspace_reuses,
2970            "lifecycle.workspace_reuses",
2971        )?;
2972        report.lifecycle.unknown_steps = Self::checked_report_counter_add(
2973            report.lifecycle.unknown_steps,
2974            step_report.lifecycle.unknown_steps,
2975            "lifecycle.unknown_steps",
2976        )?;
2977        report.lifecycle.timeout_steps = Self::checked_report_counter_add(
2978            report.lifecycle.timeout_steps,
2979            step_report.lifecycle.timeout_steps,
2980            "lifecycle.timeout_steps",
2981        )?;
2982        report.maxsat.optimum_score = report
2983            .maxsat
2984            .optimum_score
2985            .max(step_report.maxsat.optimum_score);
2986        report.maxsat.candidates_checked = Self::checked_report_counter_add(
2987            report.maxsat.candidates_checked,
2988            step_report.maxsat.candidates_checked,
2989            "maxsat.candidates_checked",
2990        )?;
2991        report.maxsat.satisfiable_candidates = Self::checked_report_counter_add(
2992            report.maxsat.satisfiable_candidates,
2993            step_report.maxsat.satisfiable_candidates,
2994            "maxsat.satisfiable_candidates",
2995        )?;
2996        report.maxsat.unsat_candidates_pruned = Self::checked_report_counter_add(
2997            report.maxsat.unsat_candidates_pruned,
2998            step_report.maxsat.unsat_candidates_pruned,
2999            "maxsat.unsat_candidates_pruned",
3000        )?;
3001        report.maxsat.gpu_cdcl_candidate_encodes = Self::checked_report_counter_add(
3002            report.maxsat.gpu_cdcl_candidate_encodes,
3003            step_report.maxsat.gpu_cdcl_candidate_encodes,
3004            "maxsat.gpu_cdcl_candidate_encodes",
3005        )?;
3006        report.maxsat.gpu_cdcl_candidate_solves = Self::checked_report_counter_add(
3007            report.maxsat.gpu_cdcl_candidate_solves,
3008            step_report.maxsat.gpu_cdcl_candidate_solves,
3009            "maxsat.gpu_cdcl_candidate_solves",
3010        )?;
3011        report.maxsat.frontier_upper_bound_certificates = Self::checked_report_counter_add(
3012            report.maxsat.frontier_upper_bound_certificates,
3013            step_report.maxsat.frontier_upper_bound_certificates,
3014            "maxsat.frontier_upper_bound_certificates",
3015        )?;
3016        Ok(())
3017    }
3018
3019    /// Execute accepted solver lifecycle plus MaxSAT candidate-set work once per evidence record.
3020    pub fn solve_multi_candidate_maxsat_lifecycle_with_gpu_execution_results(
3021        &mut self,
3022        provider: &CudaKernelProvider,
3023        results: &[&EpistemicGpuExecutionResult],
3024        workspace: &mut GpuCdclWorkspace,
3025        steps: &[GpuSolverProductionLifecycleStep<'_>],
3026        candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
3027    ) -> Result<GpuSolverProductionMaxSatLifecycleReport> {
3028        self.with_trace_rollback(|this| {
3029            this.solve_multi_candidate_maxsat_lifecycle_with_gpu_execution_results_impl(
3030                provider, results, workspace, steps, candidates,
3031            )
3032        })
3033    }
3034
3035    fn solve_multi_candidate_maxsat_lifecycle_with_gpu_execution_results_impl(
3036        &mut self,
3037        provider: &CudaKernelProvider,
3038        results: &[&EpistemicGpuExecutionResult],
3039        workspace: &mut GpuCdclWorkspace,
3040        steps: &[GpuSolverProductionLifecycleStep<'_>],
3041        candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
3042    ) -> Result<GpuSolverProductionMaxSatLifecycleReport> {
3043        if results.is_empty() {
3044            return Err(XlogError::UnsupportedEpistemicConstruct {
3045                construct: "GPU solver production MaxSAT lifecycle".to_string(),
3046                context:
3047                    "multi-candidate MaxSAT lifecycle requires at least one accepted GPU result"
3048                        .to_string(),
3049            });
3050        }
3051        Self::require_maxsat_lifecycle_inputs(steps, candidates)?;
3052        self.require_workspace_on_adapter_provider(workspace)?;
3053        self.require_maxsat_lifecycle_artifacts(workspace, steps, candidates)?;
3054        let states = self.require_accepted_gpu_solver_states(provider, results)?;
3055
3056        let mut report = GpuSolverProductionMaxSatLifecycleReport::default();
3057        for state in &states {
3058            let events_before = self.trace.accepted_path_event_snapshot()?;
3059            let lifecycle = self.solve_assumption_lifecycle_steps(workspace, steps)?;
3060            let maxsat = self.solve_weighted_maxsat_candidates(candidates, 0)?;
3061            self.record_accepted_gpu_solver_production_path_events_since(events_before, state)?;
3062            self.record_accepted_gpu_candidate_state(state)?;
3063            Self::add_maxsat_lifecycle_step_report(
3064                &mut report,
3065                GpuSolverProductionMaxSatLifecycleReport {
3066                    candidate_evidence_records: 1,
3067                    lifecycle,
3068                    maxsat,
3069                },
3070            )?;
3071        }
3072
3073        Ok(report)
3074    }
3075
3076    /// Execute accepted split/batch solver lifecycle plus MaxSAT candidate-set work.
3077    pub fn solve_maxsat_lifecycle_with_gpu_batch_execution_result(
3078        &mut self,
3079        provider: &CudaKernelProvider,
3080        evidence: GpuSolverProductionBatchExecutionEvidence<'_>,
3081        workspace: &mut GpuCdclWorkspace,
3082        steps: &[GpuSolverProductionLifecycleStep<'_>],
3083        candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
3084    ) -> Result<GpuSolverProductionMaxSatLifecycleReport> {
3085        self.with_trace_rollback(|this| {
3086            Self::require_maxsat_lifecycle_inputs(steps, candidates)?;
3087            this.require_workspace_on_adapter_provider(workspace)?;
3088            this.require_maxsat_lifecycle_artifacts(workspace, steps, candidates)?;
3089            let results =
3090                this.accepted_solver_results_from_gpu_batch_execution_evidence(provider, evidence)?;
3091            let report = this.solve_multi_candidate_maxsat_lifecycle_with_gpu_execution_results(
3092                provider, &results, workspace, steps, candidates,
3093            )?;
3094            this.record_accepted_gpu_batch_candidate_evidence(results.len())?;
3095            Ok(report)
3096        })
3097    }
3098
3099    /// Solve a bounded weighted MaxSAT candidate set once per accepted GPU epistemic candidate.
3100    pub fn solve_multi_candidate_weighted_maxsat_with_gpu_execution_results(
3101        &mut self,
3102        provider: &CudaKernelProvider,
3103        results: &[&EpistemicGpuExecutionResult],
3104        candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
3105    ) -> Result<GpuSolverProductionMaxSatReport> {
3106        self.with_trace_rollback(|this| {
3107            this.solve_multi_candidate_weighted_maxsat_with_gpu_execution_results_impl(
3108                provider, results, candidates,
3109            )
3110        })
3111    }
3112
3113    fn solve_multi_candidate_weighted_maxsat_with_gpu_execution_results_impl(
3114        &mut self,
3115        provider: &CudaKernelProvider,
3116        results: &[&EpistemicGpuExecutionResult],
3117        candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
3118    ) -> Result<GpuSolverProductionMaxSatReport> {
3119        if results.is_empty() {
3120            return Err(XlogError::UnsupportedEpistemicConstruct {
3121                construct: "GPU solver production MaxSAT".to_string(),
3122                context: "multi-candidate MaxSAT requires at least one accepted GPU result"
3123                    .to_string(),
3124            });
3125        }
3126        self.require_weighted_maxsat_candidates_and_artifacts(candidates)?;
3127        let states = self.require_accepted_gpu_solver_states(provider, results)?;
3128
3129        let mut report = GpuSolverProductionMaxSatReport::default();
3130        for state in &states {
3131            let events_before = self.trace.accepted_path_event_snapshot()?;
3132            let step_report = self.solve_weighted_maxsat_candidates(candidates, 0)?;
3133            checked_solver_report_counter_inc!(report, candidate_evidence_records);
3134            report.optimum_score = report.optimum_score.max(step_report.optimum_score);
3135            checked_solver_report_counter_add!(
3136                report,
3137                candidates_checked,
3138                step_report.candidates_checked
3139            );
3140            checked_solver_report_counter_add!(
3141                report,
3142                satisfiable_candidates,
3143                step_report.satisfiable_candidates
3144            );
3145            checked_solver_report_counter_add!(
3146                report,
3147                unsat_candidates_pruned,
3148                step_report.unsat_candidates_pruned
3149            );
3150            checked_solver_report_counter_add!(
3151                report,
3152                gpu_cdcl_candidate_encodes,
3153                step_report.gpu_cdcl_candidate_encodes
3154            );
3155            checked_solver_report_counter_add!(
3156                report,
3157                gpu_cdcl_candidate_solves,
3158                step_report.gpu_cdcl_candidate_solves
3159            );
3160            checked_solver_report_counter_add!(
3161                report,
3162                frontier_upper_bound_certificates,
3163                step_report.frontier_upper_bound_certificates
3164            );
3165            self.record_accepted_gpu_solver_production_path_events_since(events_before, state)?;
3166            self.record_accepted_gpu_candidate_state(state)?;
3167        }
3168
3169        Ok(report)
3170    }
3171
3172    /// Solve a bounded weighted MaxSAT candidate set once per accepted split/batch GPU component.
3173    ///
3174    /// The batch evidence must prove every split component reused the existing
3175    /// single-plan GPU runtime path before each component is delegated to the
3176    /// existing multi-candidate MaxSAT adapter.
3177    pub fn solve_weighted_maxsat_candidates_with_gpu_batch_execution_result(
3178        &mut self,
3179        provider: &CudaKernelProvider,
3180        evidence: GpuSolverProductionBatchExecutionEvidence<'_>,
3181        candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
3182    ) -> Result<GpuSolverProductionMaxSatReport> {
3183        self.with_trace_rollback(|this| {
3184            this.require_weighted_maxsat_candidates_and_artifacts(candidates)?;
3185            let results =
3186                this.accepted_solver_results_from_gpu_batch_execution_evidence(provider, evidence)?;
3187            let report = this.solve_multi_candidate_weighted_maxsat_with_gpu_execution_results(
3188                provider, &results, candidates,
3189            )?;
3190            this.record_accepted_gpu_batch_candidate_evidence(results.len())?;
3191            Ok(report)
3192        })
3193    }
3194
3195    fn encode_weighted_maxsat_search_candidates(
3196        &mut self,
3197        weighted: &SolveInstance,
3198        selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
3199    ) -> Result<Vec<GpuSolverProductionEncodedMaxSatSearchCandidate>> {
3200        Self::require_weighted_maxsat_encoding_inputs(weighted, selections)?;
3201
3202        let weights =
3203            weighted
3204                .weights
3205                .as_ref()
3206                .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3207                    construct: "GPU solver production MaxSAT encoding".to_string(),
3208                    context: "weighted MaxSAT encoding requires explicit soft-clause weights"
3209                        .to_string(),
3210                })?;
3211
3212        let frontier =
3213            Self::complete_weighted_maxsat_frontier_selections(weighted, weights, selections)?;
3214        if frontier.completion_candidate_count != 0 {
3215            self.trace.gpu_maxsat_frontier_completion_candidate_encodes =
3216                Self::checked_trace_counter_add(
3217                    self.trace.gpu_maxsat_frontier_completion_candidate_encodes,
3218                    frontier.completion_candidate_count,
3219                    "gpu_maxsat_frontier_completion_candidate_encodes",
3220                )?;
3221        }
3222        checked_solver_trace_counter_inc!(self, gpu_maxsat_frontier_upper_bound_certificates);
3223        let mut encoded = Vec::with_capacity(frontier.selections.len());
3224        for selection in &frontier.selections {
3225            encoded.push(self.encode_weighted_maxsat_subset(
3226                weighted,
3227                weights,
3228                &selection.soft_clause_indices,
3229                selection.status,
3230            )?);
3231        }
3232
3233        Ok(encoded)
3234    }
3235
3236    fn encode_weighted_maxsat_subset(
3237        &mut self,
3238        weighted: &SolveInstance,
3239        weights: &[f64],
3240        soft_clause_indices: &[usize],
3241        status: GpuSolverProductionMaxSatSearchStatus,
3242    ) -> Result<GpuSolverProductionEncodedMaxSatSearchCandidate> {
3243        let mut score = 0u64;
3244        let mut clauses = Vec::with_capacity(soft_clause_indices.len());
3245        for &idx in soft_clause_indices {
3246            let clause = &weighted.clauses[idx];
3247            let weight = Self::soft_clause_weight_score(idx, weights[idx])?;
3248            score = score.checked_add(weight).ok_or_else(|| {
3249                XlogError::UnsupportedEpistemicConstruct {
3250                    construct: "GPU solver production MaxSAT encoding".to_string(),
3251                    context: format!(
3252                        "soft-clause selection score overflowed u64 while adding index {}",
3253                        idx
3254                    ),
3255                }
3256            })?;
3257            clauses.push(clause.clone());
3258        }
3259
3260        let candidate_instance = SolveInstance::new(weighted.num_vars, clauses);
3261        let data_plane_before = self.provider.host_transfer_stats();
3262        let launch_metadata_before = self.provider.host_launch_metadata_transfer_stats();
3263        let cnf = GpuCnf::from_host(&candidate_instance, &self.provider)?;
3264        let data_plane_after = self.provider.host_transfer_stats();
3265        let launch_metadata_after = self.provider.host_launch_metadata_transfer_stats();
3266        self.record_encoded_maxsat_cnf_upload_transfer_delta(
3267            data_plane_before,
3268            data_plane_after,
3269            launch_metadata_before,
3270            launch_metadata_after,
3271        )?;
3272        checked_solver_trace_counter_inc!(self, gpu_maxsat_candidate_encodes);
3273        Ok(GpuSolverProductionEncodedMaxSatSearchCandidate { score, cnf, status })
3274    }
3275
3276    fn record_encoded_maxsat_cnf_upload_transfer_delta(
3277        &mut self,
3278        data_plane_before: xlog_cuda::provider::HostTransferStats,
3279        data_plane_after: xlog_cuda::provider::HostTransferStats,
3280        launch_metadata_before: xlog_cuda::provider::HostLaunchMetadataTransferStats,
3281        launch_metadata_after: xlog_cuda::provider::HostLaunchMetadataTransferStats,
3282    ) -> Result<()> {
3283        let data_plane_htod_calls = Self::checked_report_counter_delta(
3284            data_plane_after.htod_calls,
3285            data_plane_before.htod_calls,
3286            "gpu_maxsat_candidate_cnf_data_plane_htod_calls",
3287        )?;
3288        let data_plane_htod_bytes = Self::checked_report_counter_delta(
3289            data_plane_after.htod_bytes,
3290            data_plane_before.htod_bytes,
3291            "gpu_maxsat_candidate_cnf_data_plane_htod_bytes",
3292        )?;
3293        let data_plane_dtoh_calls = Self::checked_report_counter_delta(
3294            data_plane_after.dtoh_calls,
3295            data_plane_before.dtoh_calls,
3296            "gpu_maxsat_candidate_cnf_data_plane_dtoh_calls",
3297        )?;
3298        let data_plane_dtoh_bytes = Self::checked_report_counter_delta(
3299            data_plane_after.dtoh_bytes,
3300            data_plane_before.dtoh_bytes,
3301            "gpu_maxsat_candidate_cnf_data_plane_dtoh_bytes",
3302        )?;
3303        let launch_metadata_htod_calls = Self::checked_report_counter_delta(
3304            launch_metadata_after.htod_calls,
3305            launch_metadata_before.htod_calls,
3306            "gpu_maxsat_candidate_cnf_launch_metadata_htod_calls",
3307        )?;
3308        let launch_metadata_htod_bytes = Self::checked_report_counter_delta(
3309            launch_metadata_after.htod_bytes,
3310            launch_metadata_before.htod_bytes,
3311            "gpu_maxsat_candidate_cnf_launch_metadata_htod_bytes",
3312        )?;
3313
3314        self.trace.gpu_maxsat_candidate_cnf_data_plane_htod_calls =
3315            Self::checked_trace_counter_add(
3316                self.trace.gpu_maxsat_candidate_cnf_data_plane_htod_calls,
3317                data_plane_htod_calls,
3318                "gpu_maxsat_candidate_cnf_data_plane_htod_calls",
3319            )?;
3320        self.trace.gpu_maxsat_candidate_cnf_data_plane_htod_bytes =
3321            Self::checked_trace_counter_add(
3322                self.trace.gpu_maxsat_candidate_cnf_data_plane_htod_bytes,
3323                data_plane_htod_bytes,
3324                "gpu_maxsat_candidate_cnf_data_plane_htod_bytes",
3325            )?;
3326        self.trace.gpu_maxsat_candidate_cnf_data_plane_dtoh_calls =
3327            Self::checked_trace_counter_add(
3328                self.trace.gpu_maxsat_candidate_cnf_data_plane_dtoh_calls,
3329                data_plane_dtoh_calls,
3330                "gpu_maxsat_candidate_cnf_data_plane_dtoh_calls",
3331            )?;
3332        self.trace.gpu_maxsat_candidate_cnf_data_plane_dtoh_bytes =
3333            Self::checked_trace_counter_add(
3334                self.trace.gpu_maxsat_candidate_cnf_data_plane_dtoh_bytes,
3335                data_plane_dtoh_bytes,
3336                "gpu_maxsat_candidate_cnf_data_plane_dtoh_bytes",
3337            )?;
3338        self.trace
3339            .gpu_maxsat_candidate_cnf_launch_metadata_htod_calls = Self::checked_trace_counter_add(
3340            self.trace
3341                .gpu_maxsat_candidate_cnf_launch_metadata_htod_calls,
3342            launch_metadata_htod_calls,
3343            "gpu_maxsat_candidate_cnf_launch_metadata_htod_calls",
3344        )?;
3345        self.trace
3346            .gpu_maxsat_candidate_cnf_launch_metadata_htod_bytes = Self::checked_trace_counter_add(
3347            self.trace
3348                .gpu_maxsat_candidate_cnf_launch_metadata_htod_bytes,
3349            launch_metadata_htod_bytes,
3350            "gpu_maxsat_candidate_cnf_launch_metadata_htod_bytes",
3351        )?;
3352        Ok(())
3353    }
3354
3355    fn solve_weighted_maxsat_search_candidates(
3356        &mut self,
3357        workspace: &mut GpuCdclWorkspace,
3358        candidates: &[GpuSolverProductionMaxSatSearchCandidate<'_>],
3359        frontier_upper_bound_certificates: u64,
3360    ) -> Result<GpuSolverProductionMaxSatReport> {
3361        self.require_workspace_on_adapter_provider(workspace)?;
3362        self.require_weighted_maxsat_search_candidates_and_artifacts(workspace, candidates)?;
3363
3364        let solves_before = self.trace.gpu_maxsat_candidate_solves;
3365        let unsat_prunes_before = self.trace.gpu_maxsat_unsat_candidate_prunes;
3366        let mut optimum_score = 0u64;
3367        let mut satisfiable_candidates = 0u64;
3368
3369        for candidate in candidates {
3370            match candidate.status {
3371                GpuSolverProductionMaxSatSearchStatus::Satisfiable => {
3372                    let _assignment = self.solver.solve_expect_sat_with_branch_limit(
3373                        candidate.cnf,
3374                        candidate.branch_var_limit,
3375                    )?;
3376                    checked_solver_trace_counter_inc!(self, gpu_cdcl_sat_solves);
3377                    checked_solver_trace_counter_inc!(self, gpu_maxsat_candidate_solves);
3378                    satisfiable_candidates = Self::checked_trace_counter_add(
3379                        satisfiable_candidates,
3380                        1,
3381                        "maxsat_satisfiable_candidates",
3382                    )?;
3383                    optimum_score = optimum_score.max(candidate.score);
3384                }
3385                GpuSolverProductionMaxSatSearchStatus::Unsatisfiable => {
3386                    self.solve_expect_unsat_with_branch_limit_ws(
3387                        workspace,
3388                        candidate.cnf,
3389                        candidate.branch_var_limit,
3390                    )?;
3391                    checked_solver_trace_counter_inc!(self, gpu_maxsat_candidate_solves);
3392                    checked_solver_trace_counter_inc!(self, gpu_maxsat_unsat_candidate_prunes);
3393                }
3394            }
3395        }
3396
3397        checked_solver_trace_counter_inc!(self, gpu_maxsat_optima);
3398        let gpu_cdcl_candidate_solves = Self::checked_report_counter_delta(
3399            self.trace.gpu_maxsat_candidate_solves,
3400            solves_before,
3401            "gpu_maxsat_candidate_solves",
3402        )?;
3403        if frontier_upper_bound_certificates != 0 {
3404            self.trace.gpu_maxsat_frontier_certified_candidate_solves =
3405                Self::checked_trace_counter_add(
3406                    self.trace.gpu_maxsat_frontier_certified_candidate_solves,
3407                    gpu_cdcl_candidate_solves,
3408                    "gpu_maxsat_frontier_certified_candidate_solves",
3409                )?;
3410        }
3411
3412        Ok(GpuSolverProductionMaxSatReport {
3413            candidate_evidence_records: 0,
3414            optimum_score,
3415            candidates_checked: candidates.len() as u64,
3416            satisfiable_candidates,
3417            unsat_candidates_pruned: Self::checked_report_counter_delta(
3418                self.trace.gpu_maxsat_unsat_candidate_prunes,
3419                unsat_prunes_before,
3420                "gpu_maxsat_unsat_candidate_prunes",
3421            )?,
3422            gpu_cdcl_candidate_encodes: 0,
3423            gpu_cdcl_candidate_solves,
3424            frontier_upper_bound_certificates,
3425        })
3426    }
3427
3428    fn require_weighted_maxsat_search_candidates(
3429        candidates: &[GpuSolverProductionMaxSatSearchCandidate<'_>],
3430    ) -> Result<()> {
3431        if candidates.is_empty() {
3432            return Err(XlogError::UnsupportedEpistemicConstruct {
3433                construct: "GPU solver production MaxSAT search".to_string(),
3434                context: "bounded MaxSAT search requires at least one candidate CNF".to_string(),
3435            });
3436        }
3437        if !candidates.iter().any(|candidate| {
3438            matches!(
3439                candidate.status,
3440                GpuSolverProductionMaxSatSearchStatus::Satisfiable
3441            )
3442        }) {
3443            return Err(XlogError::UnsupportedEpistemicConstruct {
3444                construct: "GPU solver production MaxSAT search".to_string(),
3445                context: "bounded MaxSAT search requires at least one satisfiable GPU candidate"
3446                    .to_string(),
3447            });
3448        }
3449        Ok(())
3450    }
3451
3452    fn require_weighted_maxsat_search_selections(
3453        selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
3454    ) -> Result<()> {
3455        if selections.is_empty() {
3456            return Err(XlogError::UnsupportedEpistemicConstruct {
3457                construct: "GPU solver production MaxSAT encoding".to_string(),
3458                context: "weighted MaxSAT encoding requires at least one selection".to_string(),
3459            });
3460        }
3461        Ok(())
3462    }
3463
3464    fn require_weighted_maxsat_encoding_inputs(
3465        weighted: &SolveInstance,
3466        selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
3467    ) -> Result<()> {
3468        Self::require_weighted_maxsat_search_selections(selections)?;
3469
3470        if weighted.objective != Objective::MaxSat {
3471            return Err(XlogError::UnsupportedEpistemicConstruct {
3472                construct: "GPU solver production MaxSAT encoding".to_string(),
3473                context: format!(
3474                    "weighted MaxSAT encoding requires Objective::MaxSat, got {:?}",
3475                    weighted.objective
3476                ),
3477            });
3478        }
3479        if weighted.num_vars == 0 {
3480            return Err(XlogError::UnsupportedEpistemicConstruct {
3481                construct: "GPU solver production MaxSAT encoding".to_string(),
3482                context: "weighted MaxSAT encoding requires num_vars > 0".to_string(),
3483            });
3484        }
3485
3486        let weights =
3487            weighted
3488                .weights
3489                .as_ref()
3490                .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3491                    construct: "GPU solver production MaxSAT encoding".to_string(),
3492                    context: "weighted MaxSAT encoding requires explicit soft-clause weights"
3493                        .to_string(),
3494                })?;
3495        if weights.len() != weighted.clauses.len() {
3496            return Err(XlogError::UnsupportedEpistemicConstruct {
3497                construct: "GPU solver production MaxSAT encoding".to_string(),
3498                context: format!(
3499                    "soft-clause weights length {} does not match clause count {}",
3500                    weights.len(),
3501                    weighted.clauses.len()
3502                ),
3503            });
3504        }
3505
3506        for selection in selections {
3507            if selection.soft_clause_indices.is_empty() {
3508                return Err(XlogError::UnsupportedEpistemicConstruct {
3509                    construct: "GPU solver production MaxSAT encoding".to_string(),
3510                    context:
3511                        "weighted MaxSAT search selections must include at least one soft clause"
3512                            .to_string(),
3513                });
3514            }
3515            let mut seen_indices = BTreeSet::new();
3516            for (position, &idx) in selection.soft_clause_indices.iter().enumerate() {
3517                if !seen_indices.insert(idx) {
3518                    return Err(XlogError::UnsupportedEpistemicConstruct {
3519                        construct: "GPU solver production MaxSAT encoding".to_string(),
3520                        context: format!(
3521                            "soft-clause selection duplicates index {} at position {}; \
3522                             weighted MaxSAT candidates must count each soft clause at most once",
3523                            idx, position
3524                        ),
3525                    });
3526                }
3527            }
3528            for &idx in selection.soft_clause_indices {
3529                let _clause = weighted.clauses.get(idx).ok_or_else(|| {
3530                    XlogError::UnsupportedEpistemicConstruct {
3531                        construct: "GPU solver production MaxSAT encoding".to_string(),
3532                        context: format!(
3533                            "soft-clause selection index {} is out of range for {} clauses",
3534                            idx,
3535                            weighted.clauses.len()
3536                        ),
3537                    }
3538                })?;
3539                let weight =
3540                    *weights
3541                        .get(idx)
3542                        .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3543                            construct: "GPU solver production MaxSAT encoding".to_string(),
3544                            context: format!(
3545                                "soft-clause weight index {} is out of range for {} weights",
3546                                idx,
3547                                weights.len()
3548                            ),
3549                        })?;
3550                let _ = Self::soft_clause_weight_score(idx, weight)?;
3551            }
3552        }
3553        Ok(())
3554    }
3555
3556    fn complete_weighted_maxsat_frontier_selections(
3557        weighted: &SolveInstance,
3558        weights: &[f64],
3559        selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
3560    ) -> Result<GpuSolverProductionCompletedWeightedMaxSatFrontier> {
3561        let mut completed = Vec::with_capacity(selections.len());
3562        let mut completion_candidate_count = 0u64;
3563        let mut seen = BTreeMap::new();
3564
3565        for selection in selections {
3566            let mut indices = selection.soft_clause_indices.to_vec();
3567            indices.sort_unstable();
3568            match seen.get(&indices) {
3569                Some(status) if *status != selection.status => {
3570                    return Err(XlogError::UnsupportedEpistemicConstruct {
3571                        construct: "GPU solver production MaxSAT encoding".to_string(),
3572                        context: format!(
3573                            "soft-clause selection {:?} has conflicting statuses {:?} and {:?}",
3574                            indices, status, selection.status
3575                        ),
3576                    });
3577                }
3578                Some(_) => continue,
3579                None => {
3580                    seen.insert(indices.clone(), selection.status);
3581                    completed.push(GpuSolverProductionOwnedWeightedMaxSatSelection {
3582                        soft_clause_indices: indices,
3583                        status: selection.status,
3584                    });
3585                }
3586            }
3587        }
3588
3589        let all_clause_indices: Vec<_> = (0..weighted.clauses.len()).collect();
3590        let certificates = Self::unsat_frontier_certificates(weights, &completed)?;
3591        let disjoint_frontier = certificates.len() > 1
3592            && Self::frontier_certificates_are_pairwise_disjoint(&certificates);
3593        Self::require_weighted_maxsat_frontier_completion_bound(&certificates, disjoint_frontier)?;
3594        if disjoint_frontier {
3595            let mut exclusions = Vec::with_capacity(certificates.len());
3596            Self::complete_disjoint_unsat_frontier_boundaries(
3597                &certificates,
3598                0,
3599                &mut exclusions,
3600                &all_clause_indices,
3601                &mut seen,
3602                &mut completed,
3603                &mut completion_candidate_count,
3604            )?;
3605        } else {
3606            for certificate in &certificates {
3607                for &excluded_idx in &certificate.min_weight_indices {
3608                    let boundary: Vec<_> = all_clause_indices
3609                        .iter()
3610                        .copied()
3611                        .filter(|idx| *idx != excluded_idx)
3612                        .collect();
3613                    Self::push_completed_frontier_candidate(
3614                        boundary,
3615                        &mut seen,
3616                        &mut completed,
3617                        &mut completion_candidate_count,
3618                    )?;
3619                }
3620            }
3621        }
3622
3623        Self::require_weighted_maxsat_frontier_upper_bound(weights, &completed)?;
3624        Ok(GpuSolverProductionCompletedWeightedMaxSatFrontier {
3625            selections: completed,
3626            completion_candidate_count,
3627        })
3628    }
3629
3630    fn require_weighted_maxsat_frontier_completion_bound(
3631        certificates: &[GpuSolverProductionUnsatFrontierCertificate],
3632        disjoint_frontier: bool,
3633    ) -> Result<()> {
3634        let implied_candidates = if disjoint_frontier {
3635            certificates.iter().try_fold(1u64, |acc, certificate| {
3636                acc.checked_mul(certificate.min_weight_indices.len() as u64)
3637                    .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3638                        construct: "GPU solver production MaxSAT encoding".to_string(),
3639                        context: "weighted MaxSAT disjoint frontier completion bound overflowed"
3640                            .to_string(),
3641                    })
3642            })?
3643        } else {
3644            certificates.iter().try_fold(0u64, |acc, certificate| {
3645                acc.checked_add(certificate.min_weight_indices.len() as u64)
3646                    .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3647                        construct: "GPU solver production MaxSAT encoding".to_string(),
3648                        context: "weighted MaxSAT frontier completion bound overflowed".to_string(),
3649                    })
3650            })?
3651        };
3652
3653        if implied_candidates > MAX_WEIGHTED_MAXSAT_FRONTIER_COMPLETION_CANDIDATES {
3654            return Err(XlogError::UnsupportedEpistemicConstruct {
3655                construct: "GPU solver production MaxSAT encoding".to_string(),
3656                context: format!(
3657                    "weighted MaxSAT frontier completion would require {} CPU-generated \
3658                     boundary candidates, exceeding production bound {}; provide explicit \
3659                     GPU scheduler selections",
3660                    implied_candidates, MAX_WEIGHTED_MAXSAT_FRONTIER_COMPLETION_CANDIDATES
3661                ),
3662            });
3663        }
3664        Ok(())
3665    }
3666
3667    fn unsat_frontier_certificates(
3668        weights: &[f64],
3669        selections: &[GpuSolverProductionOwnedWeightedMaxSatSelection],
3670    ) -> Result<Vec<GpuSolverProductionUnsatFrontierCertificate>> {
3671        let mut certificates = Vec::new();
3672        for selection in selections {
3673            if selection.status != GpuSolverProductionMaxSatSearchStatus::Unsatisfiable {
3674                continue;
3675            }
3676            let mut indices = selection.soft_clause_indices.clone();
3677            indices.sort_unstable();
3678            let mut min_weight = None;
3679            let mut min_weight_indices = Vec::new();
3680            for &idx in &indices {
3681                let weight = Self::soft_clause_weight_score(idx, weights[idx])?;
3682                match min_weight {
3683                    None => {
3684                        min_weight = Some(weight);
3685                        min_weight_indices.push(idx);
3686                    }
3687                    Some(current) if weight < current => {
3688                        min_weight = Some(weight);
3689                        min_weight_indices.clear();
3690                        min_weight_indices.push(idx);
3691                    }
3692                    Some(current) if weight == current => min_weight_indices.push(idx),
3693                    Some(_) => {}
3694                }
3695            }
3696            if let Some(min_weight) = min_weight {
3697                certificates.push(GpuSolverProductionUnsatFrontierCertificate {
3698                    indices,
3699                    min_weight,
3700                    min_weight_indices,
3701                });
3702            }
3703        }
3704        Ok(certificates)
3705    }
3706
3707    fn frontier_certificates_are_pairwise_disjoint(
3708        certificates: &[GpuSolverProductionUnsatFrontierCertificate],
3709    ) -> bool {
3710        let mut seen = BTreeSet::new();
3711        for certificate in certificates {
3712            for &idx in &certificate.indices {
3713                if !seen.insert(idx) {
3714                    return false;
3715                }
3716            }
3717        }
3718        true
3719    }
3720
3721    fn complete_disjoint_unsat_frontier_boundaries(
3722        certificates: &[GpuSolverProductionUnsatFrontierCertificate],
3723        depth: usize,
3724        exclusions: &mut Vec<usize>,
3725        all_clause_indices: &[usize],
3726        seen: &mut BTreeMap<Vec<usize>, GpuSolverProductionMaxSatSearchStatus>,
3727        completed: &mut Vec<GpuSolverProductionOwnedWeightedMaxSatSelection>,
3728        completion_candidate_count: &mut u64,
3729    ) -> Result<()> {
3730        if depth == certificates.len() {
3731            let exclusion_set: BTreeSet<_> = exclusions.iter().copied().collect();
3732            let boundary: Vec<_> = all_clause_indices
3733                .iter()
3734                .copied()
3735                .filter(|idx| !exclusion_set.contains(idx))
3736                .collect();
3737            return Self::push_completed_frontier_candidate(
3738                boundary,
3739                seen,
3740                completed,
3741                completion_candidate_count,
3742            );
3743        }
3744
3745        for &excluded_idx in &certificates[depth].min_weight_indices {
3746            exclusions.push(excluded_idx);
3747            Self::complete_disjoint_unsat_frontier_boundaries(
3748                certificates,
3749                depth + 1,
3750                exclusions,
3751                all_clause_indices,
3752                seen,
3753                completed,
3754                completion_candidate_count,
3755            )?;
3756            exclusions.pop();
3757        }
3758        Ok(())
3759    }
3760
3761    fn push_completed_frontier_candidate(
3762        boundary: Vec<usize>,
3763        seen: &mut BTreeMap<Vec<usize>, GpuSolverProductionMaxSatSearchStatus>,
3764        completed: &mut Vec<GpuSolverProductionOwnedWeightedMaxSatSelection>,
3765        completion_candidate_count: &mut u64,
3766    ) -> Result<()> {
3767        if boundary.is_empty() || seen.contains_key(&boundary) {
3768            return Ok(());
3769        }
3770        if *completion_candidate_count >= MAX_WEIGHTED_MAXSAT_FRONTIER_COMPLETION_CANDIDATES {
3771            return Err(XlogError::UnsupportedEpistemicConstruct {
3772                construct: "GPU solver production MaxSAT encoding".to_string(),
3773                context: format!(
3774                    "weighted MaxSAT frontier completion exceeded production bound {}; \
3775                     provide explicit GPU scheduler selections",
3776                    MAX_WEIGHTED_MAXSAT_FRONTIER_COMPLETION_CANDIDATES
3777                ),
3778            });
3779        }
3780        seen.insert(
3781            boundary.clone(),
3782            GpuSolverProductionMaxSatSearchStatus::Satisfiable,
3783        );
3784        *completion_candidate_count =
3785            completion_candidate_count.checked_add(1).ok_or_else(|| {
3786                XlogError::UnsupportedEpistemicConstruct {
3787                    construct: "GPU solver production MaxSAT encoding".to_string(),
3788                    context: "frontier completion candidate count overflowed".to_string(),
3789                }
3790            })?;
3791        completed.push(GpuSolverProductionOwnedWeightedMaxSatSelection {
3792            soft_clause_indices: boundary,
3793            status: GpuSolverProductionMaxSatSearchStatus::Satisfiable,
3794        });
3795        Ok(())
3796    }
3797
3798    fn require_weighted_maxsat_frontier_upper_bound(
3799        weights: &[f64],
3800        selections: &[GpuSolverProductionOwnedWeightedMaxSatSelection],
3801    ) -> Result<()> {
3802        let mut total_score = 0u64;
3803        for (idx, &weight) in weights.iter().enumerate() {
3804            total_score = total_score
3805                .checked_add(Self::soft_clause_weight_score(idx, weight)?)
3806                .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3807                    construct: "GPU solver production MaxSAT encoding".to_string(),
3808                    context: format!(
3809                        "weighted MaxSAT total score overflowed while adding soft clause {}",
3810                        idx
3811                    ),
3812                })?;
3813        }
3814
3815        let mut upper_bound = total_score;
3816        let mut best_satisfiable_score = 0u64;
3817        let certificates = Self::unsat_frontier_certificates(weights, selections)?;
3818
3819        for selection in selections {
3820            let mut indices = selection.soft_clause_indices.to_vec();
3821            indices.sort_unstable();
3822            let score = Self::weighted_maxsat_selection_score(weights, &indices)?;
3823            match selection.status {
3824                GpuSolverProductionMaxSatSearchStatus::Satisfiable => {
3825                    best_satisfiable_score = best_satisfiable_score.max(score);
3826                }
3827                GpuSolverProductionMaxSatSearchStatus::Unsatisfiable => {}
3828            }
3829        }
3830
3831        if certificates.len() > 1
3832            && Self::frontier_certificates_are_pairwise_disjoint(&certificates)
3833        {
3834            let certified_loss = certificates.iter().try_fold(0u64, |acc, certificate| {
3835                acc.checked_add(certificate.min_weight).ok_or_else(|| {
3836                    XlogError::UnsupportedEpistemicConstruct {
3837                        construct: "GPU solver production MaxSAT encoding".to_string(),
3838                        context: "weighted MaxSAT disjoint frontier loss overflowed".to_string(),
3839                    }
3840                })
3841            })?;
3842            upper_bound = total_score.checked_sub(certified_loss).ok_or_else(|| {
3843                XlogError::UnsupportedEpistemicConstruct {
3844                    construct: "GPU solver production MaxSAT encoding".to_string(),
3845                    context: format!(
3846                        "weighted MaxSAT disjoint frontier loss {} exceeds total score {}",
3847                        certified_loss, total_score
3848                    ),
3849                }
3850            })?;
3851        } else {
3852            for certificate in &certificates {
3853                let certificate_bound =
3854                    total_score
3855                        .checked_sub(certificate.min_weight)
3856                        .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3857                            construct: "GPU solver production MaxSAT encoding".to_string(),
3858                            context: format!(
3859                                "weighted MaxSAT UNSAT certificate {:?} has minimum weight {} above total score {}",
3860                                certificate.indices, certificate.min_weight, total_score
3861                            ),
3862                        })?;
3863                upper_bound = upper_bound.min(certificate_bound);
3864            }
3865        }
3866
3867        if best_satisfiable_score < upper_bound {
3868            return Err(XlogError::UnsupportedEpistemicConstruct {
3869                construct: "GPU solver production MaxSAT encoding".to_string(),
3870                context: format!(
3871                    "weighted MaxSAT frontier is incomplete: best GPU-certified satisfiable score {} is below the certified upper bound {}",
3872                    best_satisfiable_score, upper_bound
3873                ),
3874            });
3875        }
3876        Ok(())
3877    }
3878
3879    fn weighted_maxsat_selection_score(weights: &[f64], indices: &[usize]) -> Result<u64> {
3880        let mut score = 0u64;
3881        for &idx in indices {
3882            let weight =
3883                *weights
3884                    .get(idx)
3885                    .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3886                        construct: "GPU solver production MaxSAT encoding".to_string(),
3887                        context: format!(
3888                            "soft-clause weight index {} is out of range for {} weights",
3889                            idx,
3890                            weights.len()
3891                        ),
3892                    })?;
3893            score = score
3894                .checked_add(Self::soft_clause_weight_score(idx, weight)?)
3895                .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3896                    construct: "GPU solver production MaxSAT encoding".to_string(),
3897                    context: format!(
3898                        "soft-clause selection score overflowed u64 while adding index {}",
3899                        idx
3900                    ),
3901                })?;
3902        }
3903        Ok(score)
3904    }
3905
3906    fn soft_clause_weight_score(idx: usize, weight: f64) -> Result<u64> {
3907        if !weight.is_finite() || weight < 0.0 || weight.fract() != 0.0 {
3908            return Err(XlogError::UnsupportedEpistemicConstruct {
3909                construct: "GPU solver production MaxSAT encoding".to_string(),
3910                context: format!(
3911                    "soft-clause weight at index {} must be a finite nonnegative integer, got {}",
3912                    idx, weight
3913                ),
3914            });
3915        }
3916        if weight >= u64::MAX as f64 {
3917            return Err(XlogError::UnsupportedEpistemicConstruct {
3918                construct: "GPU solver production MaxSAT encoding".to_string(),
3919                context: format!(
3920                    "soft-clause weight at index {} exceeds u64 score range",
3921                    idx
3922                ),
3923            });
3924        }
3925        Ok(weight as u64)
3926    }
3927
3928    /// Search a bounded weighted MaxSAT candidate set after accepted GPU epistemic execution.
3929    ///
3930    /// Satisfiable candidates are scored through the existing GPU CDCL SAT path.
3931    /// Unsatisfiable candidates are pruned through the existing workspace-backed
3932    /// GPU CDCL UNSAT path. The adapter records no CPU assignment or MaxSAT
3933    /// enumeration.
3934    pub fn solve_weighted_maxsat_search_with_gpu_execution_result(
3935        &mut self,
3936        provider: &CudaKernelProvider,
3937        result: &EpistemicGpuExecutionResult,
3938        workspace: &mut GpuCdclWorkspace,
3939        candidates: &[GpuSolverProductionMaxSatSearchCandidate<'_>],
3940    ) -> Result<GpuSolverProductionMaxSatReport> {
3941        self.with_trace_rollback(|this| {
3942            this.solve_weighted_maxsat_search_with_gpu_execution_result_impl(
3943                provider, result, workspace, candidates,
3944            )
3945        })
3946    }
3947
3948    fn solve_weighted_maxsat_search_with_gpu_execution_result_impl(
3949        &mut self,
3950        provider: &CudaKernelProvider,
3951        result: &EpistemicGpuExecutionResult,
3952        workspace: &mut GpuCdclWorkspace,
3953        candidates: &[GpuSolverProductionMaxSatSearchCandidate<'_>],
3954    ) -> Result<GpuSolverProductionMaxSatReport> {
3955        self.require_workspace_on_adapter_provider(workspace)?;
3956        self.require_weighted_maxsat_search_candidates_and_artifacts(workspace, candidates)?;
3957        let state = self.require_accepted_gpu_solver_evidence(provider, result)?;
3958        let events_before = self.trace.accepted_path_event_snapshot()?;
3959        let mut report = self.solve_weighted_maxsat_search_candidates(workspace, candidates, 0)?;
3960        report.candidate_evidence_records = 1;
3961        self.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
3962        self.record_accepted_gpu_candidate_state(&state)?;
3963        Ok(report)
3964    }
3965
3966    /// Search a bounded weighted MaxSAT candidate set once per accepted split-batch component.
3967    pub fn solve_weighted_maxsat_search_with_gpu_batch_execution_result(
3968        &mut self,
3969        provider: &CudaKernelProvider,
3970        evidence: GpuSolverProductionBatchExecutionEvidence<'_>,
3971        workspace: &mut GpuCdclWorkspace,
3972        candidates: &[GpuSolverProductionMaxSatSearchCandidate<'_>],
3973    ) -> Result<GpuSolverProductionMaxSatReport> {
3974        self.with_trace_rollback(|this| {
3975            this.require_workspace_on_adapter_provider(workspace)?;
3976            this.require_weighted_maxsat_search_candidates_and_artifacts(workspace, candidates)?;
3977            let results =
3978                this.accepted_solver_results_from_gpu_batch_execution_evidence(provider, evidence)?;
3979            let report = this
3980                .solve_multi_candidate_weighted_maxsat_search_with_gpu_execution_results(
3981                    provider, &results, workspace, candidates,
3982                )?;
3983            this.record_accepted_gpu_batch_candidate_evidence(results.len())?;
3984            Ok(report)
3985        })
3986    }
3987
3988    /// Search a bounded weighted MaxSAT candidate set once per accepted GPU evidence record.
3989    pub fn solve_multi_candidate_weighted_maxsat_search_with_gpu_execution_results(
3990        &mut self,
3991        provider: &CudaKernelProvider,
3992        results: &[&EpistemicGpuExecutionResult],
3993        workspace: &mut GpuCdclWorkspace,
3994        candidates: &[GpuSolverProductionMaxSatSearchCandidate<'_>],
3995    ) -> Result<GpuSolverProductionMaxSatReport> {
3996        self.with_trace_rollback(|this| {
3997            this.solve_multi_candidate_weighted_maxsat_search_with_gpu_execution_results_impl(
3998                provider, results, workspace, candidates,
3999            )
4000        })
4001    }
4002
4003    fn solve_multi_candidate_weighted_maxsat_search_with_gpu_execution_results_impl(
4004        &mut self,
4005        provider: &CudaKernelProvider,
4006        results: &[&EpistemicGpuExecutionResult],
4007        workspace: &mut GpuCdclWorkspace,
4008        candidates: &[GpuSolverProductionMaxSatSearchCandidate<'_>],
4009    ) -> Result<GpuSolverProductionMaxSatReport> {
4010        if results.is_empty() {
4011            return Err(XlogError::UnsupportedEpistemicConstruct {
4012                construct: "GPU solver production MaxSAT search".to_string(),
4013                context: "multi-candidate MaxSAT search requires at least one accepted GPU result"
4014                    .to_string(),
4015            });
4016        }
4017        self.require_workspace_on_adapter_provider(workspace)?;
4018        self.require_weighted_maxsat_search_candidates_and_artifacts(workspace, candidates)?;
4019        let states = self.require_accepted_gpu_solver_states(provider, results)?;
4020
4021        let mut report = GpuSolverProductionMaxSatReport::default();
4022        for state in &states {
4023            let events_before = self.trace.accepted_path_event_snapshot()?;
4024            let step_report =
4025                self.solve_weighted_maxsat_search_candidates(workspace, candidates, 0)?;
4026            checked_solver_report_counter_inc!(report, candidate_evidence_records);
4027            report.optimum_score = report.optimum_score.max(step_report.optimum_score);
4028            checked_solver_report_counter_add!(
4029                report,
4030                candidates_checked,
4031                step_report.candidates_checked
4032            );
4033            checked_solver_report_counter_add!(
4034                report,
4035                satisfiable_candidates,
4036                step_report.satisfiable_candidates
4037            );
4038            checked_solver_report_counter_add!(
4039                report,
4040                unsat_candidates_pruned,
4041                step_report.unsat_candidates_pruned
4042            );
4043            checked_solver_report_counter_add!(
4044                report,
4045                gpu_cdcl_candidate_encodes,
4046                step_report.gpu_cdcl_candidate_encodes
4047            );
4048            checked_solver_report_counter_add!(
4049                report,
4050                gpu_cdcl_candidate_solves,
4051                step_report.gpu_cdcl_candidate_solves
4052            );
4053            checked_solver_report_counter_add!(
4054                report,
4055                frontier_upper_bound_certificates,
4056                step_report.frontier_upper_bound_certificates
4057            );
4058            self.record_accepted_gpu_solver_production_path_events_since(events_before, state)?;
4059            self.record_accepted_gpu_candidate_state(state)?;
4060        }
4061
4062        Ok(report)
4063    }
4064
4065    /// Encode weighted soft-clause selections, then search them after accepted GPU evidence.
4066    ///
4067    /// Candidate construction is bounded by caller-declared selections. The adapter
4068    /// builds satisfaction CNFs for those selections, uploads them through the existing
4069    /// GPU CNF layout, and dispatches SAT/UNSAT certification through GPU CDCL. It
4070    /// performs no CPU assignment or MaxSAT subset enumeration.
4071    pub fn solve_weighted_maxsat_encoded_search_with_gpu_execution_result(
4072        &mut self,
4073        provider: &CudaKernelProvider,
4074        result: &EpistemicGpuExecutionResult,
4075        workspace: &mut GpuCdclWorkspace,
4076        weighted: &SolveInstance,
4077        branch_var_limit: &TrackedCudaSlice<u32>,
4078        selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
4079    ) -> Result<GpuSolverProductionMaxSatReport> {
4080        self.with_trace_rollback(|this| {
4081            this.solve_weighted_maxsat_encoded_search_with_gpu_execution_result_impl(
4082                provider,
4083                result,
4084                workspace,
4085                weighted,
4086                branch_var_limit,
4087                selections,
4088            )
4089        })
4090    }
4091
4092    fn solve_weighted_maxsat_encoded_search_with_gpu_execution_result_impl(
4093        &mut self,
4094        provider: &CudaKernelProvider,
4095        result: &EpistemicGpuExecutionResult,
4096        workspace: &mut GpuCdclWorkspace,
4097        weighted: &SolveInstance,
4098        branch_var_limit: &TrackedCudaSlice<u32>,
4099        selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
4100    ) -> Result<GpuSolverProductionMaxSatReport> {
4101        self.require_workspace_on_adapter_provider(workspace)?;
4102        self.require_weighted_maxsat_encoded_search_inputs_and_artifacts(
4103            workspace,
4104            weighted,
4105            branch_var_limit,
4106            selections,
4107        )?;
4108        let state = self.require_accepted_gpu_solver_evidence(provider, result)?;
4109        let events_before = self.trace.accepted_path_event_snapshot()?;
4110        let encodes_before = self.trace.gpu_maxsat_candidate_encodes;
4111        let certificates_before = self.trace.gpu_maxsat_frontier_upper_bound_certificates;
4112        let encoded = self.encode_weighted_maxsat_search_candidates(weighted, selections)?;
4113        let frontier_upper_bound_certificates = Self::checked_report_counter_delta(
4114            self.trace.gpu_maxsat_frontier_upper_bound_certificates,
4115            certificates_before,
4116            "gpu_maxsat_frontier_upper_bound_certificates",
4117        )?;
4118        let search_candidates: Vec<_> = encoded
4119            .iter()
4120            .map(|candidate| GpuSolverProductionMaxSatSearchCandidate {
4121                score: candidate.score,
4122                cnf: &candidate.cnf,
4123                branch_var_limit,
4124                status: candidate.status,
4125            })
4126            .collect();
4127        let mut report = self.solve_weighted_maxsat_search_candidates(
4128            workspace,
4129            &search_candidates,
4130            frontier_upper_bound_certificates,
4131        )?;
4132        report.candidate_evidence_records = 1;
4133        report.gpu_cdcl_candidate_encodes = Self::checked_report_counter_delta(
4134            self.trace.gpu_maxsat_candidate_encodes,
4135            encodes_before,
4136            "gpu_cdcl_candidate_encodes",
4137        )?;
4138        self.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
4139        self.record_accepted_gpu_candidate_state(&state)?;
4140        Ok(report)
4141    }
4142
4143    /// Encode weighted soft-clause selections, then search once per accepted GPU evidence record.
4144    ///
4145    /// This is the multi-candidate scheduler-facing variant of the bounded encoded
4146    /// MaxSAT search adapter. It validates all accepted GPU epistemic evidence up
4147    /// front, encodes the caller-declared selections through the existing GPU CNF
4148    /// layout for each accepted record, and dispatches each candidate through GPU
4149    /// CDCL SAT/UNSAT certification without CPU assignment or MaxSAT enumeration.
4150    pub fn solve_multi_candidate_weighted_maxsat_encoded_search_with_gpu_execution_results(
4151        &mut self,
4152        provider: &CudaKernelProvider,
4153        results: &[&EpistemicGpuExecutionResult],
4154        workspace: &mut GpuCdclWorkspace,
4155        weighted: &SolveInstance,
4156        branch_var_limit: &TrackedCudaSlice<u32>,
4157        selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
4158    ) -> Result<GpuSolverProductionMaxSatReport> {
4159        self.with_trace_rollback(|this| {
4160            this.solve_multi_candidate_weighted_maxsat_encoded_search_with_gpu_execution_results_impl(
4161                provider,
4162                results,
4163                workspace,
4164                weighted,
4165                branch_var_limit,
4166                selections,
4167            )
4168        })
4169    }
4170
4171    fn solve_multi_candidate_weighted_maxsat_encoded_search_with_gpu_execution_results_impl(
4172        &mut self,
4173        provider: &CudaKernelProvider,
4174        results: &[&EpistemicGpuExecutionResult],
4175        workspace: &mut GpuCdclWorkspace,
4176        weighted: &SolveInstance,
4177        branch_var_limit: &TrackedCudaSlice<u32>,
4178        selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
4179    ) -> Result<GpuSolverProductionMaxSatReport> {
4180        if results.is_empty() {
4181            return Err(XlogError::UnsupportedEpistemicConstruct {
4182                construct: "GPU solver production MaxSAT encoding".to_string(),
4183                context:
4184                    "multi-candidate weighted MaxSAT encoded search requires at least one accepted GPU result"
4185                        .to_string(),
4186            });
4187        }
4188        self.require_workspace_on_adapter_provider(workspace)?;
4189        self.require_weighted_maxsat_encoded_search_inputs_and_artifacts(
4190            workspace,
4191            weighted,
4192            branch_var_limit,
4193            selections,
4194        )?;
4195        let states = self.require_accepted_gpu_solver_states(provider, results)?;
4196
4197        let mut report = GpuSolverProductionMaxSatReport::default();
4198        for state in &states {
4199            let events_before = self.trace.accepted_path_event_snapshot()?;
4200            let encodes_before = self.trace.gpu_maxsat_candidate_encodes;
4201            let certificates_before = self.trace.gpu_maxsat_frontier_upper_bound_certificates;
4202            let encoded = self.encode_weighted_maxsat_search_candidates(weighted, selections)?;
4203            let frontier_upper_bound_certificates = Self::checked_report_counter_delta(
4204                self.trace.gpu_maxsat_frontier_upper_bound_certificates,
4205                certificates_before,
4206                "gpu_maxsat_frontier_upper_bound_certificates",
4207            )?;
4208            let search_candidates: Vec<_> = encoded
4209                .iter()
4210                .map(|candidate| GpuSolverProductionMaxSatSearchCandidate {
4211                    score: candidate.score,
4212                    cnf: &candidate.cnf,
4213                    branch_var_limit,
4214                    status: candidate.status,
4215                })
4216                .collect();
4217            let step_report = self.solve_weighted_maxsat_search_candidates(
4218                workspace,
4219                &search_candidates,
4220                frontier_upper_bound_certificates,
4221            )?;
4222            checked_solver_report_counter_inc!(report, candidate_evidence_records);
4223            report.optimum_score = report.optimum_score.max(step_report.optimum_score);
4224            checked_solver_report_counter_add!(
4225                report,
4226                candidates_checked,
4227                step_report.candidates_checked
4228            );
4229            checked_solver_report_counter_add!(
4230                report,
4231                satisfiable_candidates,
4232                step_report.satisfiable_candidates
4233            );
4234            checked_solver_report_counter_add!(
4235                report,
4236                unsat_candidates_pruned,
4237                step_report.unsat_candidates_pruned
4238            );
4239            let encoded_delta = Self::checked_report_counter_delta(
4240                self.trace.gpu_maxsat_candidate_encodes,
4241                encodes_before,
4242                "gpu_cdcl_candidate_encodes",
4243            )?;
4244            checked_solver_report_counter_add!(report, gpu_cdcl_candidate_encodes, encoded_delta);
4245            checked_solver_report_counter_add!(
4246                report,
4247                gpu_cdcl_candidate_solves,
4248                step_report.gpu_cdcl_candidate_solves
4249            );
4250            checked_solver_report_counter_add!(
4251                report,
4252                frontier_upper_bound_certificates,
4253                step_report.frontier_upper_bound_certificates
4254            );
4255            self.record_accepted_gpu_solver_production_path_events_since(events_before, state)?;
4256            self.record_accepted_gpu_candidate_state(state)?;
4257        }
4258
4259        Ok(report)
4260    }
4261
4262    /// Encode weighted soft-clause selections, then search once per accepted split-batch component.
4263    ///
4264    /// The batch evidence must prove every split component reused the existing
4265    /// single-plan GPU runtime path before each component is delegated to the
4266    /// existing multi-candidate weighted MaxSAT encoding adapter.
4267    pub fn solve_weighted_maxsat_encoded_search_with_gpu_batch_execution_result(
4268        &mut self,
4269        provider: &CudaKernelProvider,
4270        evidence: GpuSolverProductionBatchExecutionEvidence<'_>,
4271        workspace: &mut GpuCdclWorkspace,
4272        weighted: &SolveInstance,
4273        branch_var_limit: &TrackedCudaSlice<u32>,
4274        selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
4275    ) -> Result<GpuSolverProductionMaxSatReport> {
4276        self.with_trace_rollback(|this| {
4277            this.require_workspace_on_adapter_provider(workspace)?;
4278            this.require_weighted_maxsat_encoded_search_inputs_and_artifacts(
4279                workspace,
4280                weighted,
4281                branch_var_limit,
4282                selections,
4283            )?;
4284            let results =
4285                this.accepted_solver_results_from_gpu_batch_execution_evidence(provider, evidence)?;
4286            let report = this
4287                .solve_multi_candidate_weighted_maxsat_encoded_search_with_gpu_execution_results(
4288                    provider,
4289                    &results,
4290                    workspace,
4291                    weighted,
4292                    branch_var_limit,
4293                    selections,
4294                )?;
4295            this.record_accepted_gpu_batch_candidate_evidence(results.len())?;
4296            Ok(report)
4297        })
4298    }
4299
4300    fn add_maxsat_schedule_step_report(
4301        report: &mut GpuSolverProductionMaxSatScheduleReport,
4302        step_report: GpuSolverProductionMaxSatReport,
4303    ) -> Result<()> {
4304        report.optimum_score = report.optimum_score.max(step_report.optimum_score);
4305        checked_solver_report_counter_add!(
4306            report,
4307            candidates_checked,
4308            step_report.candidates_checked
4309        );
4310        checked_solver_report_counter_add!(
4311            report,
4312            satisfiable_candidates,
4313            step_report.satisfiable_candidates
4314        );
4315        checked_solver_report_counter_add!(
4316            report,
4317            unsat_candidates_pruned,
4318            step_report.unsat_candidates_pruned
4319        );
4320        checked_solver_report_counter_add!(
4321            report,
4322            gpu_cdcl_candidate_encodes,
4323            step_report.gpu_cdcl_candidate_encodes
4324        );
4325        checked_solver_report_counter_add!(
4326            report,
4327            gpu_cdcl_candidate_solves,
4328            step_report.gpu_cdcl_candidate_solves
4329        );
4330        checked_solver_report_counter_add!(
4331            report,
4332            frontier_upper_bound_certificates,
4333            step_report.frontier_upper_bound_certificates
4334        );
4335        Ok(())
4336    }
4337
4338    fn solve_maxsat_schedule_jobs(
4339        &mut self,
4340        workspace: &mut GpuCdclWorkspace,
4341        jobs: &[GpuSolverProductionMaxSatScheduleJob<'_>],
4342    ) -> Result<GpuSolverProductionMaxSatScheduleReport> {
4343        self.require_workspace_on_adapter_provider(workspace)?;
4344        Self::require_maxsat_schedule_jobs(jobs)?;
4345        self.require_maxsat_schedule_job_artifacts(workspace, jobs)?;
4346
4347        let mut report = GpuSolverProductionMaxSatScheduleReport::default();
4348        for job in jobs {
4349            checked_solver_trace_counter_inc!(self, gpu_maxsat_scheduler_jobs);
4350            checked_solver_report_counter_inc!(report, jobs);
4351
4352            match job {
4353                GpuSolverProductionMaxSatScheduleJob::CandidateSet { candidates } => {
4354                    checked_solver_trace_counter_inc!(
4355                        self,
4356                        gpu_maxsat_scheduler_candidate_set_jobs
4357                    );
4358                    checked_solver_report_counter_inc!(report, candidate_set_jobs);
4359                    let step_report = self.solve_weighted_maxsat_candidates(candidates, 0)?;
4360                    Self::add_maxsat_schedule_step_report(&mut report, step_report)?;
4361                }
4362                GpuSolverProductionMaxSatScheduleJob::Search { candidates } => {
4363                    checked_solver_trace_counter_inc!(self, gpu_maxsat_scheduler_search_jobs);
4364                    checked_solver_report_counter_inc!(report, search_jobs);
4365                    let step_report =
4366                        self.solve_weighted_maxsat_search_candidates(workspace, candidates, 0)?;
4367                    Self::add_maxsat_schedule_step_report(&mut report, step_report)?;
4368                }
4369                GpuSolverProductionMaxSatScheduleJob::EncodedSearch {
4370                    weighted,
4371                    branch_var_limit,
4372                    selections,
4373                } => {
4374                    checked_solver_trace_counter_inc!(
4375                        self,
4376                        gpu_maxsat_scheduler_encoded_search_jobs
4377                    );
4378                    checked_solver_report_counter_inc!(report, encoded_search_jobs);
4379                    let encodes_before = self.trace.gpu_maxsat_candidate_encodes;
4380                    let certificates_before =
4381                        self.trace.gpu_maxsat_frontier_upper_bound_certificates;
4382                    let encoded =
4383                        self.encode_weighted_maxsat_search_candidates(weighted, selections)?;
4384                    let frontier_upper_bound_certificates = Self::checked_report_counter_delta(
4385                        self.trace.gpu_maxsat_frontier_upper_bound_certificates,
4386                        certificates_before,
4387                        "gpu_maxsat_frontier_upper_bound_certificates",
4388                    )?;
4389                    let search_candidates: Vec<_> = encoded
4390                        .iter()
4391                        .map(|candidate| GpuSolverProductionMaxSatSearchCandidate {
4392                            score: candidate.score,
4393                            cnf: &candidate.cnf,
4394                            branch_var_limit,
4395                            status: candidate.status,
4396                        })
4397                        .collect();
4398                    let mut step_report = self.solve_weighted_maxsat_search_candidates(
4399                        workspace,
4400                        &search_candidates,
4401                        frontier_upper_bound_certificates,
4402                    )?;
4403                    step_report.gpu_cdcl_candidate_encodes = Self::checked_report_counter_delta(
4404                        self.trace.gpu_maxsat_candidate_encodes,
4405                        encodes_before,
4406                        "gpu_cdcl_candidate_encodes",
4407                    )?;
4408                    Self::add_maxsat_schedule_step_report(&mut report, step_report)?;
4409                }
4410                GpuSolverProductionMaxSatScheduleJob::Unknown { reason } => {
4411                    if reason.trim().is_empty() {
4412                        return Err(XlogError::UnsupportedEpistemicConstruct {
4413                            construct: "GPU solver production MaxSAT scheduler".to_string(),
4414                            context: "UNKNOWN scheduler status requires a diagnostic reason"
4415                                .to_string(),
4416                        });
4417                    }
4418                    checked_solver_trace_counter_inc!(
4419                        self,
4420                        gpu_maxsat_scheduler_unknown_status_jobs
4421                    );
4422                    checked_solver_report_counter_inc!(report, unknown_jobs);
4423                }
4424                GpuSolverProductionMaxSatScheduleJob::Timeout { budget_micros } => {
4425                    if *budget_micros == 0 {
4426                        return Err(XlogError::UnsupportedEpistemicConstruct {
4427                            construct: "GPU solver production MaxSAT scheduler".to_string(),
4428                            context: "TIMEOUT scheduler status requires a nonzero budget"
4429                                .to_string(),
4430                        });
4431                    }
4432                    checked_solver_trace_counter_inc!(
4433                        self,
4434                        gpu_maxsat_scheduler_timeout_status_jobs
4435                    );
4436                    checked_solver_report_counter_inc!(report, timeout_jobs);
4437                }
4438            }
4439        }
4440
4441        Ok(report)
4442    }
4443
4444    fn require_maxsat_schedule_jobs(
4445        jobs: &[GpuSolverProductionMaxSatScheduleJob<'_>],
4446    ) -> Result<()> {
4447        if jobs.is_empty() {
4448            return Err(XlogError::UnsupportedEpistemicConstruct {
4449                construct: "GPU solver production MaxSAT scheduler".to_string(),
4450                context: "accepted MaxSAT scheduler requires at least one GPU job".to_string(),
4451            });
4452        }
4453
4454        for job in jobs {
4455            match job {
4456                GpuSolverProductionMaxSatScheduleJob::CandidateSet { candidates } => {
4457                    Self::require_weighted_maxsat_candidates(candidates)?;
4458                }
4459                GpuSolverProductionMaxSatScheduleJob::Search { candidates } => {
4460                    Self::require_weighted_maxsat_search_candidates(candidates)?;
4461                }
4462                GpuSolverProductionMaxSatScheduleJob::EncodedSearch {
4463                    weighted,
4464                    selections,
4465                    ..
4466                } => {
4467                    Self::require_weighted_maxsat_encoding_inputs(weighted, selections)?;
4468                }
4469                GpuSolverProductionMaxSatScheduleJob::Unknown { reason } => {
4470                    if reason.trim().is_empty() {
4471                        return Err(XlogError::UnsupportedEpistemicConstruct {
4472                            construct: "GPU solver production MaxSAT scheduler".to_string(),
4473                            context: "UNKNOWN scheduler status requires a diagnostic reason"
4474                                .to_string(),
4475                        });
4476                    }
4477                }
4478                GpuSolverProductionMaxSatScheduleJob::Timeout { budget_micros } => {
4479                    if *budget_micros == 0 {
4480                        return Err(XlogError::UnsupportedEpistemicConstruct {
4481                            construct: "GPU solver production MaxSAT scheduler".to_string(),
4482                            context: "TIMEOUT scheduler status requires a nonzero budget"
4483                                .to_string(),
4484                        });
4485                    }
4486                }
4487            }
4488        }
4489        Ok(())
4490    }
4491
4492    fn require_maxsat_schedule_job_artifacts(
4493        &self,
4494        workspace: &GpuCdclWorkspace,
4495        jobs: &[GpuSolverProductionMaxSatScheduleJob<'_>],
4496    ) -> Result<()> {
4497        for job in jobs {
4498            match job {
4499                GpuSolverProductionMaxSatScheduleJob::CandidateSet { candidates } => {
4500                    self.require_weighted_maxsat_candidate_artifacts(candidates)?;
4501                }
4502                GpuSolverProductionMaxSatScheduleJob::Search { candidates } => {
4503                    self.require_weighted_maxsat_search_candidates_and_artifacts(
4504                        workspace, candidates,
4505                    )?;
4506                }
4507                GpuSolverProductionMaxSatScheduleJob::EncodedSearch {
4508                    weighted,
4509                    branch_var_limit,
4510                    ..
4511                } => {
4512                    self.require_workspace_capacity_for_weighted_maxsat_encoding(
4513                        workspace,
4514                        weighted,
4515                        "GPU solver production MaxSAT scheduler",
4516                    )?;
4517                    self.require_branch_var_limit_on_adapter_provider(
4518                        branch_var_limit,
4519                        "GPU solver production MaxSAT scheduler",
4520                    )?;
4521                }
4522                GpuSolverProductionMaxSatScheduleJob::Unknown { .. }
4523                | GpuSolverProductionMaxSatScheduleJob::Timeout { .. } => {}
4524            }
4525        }
4526        Ok(())
4527    }
4528
4529    /// Execute a heterogeneous MaxSAT schedule once per accepted GPU evidence record.
4530    ///
4531    /// The scheduler is a thin production-path adapter: it validates accepted
4532    /// epistemic GPU execution up front, then dispatches candidate-set,
4533    /// search-pruning, and weighted encoded-search jobs through the existing GPU
4534    /// CNF/CDCL helpers. UNKNOWN and TIMEOUT jobs are status propagation records;
4535    /// they never fall back to CPU assignment or MaxSAT enumeration.
4536    pub fn solve_maxsat_schedule_with_gpu_execution_results(
4537        &mut self,
4538        provider: &CudaKernelProvider,
4539        results: &[&EpistemicGpuExecutionResult],
4540        workspace: &mut GpuCdclWorkspace,
4541        jobs: &[GpuSolverProductionMaxSatScheduleJob<'_>],
4542    ) -> Result<GpuSolverProductionMaxSatScheduleReport> {
4543        self.with_trace_rollback(|this| {
4544            this.solve_maxsat_schedule_with_gpu_execution_results_impl(
4545                provider, results, workspace, jobs,
4546            )
4547        })
4548    }
4549
4550    fn solve_maxsat_schedule_with_gpu_execution_results_impl(
4551        &mut self,
4552        provider: &CudaKernelProvider,
4553        results: &[&EpistemicGpuExecutionResult],
4554        workspace: &mut GpuCdclWorkspace,
4555        jobs: &[GpuSolverProductionMaxSatScheduleJob<'_>],
4556    ) -> Result<GpuSolverProductionMaxSatScheduleReport> {
4557        if results.is_empty() {
4558            return Err(XlogError::UnsupportedEpistemicConstruct {
4559                construct: "GPU solver production MaxSAT scheduler".to_string(),
4560                context: "MaxSAT scheduler requires at least one accepted GPU result".to_string(),
4561            });
4562        }
4563        Self::require_maxsat_schedule_jobs(jobs)?;
4564        self.require_workspace_on_adapter_provider(workspace)?;
4565        self.require_maxsat_schedule_job_artifacts(workspace, jobs)?;
4566        let states = self.require_accepted_gpu_solver_states(provider, results)?;
4567
4568        let mut report = GpuSolverProductionMaxSatScheduleReport::default();
4569        for state in &states {
4570            let events_before = self.trace.accepted_path_event_snapshot()?;
4571            let step_report = self.solve_maxsat_schedule_jobs(workspace, jobs)?;
4572            checked_solver_report_counter_inc!(report, candidate_evidence_records);
4573            checked_solver_report_counter_add!(report, jobs, step_report.jobs);
4574            checked_solver_report_counter_add!(
4575                report,
4576                candidate_set_jobs,
4577                step_report.candidate_set_jobs
4578            );
4579            checked_solver_report_counter_add!(report, search_jobs, step_report.search_jobs);
4580            checked_solver_report_counter_add!(
4581                report,
4582                encoded_search_jobs,
4583                step_report.encoded_search_jobs
4584            );
4585            checked_solver_report_counter_add!(report, unknown_jobs, step_report.unknown_jobs);
4586            checked_solver_report_counter_add!(report, timeout_jobs, step_report.timeout_jobs);
4587            Self::add_maxsat_schedule_step_report(
4588                &mut report,
4589                GpuSolverProductionMaxSatReport {
4590                    optimum_score: step_report.optimum_score,
4591                    candidates_checked: step_report.candidates_checked,
4592                    satisfiable_candidates: step_report.satisfiable_candidates,
4593                    unsat_candidates_pruned: step_report.unsat_candidates_pruned,
4594                    gpu_cdcl_candidate_encodes: step_report.gpu_cdcl_candidate_encodes,
4595                    gpu_cdcl_candidate_solves: step_report.gpu_cdcl_candidate_solves,
4596                    frontier_upper_bound_certificates: step_report
4597                        .frontier_upper_bound_certificates,
4598                    ..GpuSolverProductionMaxSatReport::default()
4599                },
4600            )?;
4601            self.record_accepted_gpu_solver_production_path_events_since(events_before, state)?;
4602            self.record_accepted_gpu_candidate_state(state)?;
4603        }
4604
4605        Ok(report)
4606    }
4607
4608    /// Execute a heterogeneous MaxSAT schedule once per accepted split-batch component.
4609    ///
4610    /// This preserves the scheduler's existing GPU CNF/CDCL dispatch behavior while requiring
4611    /// the typed `Gpu`/`RejectUnsupported` policy plus observed GPU dispatch and candidate
4612    /// accounting, scoped transfer accounting, and CUDA-event timing before any scheduled job
4613    /// runs.
4614    pub fn solve_maxsat_schedule_with_gpu_batch_execution_result(
4615        &mut self,
4616        provider: &CudaKernelProvider,
4617        evidence: GpuSolverProductionBatchExecutionEvidence<'_>,
4618        workspace: &mut GpuCdclWorkspace,
4619        jobs: &[GpuSolverProductionMaxSatScheduleJob<'_>],
4620    ) -> Result<GpuSolverProductionMaxSatScheduleReport> {
4621        self.with_trace_rollback(|this| {
4622            Self::require_maxsat_schedule_jobs(jobs)?;
4623            this.require_workspace_on_adapter_provider(workspace)?;
4624            this.require_maxsat_schedule_job_artifacts(workspace, jobs)?;
4625            let results =
4626                this.accepted_solver_results_from_gpu_batch_execution_evidence(provider, evidence)?;
4627            let report = this.solve_maxsat_schedule_with_gpu_execution_results(
4628                provider, &results, workspace, jobs,
4629            )?;
4630            this.record_accepted_gpu_batch_candidate_evidence(results.len())?;
4631            Ok(report)
4632        })
4633    }
4634
4635    fn solve_portfolio_jobs(
4636        &mut self,
4637        jobs: &[GpuSolverProductionPortfolioJob<'_>],
4638    ) -> Result<GpuSolverProductionPortfolioReport> {
4639        self.require_portfolio_jobs_and_artifacts(jobs)?;
4640
4641        let mut report = GpuSolverProductionPortfolioReport::default();
4642        for job in jobs {
4643            checked_solver_trace_counter_inc!(self, gpu_portfolio_jobs);
4644            checked_solver_report_counter_inc!(report, jobs);
4645
4646            match job {
4647                GpuSolverProductionPortfolioJob::Sat {
4648                    cnf,
4649                    branch_var_limit,
4650                } => {
4651                    let _assignment = self
4652                        .solver
4653                        .solve_expect_sat_with_branch_limit(cnf, branch_var_limit)?;
4654                    checked_solver_trace_counter_inc!(self, gpu_cdcl_sat_solves);
4655                    checked_solver_trace_counter_inc!(self, gpu_portfolio_sat_jobs);
4656                    checked_solver_report_counter_inc!(report, sat_jobs);
4657                }
4658                GpuSolverProductionPortfolioJob::MaxSat { candidates } => {
4659                    let maxsat = self.solve_weighted_maxsat_candidates(candidates, 0)?;
4660                    checked_solver_trace_counter_inc!(self, gpu_portfolio_maxsat_jobs);
4661                    checked_solver_report_counter_inc!(report, maxsat_jobs);
4662                    Self::add_portfolio_maxsat_report(&mut report, maxsat)?;
4663                }
4664                GpuSolverProductionPortfolioJob::EncodedMaxSat {
4665                    weighted,
4666                    branch_var_limit,
4667                    selections,
4668                } => {
4669                    let encodes_before = self.trace.gpu_maxsat_candidate_encodes;
4670                    let certificates_before =
4671                        self.trace.gpu_maxsat_frontier_upper_bound_certificates;
4672                    let encoded =
4673                        self.encode_weighted_maxsat_search_candidates(weighted, selections)?;
4674                    let frontier_upper_bound_certificates = Self::checked_report_counter_delta(
4675                        self.trace.gpu_maxsat_frontier_upper_bound_certificates,
4676                        certificates_before,
4677                        "gpu_maxsat_frontier_upper_bound_certificates",
4678                    )?;
4679                    let search_candidates: Vec<_> = encoded
4680                        .iter()
4681                        .map(|candidate| GpuSolverProductionMaxSatSearchCandidate {
4682                            score: candidate.score,
4683                            cnf: &candidate.cnf,
4684                            branch_var_limit,
4685                            status: candidate.status,
4686                        })
4687                        .collect();
4688                    let mut workspace = self.new_workspace(
4689                        weighted.num_vars,
4690                        Self::checked_workspace_clause_cap(weighted)?,
4691                    )?;
4692                    let mut maxsat = self.solve_weighted_maxsat_search_candidates(
4693                        &mut workspace,
4694                        &search_candidates,
4695                        frontier_upper_bound_certificates,
4696                    )?;
4697                    maxsat.gpu_cdcl_candidate_encodes = Self::checked_report_counter_delta(
4698                        self.trace.gpu_maxsat_candidate_encodes,
4699                        encodes_before,
4700                        "gpu_cdcl_candidate_encodes",
4701                    )?;
4702                    checked_solver_trace_counter_inc!(self, gpu_portfolio_maxsat_jobs);
4703                    checked_solver_report_counter_inc!(report, maxsat_jobs);
4704                    Self::add_portfolio_maxsat_report(&mut report, maxsat)?;
4705                }
4706                GpuSolverProductionPortfolioJob::Unknown { .. } => {
4707                    checked_solver_trace_counter_inc!(self, gpu_portfolio_unknown_status_jobs);
4708                    checked_solver_report_counter_inc!(report, unknown_jobs);
4709                }
4710                GpuSolverProductionPortfolioJob::Timeout { .. } => {
4711                    checked_solver_trace_counter_inc!(self, gpu_portfolio_timeout_status_jobs);
4712                    checked_solver_report_counter_inc!(report, timeout_jobs);
4713                }
4714            }
4715        }
4716
4717        Ok(report)
4718    }
4719
4720    fn require_portfolio_jobs(jobs: &[GpuSolverProductionPortfolioJob<'_>]) -> Result<()> {
4721        if jobs.is_empty() {
4722            return Err(XlogError::UnsupportedEpistemicConstruct {
4723                construct: "GPU solver production portfolio".to_string(),
4724                context: "accepted solver portfolio requires at least one GPU job".to_string(),
4725            });
4726        }
4727
4728        for job in jobs {
4729            match job {
4730                GpuSolverProductionPortfolioJob::Sat { .. } => {}
4731                GpuSolverProductionPortfolioJob::MaxSat { candidates } => {
4732                    Self::require_weighted_maxsat_candidates(candidates)?;
4733                }
4734                GpuSolverProductionPortfolioJob::EncodedMaxSat {
4735                    weighted,
4736                    selections,
4737                    ..
4738                } => {
4739                    Self::require_weighted_maxsat_encoding_inputs(weighted, selections)?;
4740                    Self::checked_workspace_clause_cap(weighted)?;
4741                }
4742                GpuSolverProductionPortfolioJob::Unknown { reason } => {
4743                    if reason.trim().is_empty() {
4744                        return Err(XlogError::UnsupportedEpistemicConstruct {
4745                            construct: "GPU solver production portfolio".to_string(),
4746                            context: "UNKNOWN portfolio status requires a diagnostic reason"
4747                                .to_string(),
4748                        });
4749                    }
4750                }
4751                GpuSolverProductionPortfolioJob::Timeout { budget_micros } => {
4752                    if *budget_micros == 0 {
4753                        return Err(XlogError::UnsupportedEpistemicConstruct {
4754                            construct: "GPU solver production portfolio".to_string(),
4755                            context: "TIMEOUT portfolio status requires a nonzero budget"
4756                                .to_string(),
4757                        });
4758                    }
4759                }
4760            }
4761        }
4762        Ok(())
4763    }
4764
4765    fn require_portfolio_job_artifacts(
4766        &self,
4767        jobs: &[GpuSolverProductionPortfolioJob<'_>],
4768    ) -> Result<()> {
4769        for job in jobs {
4770            match job {
4771                GpuSolverProductionPortfolioJob::Sat {
4772                    cnf,
4773                    branch_var_limit,
4774                } => {
4775                    self.require_solver_artifact_on_adapter_provider(
4776                        cnf,
4777                        branch_var_limit,
4778                        "GPU solver production portfolio",
4779                    )?;
4780                }
4781                GpuSolverProductionPortfolioJob::MaxSat { candidates } => {
4782                    self.require_weighted_maxsat_candidate_artifacts(candidates)?;
4783                }
4784                GpuSolverProductionPortfolioJob::EncodedMaxSat {
4785                    branch_var_limit, ..
4786                } => {
4787                    self.require_branch_var_limit_on_adapter_provider(
4788                        branch_var_limit,
4789                        "GPU solver production portfolio",
4790                    )?;
4791                }
4792                GpuSolverProductionPortfolioJob::Unknown { .. }
4793                | GpuSolverProductionPortfolioJob::Timeout { .. } => {}
4794            }
4795        }
4796        Ok(())
4797    }
4798
4799    fn require_portfolio_jobs_and_artifacts(
4800        &self,
4801        jobs: &[GpuSolverProductionPortfolioJob<'_>],
4802    ) -> Result<()> {
4803        Self::require_portfolio_jobs(jobs)?;
4804        self.require_portfolio_job_artifacts(jobs)
4805    }
4806
4807    fn add_portfolio_maxsat_report(
4808        report: &mut GpuSolverProductionPortfolioReport,
4809        maxsat: GpuSolverProductionMaxSatReport,
4810    ) -> Result<()> {
4811        checked_solver_report_counter_add!(report, maxsat_optimum_scores, maxsat.optimum_score);
4812        checked_solver_report_counter_add!(
4813            report,
4814            maxsat_candidates_checked,
4815            maxsat.candidates_checked
4816        );
4817        checked_solver_report_counter_add!(
4818            report,
4819            maxsat_satisfiable_candidates,
4820            maxsat.satisfiable_candidates
4821        );
4822        checked_solver_report_counter_add!(
4823            report,
4824            maxsat_unsat_candidates_pruned,
4825            maxsat.unsat_candidates_pruned
4826        );
4827        checked_solver_report_counter_add!(
4828            report,
4829            maxsat_gpu_cdcl_candidate_encodes,
4830            maxsat.gpu_cdcl_candidate_encodes
4831        );
4832        checked_solver_report_counter_add!(
4833            report,
4834            maxsat_gpu_cdcl_candidate_solves,
4835            maxsat.gpu_cdcl_candidate_solves
4836        );
4837        checked_solver_report_counter_add!(
4838            report,
4839            maxsat_frontier_upper_bound_certificates,
4840            maxsat.frontier_upper_bound_certificates
4841        );
4842        Ok(())
4843    }
4844
4845    fn add_portfolio_report(
4846        report: &mut GpuSolverProductionPortfolioReport,
4847        step_report: GpuSolverProductionPortfolioReport,
4848    ) -> Result<()> {
4849        checked_solver_report_counter_add!(report, jobs, step_report.jobs);
4850        checked_solver_report_counter_add!(report, sat_jobs, step_report.sat_jobs);
4851        checked_solver_report_counter_add!(report, maxsat_jobs, step_report.maxsat_jobs);
4852        checked_solver_report_counter_add!(report, unknown_jobs, step_report.unknown_jobs);
4853        checked_solver_report_counter_add!(report, timeout_jobs, step_report.timeout_jobs);
4854        checked_solver_report_counter_add!(
4855            report,
4856            maxsat_optimum_scores,
4857            step_report.maxsat_optimum_scores
4858        );
4859        checked_solver_report_counter_add!(
4860            report,
4861            maxsat_candidates_checked,
4862            step_report.maxsat_candidates_checked
4863        );
4864        checked_solver_report_counter_add!(
4865            report,
4866            maxsat_satisfiable_candidates,
4867            step_report.maxsat_satisfiable_candidates
4868        );
4869        checked_solver_report_counter_add!(
4870            report,
4871            maxsat_unsat_candidates_pruned,
4872            step_report.maxsat_unsat_candidates_pruned
4873        );
4874        checked_solver_report_counter_add!(
4875            report,
4876            maxsat_gpu_cdcl_candidate_encodes,
4877            step_report.maxsat_gpu_cdcl_candidate_encodes
4878        );
4879        checked_solver_report_counter_add!(
4880            report,
4881            maxsat_gpu_cdcl_candidate_solves,
4882            step_report.maxsat_gpu_cdcl_candidate_solves
4883        );
4884        checked_solver_report_counter_add!(
4885            report,
4886            maxsat_frontier_upper_bound_certificates,
4887            step_report.maxsat_frontier_upper_bound_certificates
4888        );
4889        Ok(())
4890    }
4891
4892    /// Execute a bounded SAT/MaxSAT/status-aware portfolio after accepted GPU epistemic execution.
4893    ///
4894    /// The portfolio is a production adapter over existing GPU CDCL calls. It records
4895    /// per-job counters and rejects empty portfolios without falling back to the CPU
4896    /// semantic-oracle solver.
4897    pub fn solve_portfolio_with_gpu_execution_result(
4898        &mut self,
4899        provider: &CudaKernelProvider,
4900        result: &EpistemicGpuExecutionResult,
4901        jobs: &[GpuSolverProductionPortfolioJob<'_>],
4902    ) -> Result<GpuSolverProductionPortfolioReport> {
4903        self.with_trace_rollback(|this| {
4904            this.solve_portfolio_with_gpu_execution_result_impl(provider, result, jobs)
4905        })
4906    }
4907
4908    fn solve_portfolio_with_gpu_execution_result_impl(
4909        &mut self,
4910        provider: &CudaKernelProvider,
4911        result: &EpistemicGpuExecutionResult,
4912        jobs: &[GpuSolverProductionPortfolioJob<'_>],
4913    ) -> Result<GpuSolverProductionPortfolioReport> {
4914        self.require_portfolio_jobs_and_artifacts(jobs)?;
4915        let state = self.require_accepted_gpu_solver_evidence(provider, result)?;
4916
4917        let events_before = self.trace.accepted_path_event_snapshot()?;
4918        let mut report = self.solve_portfolio_jobs(jobs)?;
4919        report.candidate_evidence_records = 1;
4920
4921        self.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
4922        self.record_accepted_gpu_candidate_state(&state)?;
4923        Ok(report)
4924    }
4925
4926    /// Execute the same bounded portfolio once per accepted GPU epistemic candidate.
4927    pub fn solve_multi_candidate_portfolio_with_gpu_execution_results(
4928        &mut self,
4929        provider: &CudaKernelProvider,
4930        results: &[&EpistemicGpuExecutionResult],
4931        jobs: &[GpuSolverProductionPortfolioJob<'_>],
4932    ) -> Result<GpuSolverProductionPortfolioReport> {
4933        self.with_trace_rollback(|this| {
4934            this.solve_multi_candidate_portfolio_with_gpu_execution_results_impl(
4935                provider, results, jobs,
4936            )
4937        })
4938    }
4939
4940    fn solve_multi_candidate_portfolio_with_gpu_execution_results_impl(
4941        &mut self,
4942        provider: &CudaKernelProvider,
4943        results: &[&EpistemicGpuExecutionResult],
4944        jobs: &[GpuSolverProductionPortfolioJob<'_>],
4945    ) -> Result<GpuSolverProductionPortfolioReport> {
4946        if results.is_empty() {
4947            return Err(XlogError::UnsupportedEpistemicConstruct {
4948                construct: "GPU solver production portfolio".to_string(),
4949                context: "multi-candidate portfolio requires at least one accepted GPU result"
4950                    .to_string(),
4951            });
4952        }
4953        self.require_portfolio_jobs_and_artifacts(jobs)?;
4954        let states = self.require_accepted_gpu_solver_states(provider, results)?;
4955
4956        let mut report = GpuSolverProductionPortfolioReport::default();
4957        for state in &states {
4958            let events_before = self.trace.accepted_path_event_snapshot()?;
4959            let step_report = self.solve_portfolio_jobs(jobs)?;
4960            checked_solver_report_counter_inc!(report, candidate_evidence_records);
4961            Self::add_portfolio_report(&mut report, step_report)?;
4962            self.record_accepted_gpu_solver_production_path_events_since(events_before, state)?;
4963            self.record_accepted_gpu_candidate_state(state)?;
4964        }
4965
4966        Ok(report)
4967    }
4968
4969    /// Execute a bounded SAT/MaxSAT/status-aware portfolio for accepted split/batch evidence.
4970    ///
4971    /// The batch evidence must prove every split component reused the existing
4972    /// single-plan GPU runtime path before each component is delegated to the
4973    /// existing multi-candidate portfolio adapter.
4974    pub fn solve_portfolio_with_gpu_batch_execution_result(
4975        &mut self,
4976        provider: &CudaKernelProvider,
4977        evidence: GpuSolverProductionBatchExecutionEvidence<'_>,
4978        jobs: &[GpuSolverProductionPortfolioJob<'_>],
4979    ) -> Result<GpuSolverProductionPortfolioReport> {
4980        self.with_trace_rollback(|this| {
4981            this.require_portfolio_jobs_and_artifacts(jobs)?;
4982            let results =
4983                this.accepted_solver_results_from_gpu_batch_execution_evidence(provider, evidence)?;
4984            let report = this.solve_multi_candidate_portfolio_with_gpu_execution_results(
4985                provider, &results, jobs,
4986            )?;
4987            this.record_accepted_gpu_batch_candidate_evidence(results.len())?;
4988            Ok(report)
4989        })
4990    }
4991}
4992
4993fn require_accepted_gpu_solver_states(
4994    provider: &CudaKernelProvider,
4995    results: &[&EpistemicGpuExecutionResult],
4996) -> Result<Vec<GpuSolverAcceptedCandidateState>> {
4997    results
4998        .iter()
4999        .map(|result| require_accepted_gpu_solver_evidence(provider, result))
5000        .collect()
5001}
5002
5003fn require_accepted_gpu_solver_evidence(
5004    provider: &CudaKernelProvider,
5005    result: &EpistemicGpuExecutionResult,
5006) -> Result<GpuSolverAcceptedCandidateState> {
5007    let provider_identity = EpistemicGpuProviderIdentity::from_provider(provider);
5008    if result.provider_identity != provider_identity {
5009        return Err(XlogError::UnsupportedEpistemicConstruct {
5010            construct: "accepted GPU solver candidate evidence".to_string(),
5011            context: format!(
5012                "solver evidence provider mismatch: result device={} provider device={} \
5013                 result_device_ptr={} provider_device_ptr={} result_memory_ptr={} \
5014                 provider_memory_ptr={}",
5015                result.provider_identity.device_ordinal,
5016                provider_identity.device_ordinal,
5017                result.provider_identity.device_ptr,
5018                provider_identity.device_ptr,
5019                result.provider_identity.memory_ptr,
5020                provider_identity.memory_ptr
5021            ),
5022        });
5023    }
5024    // The runtime preflight types currently permit only GPU execution with
5025    // reject-unsupported behavior. Positive runtime certification below is
5026    // the falsifiable eligibility gate.
5027    if result.candidate_generation.literal_count == 0
5028        || result.prepared.preflight.tuple_membership_binding_count == 0
5029    {
5030        return Err(XlogError::UnsupportedEpistemicConstruct {
5031            construct: "accepted GPU solver candidate evidence".to_string(),
5032            context: format!(
5033                "solver evidence requires at least one GPU-validated epistemic literal and \
5034                 tuple-membership binding, got literals={} bindings={}",
5035                result.candidate_generation.literal_count,
5036                result.prepared.preflight.tuple_membership_binding_count
5037            ),
5038        });
5039    }
5040    if result.prepared.preflight.solver_assumption_binding_count == 0 {
5041        return Err(XlogError::UnsupportedEpistemicConstruct {
5042            construct: "accepted GPU solver candidate evidence".to_string(),
5043            context: "solver evidence requires planner-exported solver assumption bindings"
5044                .to_string(),
5045        });
5046    }
5047    if result.prepared.preflight.solver_required_capability_count
5048        < PRODUCTION_SOLVER_REQUIRED_CAPABILITY_COUNT as usize
5049        || result.prepared.preflight.solver_required_status_count
5050            < PRODUCTION_SOLVER_REQUIRED_STATUS_COUNT as usize
5051    {
5052        return Err(XlogError::UnsupportedEpistemicConstruct {
5053            construct: "accepted GPU solver candidate evidence".to_string(),
5054            context: format!(
5055                "solver evidence requires the production capability/status contract, got \
5056                 capabilities={} statuses={}",
5057                result.prepared.preflight.solver_required_capability_count,
5058                result.prepared.preflight.solver_required_status_count
5059            ),
5060        });
5061    }
5062    result.require_runtime_dispatch_certification()?;
5063    result
5064        .model_membership
5065        .require_stable_model_tuple_source()?;
5066    if result.constraint_validation.violated_constraint_relations != 0 {
5067        return Err(XlogError::UnsupportedEpistemicConstruct {
5068            construct: "accepted GPU solver candidate evidence".to_string(),
5069            context: format!(
5070                "solver evidence requires zero reduced constraint violations, got {} across {} \
5071                 checked constraint relations",
5072                result.constraint_validation.violated_constraint_relations,
5073                result.constraint_validation.checked_constraint_relations
5074            ),
5075        });
5076    }
5077    if result.constraint_validation.row_count_device_reads as usize
5078        > result.constraint_validation.checked_constraint_relations
5079    {
5080        return Err(XlogError::UnsupportedEpistemicConstruct {
5081            construct: "accepted GPU solver candidate evidence".to_string(),
5082            context: format!(
5083                "solver evidence constraint metadata reads cannot exceed checked reduced \
5084                 constraint relations, got reads={} checked={}",
5085                result.constraint_validation.row_count_device_reads,
5086                result.constraint_validation.checked_constraint_relations
5087            ),
5088        });
5089    }
5090    require_gpu_kernel_trace(
5091        "candidate generation",
5092        result.candidate_generation.kernel_launches,
5093        result.candidate_generation.host_write_ops,
5094        result.candidate_generation.kernel_timing,
5095    )?;
5096    require_gpu_kernel_trace(
5097        "candidate propagation",
5098        result.propagation.kernel_launches,
5099        result.propagation.host_write_ops,
5100        result.propagation.kernel_timing,
5101    )?;
5102    require_gpu_kernel_trace(
5103        "candidate validation",
5104        result.candidate_validation.kernel_launches,
5105        result.candidate_validation.host_write_ops,
5106        result.candidate_validation.kernel_timing,
5107    )?;
5108    require_gpu_kernel_trace(
5109        "model membership",
5110        result.model_membership.kernel_launches,
5111        result.model_membership.host_write_ops,
5112        result.model_membership.kernel_timing,
5113    )?;
5114    require_gpu_kernel_trace(
5115        "world-view validation",
5116        result.world_view_validation.kernel_launches,
5117        result.world_view_validation.host_write_ops,
5118        result.world_view_validation.kernel_timing,
5119    )?;
5120    require_gpu_kernel_trace(
5121        "accepted-candidate materialization",
5122        result.materialization.kernel_launches,
5123        result.materialization.host_write_ops,
5124        result.materialization.kernel_timing,
5125    )?;
5126    require_gpu_kernel_trace(
5127        "final-result materialization",
5128        result.final_result_materialization.kernel_launches,
5129        result.final_result_materialization.host_write_ops,
5130        result.final_result_materialization.kernel_timing,
5131    )?;
5132    require_gpu_kernel_trace(
5133        "final tuple materialization",
5134        result.final_tuple_materialization.kernel_launches,
5135        result.final_tuple_materialization.host_write_ops,
5136        result.final_tuple_materialization.kernel_timing,
5137    )?;
5138    // The runtime has already captured this via read_device_row_count during
5139    // the bounded final-result transfer; do not re-read it in the solver gate.
5140    let accepted_rows = result.final_result_transfer.final_output_rows;
5141    result
5142        .final_tuple_materialization
5143        .require_row_filter_materialization_evidence(
5144            "accepted GPU solver candidate evidence",
5145            accepted_rows,
5146        )?;
5147    if result.transfer_budget.tracked_dtoh_calls != 0
5148        || result.transfer_budget.tracked_htod_calls != 0
5149        || result.transfer_budget.tracked_data_plane_htod_calls != 0
5150        || result.transfer_budget.per_candidate_host_round_trips != 0
5151    {
5152        return Err(XlogError::UnsupportedEpistemicConstruct {
5153            construct: "accepted GPU solver candidate evidence".to_string(),
5154            context: format!(
5155                "solver evidence requires zero hot-path transfers outside bounded launch \
5156                 metadata, got dtoh_calls={}, htod_calls={}, data_plane_htod_calls={}, \
5157                 launch_metadata_htod_calls={}, per_candidate_round_trips={}",
5158                result.transfer_budget.tracked_dtoh_calls,
5159                result.transfer_budget.tracked_htod_calls,
5160                result.transfer_budget.tracked_data_plane_htod_calls,
5161                result.transfer_budget.tracked_launch_metadata_htod_calls,
5162                result.transfer_budget.per_candidate_host_round_trips
5163            ),
5164        });
5165    }
5166    require_accepted_gpu_solver_semantic_trace(result)?;
5167
5168    if accepted_rows == 0 {
5169        return Err(XlogError::UnsupportedEpistemicConstruct {
5170            construct: "accepted GPU solver candidate evidence".to_string(),
5171            context: "solver evidence requires non-empty accepted GPU final output".to_string(),
5172        });
5173    }
5174    if result.semantic_trace.accepted_candidates == 0 {
5175        return Err(XlogError::UnsupportedEpistemicConstruct {
5176            construct: "accepted GPU solver candidate evidence".to_string(),
5177            context: "solver evidence requires at least one GPU-accepted candidate".to_string(),
5178        });
5179    }
5180
5181    Ok(GpuSolverAcceptedCandidateState::from_validated_result(
5182        result,
5183        accepted_rows,
5184    ))
5185}
5186
5187fn require_accepted_gpu_solver_semantic_trace(result: &EpistemicGpuExecutionResult) -> Result<()> {
5188    let trace = &result.semantic_trace;
5189    let accounted_candidates = trace
5190        .accepted_candidates
5191        .checked_add(trace.rejected_candidates)
5192        .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
5193            construct: "accepted GPU solver candidate evidence".to_string(),
5194            context: format!(
5195                "solver evidence semantic trace candidate accounting overflowed: accepted={} \
5196                 rejected={}",
5197                trace.accepted_candidates, trace.rejected_candidates
5198            ),
5199        })?;
5200    if trace.generated_candidates != result.candidate_generation.generated_candidates
5201        || trace.tested_candidates != result.world_view_validation.candidates_checked
5202        || trace.accepted_candidates != trace.accepted_candidate_indices.len()
5203        || trace.rejected_candidates != trace.rejected_candidate_indices.len()
5204        || trace.accepted_world_views != trace.accepted_candidates
5205        || accounted_candidates != trace.generated_candidates
5206    {
5207        return Err(XlogError::UnsupportedEpistemicConstruct {
5208            construct: "accepted GPU solver candidate evidence".to_string(),
5209            context: format!(
5210                "solver evidence requires a consistent GPU semantic trace, got \
5211                 generated={}, tested={}, expected_generated={}, \
5212                 expected_tested={}, accepted={} accepted_indices={}, accepted_world_views={}, \
5213                 rejected={} rejected_indices={}",
5214                trace.generated_candidates,
5215                trace.tested_candidates,
5216                result.candidate_generation.generated_candidates,
5217                result.world_view_validation.candidates_checked,
5218                trace.accepted_candidates,
5219                trace.accepted_candidate_indices.len(),
5220                trace.accepted_world_views,
5221                trace.rejected_candidates,
5222                trace.rejected_candidate_indices.len()
5223            ),
5224        });
5225    }
5226    Ok(())
5227}
5228
5229fn require_accepted_gpu_solver_batch_evidence<'a>(
5230    provider: &CudaKernelProvider,
5231    batch: &'a EpistemicGpuBatchExecutionResult,
5232) -> Result<Vec<&'a EpistemicGpuExecutionResult>> {
5233    if batch.results.is_empty() {
5234        return Err(XlogError::UnsupportedEpistemicConstruct {
5235            construct: "accepted GPU solver batch evidence".to_string(),
5236            context: "solver batch evidence requires at least one accepted GPU component"
5237                .to_string(),
5238        });
5239    }
5240    let trace = batch.trace;
5241    if trace.component_count != batch.results.len()
5242        || trace.gpu_runtime_component_executions != batch.results.len()
5243        || trace.tracked_dtoh_calls != 0
5244        || trace.tracked_htod_calls != 0
5245        || trace.tracked_data_plane_htod_calls != 0
5246        || trace.per_candidate_host_round_trips != 0
5247        || trace.violated_constraint_relations != 0
5248        || !trace.aggregate_kernel_timing.is_recorded()
5249    {
5250        return Err(XlogError::UnsupportedEpistemicConstruct {
5251            construct: "accepted GPU solver batch evidence".to_string(),
5252            context: format!(
5253                "solver batch evidence requires complete GPU component execution, zero \
5254                 observed hot-path transfers outside bounded launch metadata, and aggregate \
5255                 CUDA-event timing, got components={}/{}, dtoh_calls={}, \
5256                 htod_calls={}, data_plane_htod_calls={}, launch_metadata_htod_calls={}, \
5257                 round_trips={}, constraint_violations={}, aggregate_timing_recorded={}",
5258                trace.gpu_runtime_component_executions,
5259                trace.component_count,
5260                trace.tracked_dtoh_calls,
5261                trace.tracked_htod_calls,
5262                trace.tracked_data_plane_htod_calls,
5263                trace.tracked_launch_metadata_htod_calls,
5264                trace.per_candidate_host_round_trips,
5265                trace.violated_constraint_relations,
5266                trace.aggregate_kernel_timing.is_recorded()
5267            ),
5268        });
5269    }
5270    batch.require_trace_matches_components("accepted GPU solver batch evidence")?;
5271
5272    let mut results = Vec::with_capacity(batch.results.len());
5273    for result in &batch.results {
5274        require_accepted_gpu_solver_evidence(provider, result)?;
5275        results.push(result);
5276    }
5277    Ok(results)
5278}
5279
5280fn require_same_gpu_cnf_for_learned_clause_reuse(source: &GpuCnf, target: &GpuCnf) -> Result<()> {
5281    let same_shape = source.var_cap == target.var_cap
5282        && source.clause_cap == target.clause_cap
5283        && source.lit_cap == target.lit_cap;
5284    let same_buffers = source.num_vars.device_ptr_value() == target.num_vars.device_ptr_value()
5285        && source.num_clauses.device_ptr_value() == target.num_clauses.device_ptr_value()
5286        && source.num_lits.device_ptr_value() == target.num_lits.device_ptr_value()
5287        && source.clause_offsets.device_ptr_value() == target.clause_offsets.device_ptr_value()
5288        && source.literals.device_ptr_value() == target.literals.device_ptr_value();
5289    if !same_shape || !same_buffers {
5290        return Err(XlogError::UnsupportedEpistemicConstruct {
5291            construct: "GPU solver learned-clause reuse".to_string(),
5292            context: "learned-clause import is currently certified only for the same \
5293                 device-resident CNF; distinct candidate CNFs must not reuse imported clauses"
5294                .to_string(),
5295        });
5296    }
5297    Ok(())
5298}
5299
5300fn require_stable_learned_clause_arena(
5301    phase: &'static str,
5302    workspace: &GpuCdclWorkspace,
5303    learned_offsets_ptr: cudarc::driver::sys::CUdeviceptr,
5304    learned_lits_ptr: cudarc::driver::sys::CUdeviceptr,
5305    proof_offsets_ptr: cudarc::driver::sys::CUdeviceptr,
5306    proof_data_ptr: cudarc::driver::sys::CUdeviceptr,
5307    learned_count_ptr: cudarc::driver::sys::CUdeviceptr,
5308) -> Result<()> {
5309    if workspace.learned_offsets.device_ptr_value() != learned_offsets_ptr
5310        || workspace.learned_lits.device_ptr_value() != learned_lits_ptr
5311        || workspace.proof_offsets.device_ptr_value() != proof_offsets_ptr
5312        || workspace.proof_data.device_ptr_value() != proof_data_ptr
5313        || workspace.out_learned_count.device_ptr_value() != learned_count_ptr
5314    {
5315        return Err(XlogError::UnsupportedEpistemicConstruct {
5316            construct: "GPU solver learned-clause reuse".to_string(),
5317            context: format!("learned-clause {phase} must keep the reusable GPU workspace arena"),
5318        });
5319    }
5320    Ok(())
5321}
5322
5323fn require_gpu_kernel_trace(
5324    phase: &'static str,
5325    kernel_launches: u32,
5326    host_write_ops: u32,
5327    kernel_timing: EpistemicGpuKernelTimingTrace,
5328) -> Result<()> {
5329    if kernel_launches == 0 || host_write_ops != 0 || !kernel_timing.is_recorded() {
5330        return Err(XlogError::UnsupportedEpistemicConstruct {
5331            construct: "accepted GPU solver candidate evidence".to_string(),
5332            context: format!(
5333                "solver evidence requires GPU {phase} trace with nonzero launches and \
5334                 zero host writes plus CUDA-event timing, got launches={kernel_launches}, \
5335                 host_writes={host_write_ops}, timing_recorded={}",
5336                kernel_timing.is_recorded()
5337            ),
5338        });
5339    }
5340    Ok(())
5341}
5342
5343#[cfg(test)]
5344mod tests {
5345    use std::sync::Arc;
5346
5347    use xlog_core::MemoryBudget;
5348    use xlog_cuda::{CudaDevice, CudaKernelProvider, GpuMemoryManager};
5349
5350    use super::*;
5351    use crate::{Clause, Literal};
5352
5353    fn try_provider() -> Option<Arc<CudaKernelProvider>> {
5354        let device = match CudaDevice::new(0) {
5355            Ok(device) => Arc::new(device),
5356            Err(err) => {
5357                eprintln!("Skipping test: CUDA runtime unavailable: {err}");
5358                return None;
5359            }
5360        };
5361        let budget = MemoryBudget::with_limit(1024 * 1024 * 1024);
5362        let memory = Arc::new(GpuMemoryManager::new(device.clone(), budget));
5363        match CudaKernelProvider::new(device, memory) {
5364            Ok(provider) => Some(Arc::new(provider)),
5365            Err(err) => {
5366                eprintln!("Skipping test: failed to create CUDA kernel provider: {err}");
5367                None
5368            }
5369        }
5370    }
5371
5372    fn alloc_u32(
5373        provider: &Arc<CudaKernelProvider>,
5374        value: u32,
5375    ) -> xlog_cuda::memory::TrackedCudaSlice<u32> {
5376        let memory = provider.memory();
5377        let mut slot = memory.alloc::<u32>(1).expect("alloc u32 scalar");
5378        provider
5379            .device()
5380            .inner()
5381            .htod_sync_copy_into(&[value], &mut slot)
5382            .expect("upload u32 scalar");
5383        slot
5384    }
5385
5386    #[test]
5387    fn weighted_maxsat_frontier_completion_fails_closed_before_cpu_expansion() {
5388        let clauses: Vec<_> = (0..18)
5389            .map(|idx| {
5390                let lit = if idx % 2 == 0 {
5391                    Literal::positive(0)
5392                } else {
5393                    Literal::negative(0)
5394                };
5395                Clause::new(vec![lit])
5396            })
5397            .collect();
5398        let weighted = SolveInstance::with_weights(1, clauses, vec![1.0; 18]);
5399        let first_frontier: Vec<_> = (0..9).collect();
5400        let second_frontier: Vec<_> = (9..18).collect();
5401        let selections = [
5402            GpuSolverProductionWeightedMaxSatSelection {
5403                soft_clause_indices: &first_frontier,
5404                status: GpuSolverProductionMaxSatSearchStatus::Unsatisfiable,
5405            },
5406            GpuSolverProductionWeightedMaxSatSelection {
5407                soft_clause_indices: &second_frontier,
5408                status: GpuSolverProductionMaxSatSearchStatus::Unsatisfiable,
5409            },
5410        ];
5411
5412        let result = GpuSolverProductionAdapter::complete_weighted_maxsat_frontier_selections(
5413            &weighted,
5414            weighted.weights.as_ref().expect("weighted MaxSAT weights"),
5415            &selections,
5416        );
5417        let Err(err) = result else {
5418            panic!("frontier completion should reject CPU combinatorial expansion");
5419        };
5420        let message = err.to_string();
5421        assert!(message.contains("frontier completion"));
5422        assert!(message.contains("explicit GPU scheduler selections"));
5423    }
5424
5425    #[test]
5426    fn encoded_weighted_maxsat_search_runs_real_gpu_sat_unsat_candidates() {
5427        let Some(provider) = try_provider() else {
5428            return;
5429        };
5430
5431        let weighted = SolveInstance::with_weights(
5432            1,
5433            vec![
5434                Clause::new(vec![Literal::positive(0)]),
5435                Clause::new(vec![Literal::negative(0)]),
5436            ],
5437            vec![2.0, 1.0],
5438        );
5439        let mut adapter =
5440            GpuSolverProductionAdapter::new(provider.clone(), GpuCdclConfig::default());
5441        let mut workspace = adapter
5442            .new_workspace(weighted.num_vars, weighted.clauses.len() as u32)
5443            .expect("new MaxSAT workspace");
5444        let branch_limit = alloc_u32(&provider, weighted.num_vars);
5445        let contradictory_selection = [0usize, 1usize];
5446        let selections = [GpuSolverProductionWeightedMaxSatSelection {
5447            soft_clause_indices: &contradictory_selection,
5448            status: GpuSolverProductionMaxSatSearchStatus::Unsatisfiable,
5449        }];
5450
5451        let certificates_before = adapter.trace.gpu_maxsat_frontier_upper_bound_certificates;
5452        let encoded = adapter
5453            .encode_weighted_maxsat_search_candidates(&weighted, &selections)
5454            .expect("encode weighted MaxSAT candidates");
5455        let frontier_upper_bound_certificates =
5456            GpuSolverProductionAdapter::checked_report_counter_delta(
5457                adapter.trace.gpu_maxsat_frontier_upper_bound_certificates,
5458                certificates_before,
5459                "gpu_maxsat_frontier_upper_bound_certificates",
5460            )
5461            .expect("frontier certificate delta");
5462        let search_candidates: Vec<_> = encoded
5463            .iter()
5464            .map(|candidate| GpuSolverProductionMaxSatSearchCandidate {
5465                score: candidate.score,
5466                cnf: &candidate.cnf,
5467                branch_var_limit: &branch_limit,
5468                status: candidate.status,
5469            })
5470            .collect();
5471
5472        let report = adapter
5473            .solve_weighted_maxsat_search_candidates(
5474                &mut workspace,
5475                &search_candidates,
5476                frontier_upper_bound_certificates,
5477            )
5478            .expect("GPU weighted MaxSAT search");
5479
5480        assert_eq!(report.candidate_evidence_records, 0);
5481        assert_eq!(report.optimum_score, 2);
5482        assert_eq!(report.candidates_checked, 2);
5483        assert_eq!(report.satisfiable_candidates, 1);
5484        assert_eq!(report.unsat_candidates_pruned, 1);
5485        assert_eq!(report.gpu_cdcl_candidate_solves, 2);
5486        assert_eq!(report.frontier_upper_bound_certificates, 1);
5487
5488        let trace = adapter.trace();
5489        assert_eq!(trace.gpu_maxsat_candidate_encodes, 2);
5490        assert_eq!(trace.gpu_maxsat_frontier_completion_candidate_encodes, 1);
5491        assert_eq!(trace.gpu_maxsat_frontier_upper_bound_certificates, 1);
5492        assert_eq!(trace.gpu_maxsat_candidate_solves, 2);
5493        assert_eq!(trace.gpu_maxsat_unsat_candidate_prunes, 1);
5494        assert_eq!(trace.gpu_cdcl_sat_solves, 1);
5495        assert_eq!(trace.gpu_cdcl_workspace_unsat_solves, 1);
5496        assert_eq!(trace.gpu_maxsat_optima, 1);
5497        trace
5498            .require_production_metric_eligibility()
5499            .expect("MaxSAT production search must not use CPU search");
5500    }
5501
5502    #[test]
5503    fn portfolio_jobs_dispatch_real_gpu_sat_and_encoded_maxsat_paths() {
5504        let Some(provider) = try_provider() else {
5505            return;
5506        };
5507
5508        let sat_instance = SolveInstance::new(1, vec![Clause::new(vec![Literal::positive(0)])]);
5509        let sat_cnf = GpuCnf::from_host(&sat_instance, &provider).expect("SAT GpuCnf upload");
5510        let weighted = SolveInstance::with_weights(
5511            1,
5512            vec![
5513                Clause::new(vec![Literal::positive(0)]),
5514                Clause::new(vec![Literal::negative(0)]),
5515            ],
5516            vec![2.0, 1.0],
5517        );
5518        let branch_limit = alloc_u32(&provider, weighted.num_vars);
5519        let contradictory_selection = [0usize, 1usize];
5520        let selections = [GpuSolverProductionWeightedMaxSatSelection {
5521            soft_clause_indices: &contradictory_selection,
5522            status: GpuSolverProductionMaxSatSearchStatus::Unsatisfiable,
5523        }];
5524        let jobs = [
5525            GpuSolverProductionPortfolioJob::Sat {
5526                cnf: &sat_cnf,
5527                branch_var_limit: &branch_limit,
5528            },
5529            GpuSolverProductionPortfolioJob::EncodedMaxSat {
5530                weighted: &weighted,
5531                branch_var_limit: &branch_limit,
5532                selections: &selections,
5533            },
5534            GpuSolverProductionPortfolioJob::Unknown {
5535                reason: "bounded portfolio diagnostic",
5536            },
5537            GpuSolverProductionPortfolioJob::Timeout { budget_micros: 1 },
5538        ];
5539        let mut adapter =
5540            GpuSolverProductionAdapter::new(provider.clone(), GpuCdclConfig::default());
5541
5542        let report = adapter
5543            .solve_portfolio_jobs(&jobs)
5544            .expect("GPU production portfolio jobs");
5545
5546        assert_eq!(report.candidate_evidence_records, 0);
5547        assert_eq!(report.jobs, 4);
5548        assert_eq!(report.sat_jobs, 1);
5549        assert_eq!(report.maxsat_jobs, 1);
5550        assert_eq!(report.unknown_jobs, 1);
5551        assert_eq!(report.timeout_jobs, 1);
5552        assert_eq!(report.maxsat_optimum_scores, 2);
5553        assert_eq!(report.maxsat_candidates_checked, 2);
5554        assert_eq!(report.maxsat_satisfiable_candidates, 1);
5555        assert_eq!(report.maxsat_unsat_candidates_pruned, 1);
5556        assert_eq!(report.maxsat_gpu_cdcl_candidate_encodes, 2);
5557        assert_eq!(report.maxsat_gpu_cdcl_candidate_solves, 2);
5558        assert_eq!(report.maxsat_frontier_upper_bound_certificates, 1);
5559
5560        let trace = adapter.trace();
5561        assert_eq!(trace.gpu_portfolio_jobs, 4);
5562        assert_eq!(trace.gpu_portfolio_sat_jobs, 1);
5563        assert_eq!(trace.gpu_portfolio_maxsat_jobs, 1);
5564        assert_eq!(trace.gpu_portfolio_unknown_status_jobs, 1);
5565        assert_eq!(trace.gpu_portfolio_timeout_status_jobs, 1);
5566        assert_eq!(trace.gpu_cdcl_sat_solves, 2);
5567        assert_eq!(trace.gpu_cdcl_workspace_unsat_solves, 1);
5568        assert_eq!(trace.gpu_maxsat_candidate_encodes, 2);
5569        assert_eq!(trace.gpu_maxsat_candidate_solves, 2);
5570        assert_eq!(trace.gpu_maxsat_unsat_candidate_prunes, 1);
5571        assert_eq!(trace.gpu_maxsat_optima, 1);
5572        trace
5573            .require_production_metric_eligibility()
5574            .expect("portfolio production search must not use CPU search");
5575    }
5576
5577    #[test]
5578    fn learned_clause_reuse_publishes_and_imports_gpu_workspace_arena() {
5579        let Some(provider) = try_provider() else {
5580            return;
5581        };
5582
5583        let unsat_instance = SolveInstance::new(
5584            1,
5585            vec![
5586                Clause::new(vec![Literal::positive(0)]),
5587                Clause::new(vec![Literal::negative(0)]),
5588            ],
5589        );
5590        let cnf = GpuCnf::from_host(&unsat_instance, &provider).expect("UNSAT GpuCnf upload");
5591        let branch_limit = alloc_u32(&provider, cnf.var_cap as u32);
5592        let mut adapter =
5593            GpuSolverProductionAdapter::new(provider.clone(), GpuCdclConfig::default());
5594        let mut workspace = adapter
5595            .new_workspace(cnf.var_cap, cnf.clause_cap)
5596            .expect("new learned-clause workspace");
5597
5598        let learned_offsets_ptr = workspace.learned_offsets.device_ptr_value();
5599        let learned_lits_ptr = workspace.learned_lits.device_ptr_value();
5600        let proof_offsets_ptr = workspace.proof_offsets.device_ptr_value();
5601        let proof_data_ptr = workspace.proof_data.device_ptr_value();
5602        let learned_count_ptr = workspace.out_learned_count.device_ptr_value();
5603
5604        let report = adapter
5605            .solve_unsat_then_reuse_learned_clauses(
5606                &mut workspace,
5607                &cnf,
5608                &branch_limit,
5609                &cnf,
5610                &branch_limit,
5611            )
5612            .expect("GPU learned-clause reuse");
5613
5614        assert_eq!(report.candidate_evidence_records, 0);
5615        assert_eq!(report.candidates, 2);
5616        assert_eq!(report.unsat_solves, 2);
5617        assert_eq!(report.gpu_learned_clause_arena_publications, 1);
5618        assert_eq!(report.gpu_learned_clause_imports, 1);
5619        assert_eq!(report.gpu_learned_clause_reused_solves, 1);
5620        assert_eq!(
5621            workspace.learned_offsets.device_ptr_value(),
5622            learned_offsets_ptr
5623        );
5624        assert_eq!(workspace.learned_lits.device_ptr_value(), learned_lits_ptr);
5625        assert_eq!(
5626            workspace.proof_offsets.device_ptr_value(),
5627            proof_offsets_ptr
5628        );
5629        assert_eq!(workspace.proof_data.device_ptr_value(), proof_data_ptr);
5630        assert_eq!(
5631            workspace.out_learned_count.device_ptr_value(),
5632            learned_count_ptr
5633        );
5634
5635        let trace = adapter.trace();
5636        assert_eq!(trace.gpu_cdcl_workspace_unsat_solves, 2);
5637        assert_eq!(trace.gpu_learned_clause_arena_publications, 1);
5638        assert_eq!(trace.gpu_learned_count_buffer_publications, 1);
5639        assert_eq!(trace.gpu_learned_clause_imports, 1);
5640        assert_eq!(trace.gpu_learned_clause_reused_solves, 1);
5641        trace
5642            .require_production_metric_eligibility()
5643            .expect("learned-clause production reuse must not use CPU search");
5644    }
5645}