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;
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    #[expect(
2608        clippy::too_many_arguments,
2609        reason = "accepted-evidence reuse keeps the evidence provider, workspace, and both CNF decision-bound pairs explicit"
2610    )]
2611    pub fn solve_unsat_then_reuse_learned_clauses_with_gpu_execution_result(
2612        &mut self,
2613        provider: &CudaKernelProvider,
2614        result: &EpistemicGpuExecutionResult,
2615        workspace: &mut GpuCdclWorkspace,
2616        source_cnf: &GpuCnf,
2617        source_branch_var_limit: &TrackedCudaSlice<u32>,
2618        target_cnf: &GpuCnf,
2619        target_branch_var_limit: &TrackedCudaSlice<u32>,
2620    ) -> Result<GpuSolverProductionLearnedClauseReuseReport> {
2621        self.with_trace_rollback_preserving_reuse_rejections(|this| {
2622            this.require_workspace_on_adapter_provider(workspace)?;
2623            this.require_learned_clause_reuse_inputs(
2624                workspace,
2625                source_cnf,
2626                source_branch_var_limit,
2627                target_cnf,
2628                target_branch_var_limit,
2629            )?;
2630            let state = this.require_accepted_gpu_solver_evidence(provider, result)?;
2631            let events_before = this.trace.accepted_path_event_snapshot()?;
2632            let mut report = this.solve_unsat_then_reuse_learned_clauses(
2633                workspace,
2634                source_cnf,
2635                source_branch_var_limit,
2636                target_cnf,
2637                target_branch_var_limit,
2638            )?;
2639            report.candidate_evidence_records = 1;
2640            this.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
2641            this.record_accepted_gpu_candidate_state(&state)?;
2642            Ok(report)
2643        })
2644    }
2645
2646    /// Publish and reuse learned clauses once per accepted GPU epistemic candidate.
2647    #[expect(
2648        clippy::too_many_arguments,
2649        reason = "multi-candidate reuse adds an accepted-result set to the same explicit source and target CNF contract"
2650    )]
2651    pub fn solve_multi_candidate_learned_clause_reuse_with_gpu_execution_results(
2652        &mut self,
2653        provider: &CudaKernelProvider,
2654        results: &[&EpistemicGpuExecutionResult],
2655        workspace: &mut GpuCdclWorkspace,
2656        source_cnf: &GpuCnf,
2657        source_branch_var_limit: &TrackedCudaSlice<u32>,
2658        target_cnf: &GpuCnf,
2659        target_branch_var_limit: &TrackedCudaSlice<u32>,
2660    ) -> Result<GpuSolverProductionLearnedClauseReuseReport> {
2661        self.with_trace_rollback_preserving_reuse_rejections(|this| {
2662            this.solve_multi_candidate_learned_clause_reuse_with_gpu_execution_results_impl(
2663                provider,
2664                results,
2665                workspace,
2666                source_cnf,
2667                source_branch_var_limit,
2668                target_cnf,
2669                target_branch_var_limit,
2670            )
2671        })
2672    }
2673
2674    #[expect(
2675        clippy::too_many_arguments,
2676        reason = "the internal multi-candidate operation carries the public evidence and source-target reuse contract unchanged"
2677    )]
2678    fn solve_multi_candidate_learned_clause_reuse_with_gpu_execution_results_impl(
2679        &mut self,
2680        provider: &CudaKernelProvider,
2681        results: &[&EpistemicGpuExecutionResult],
2682        workspace: &mut GpuCdclWorkspace,
2683        source_cnf: &GpuCnf,
2684        source_branch_var_limit: &TrackedCudaSlice<u32>,
2685        target_cnf: &GpuCnf,
2686        target_branch_var_limit: &TrackedCudaSlice<u32>,
2687    ) -> Result<GpuSolverProductionLearnedClauseReuseReport> {
2688        if results.is_empty() {
2689            return Err(XlogError::UnsupportedEpistemicConstruct {
2690                construct: "GPU solver learned-clause reuse".to_string(),
2691                context:
2692                    "multi-candidate learned-clause reuse requires at least one accepted GPU result"
2693                        .to_string(),
2694            });
2695        }
2696        self.require_workspace_on_adapter_provider(workspace)?;
2697        self.require_learned_clause_reuse_inputs(
2698            workspace,
2699            source_cnf,
2700            source_branch_var_limit,
2701            target_cnf,
2702            target_branch_var_limit,
2703        )?;
2704        let states = self.require_accepted_gpu_solver_states(provider, results)?;
2705
2706        let mut report = GpuSolverProductionLearnedClauseReuseReport::default();
2707        for state in &states {
2708            let events_before = self.trace.accepted_path_event_snapshot()?;
2709            let step_report = self.solve_unsat_then_reuse_learned_clauses(
2710                workspace,
2711                source_cnf,
2712                source_branch_var_limit,
2713                target_cnf,
2714                target_branch_var_limit,
2715            )?;
2716            checked_solver_report_counter_inc!(report, candidate_evidence_records);
2717            checked_solver_report_counter_add!(report, candidates, step_report.candidates);
2718            checked_solver_report_counter_add!(report, unsat_solves, step_report.unsat_solves);
2719            checked_solver_report_counter_add!(
2720                report,
2721                gpu_learned_clause_arena_publications,
2722                step_report.gpu_learned_clause_arena_publications
2723            );
2724            checked_solver_report_counter_add!(
2725                report,
2726                gpu_learned_clause_imports,
2727                step_report.gpu_learned_clause_imports
2728            );
2729            checked_solver_report_counter_add!(
2730                report,
2731                gpu_learned_clause_reused_solves,
2732                step_report.gpu_learned_clause_reused_solves
2733            );
2734            self.record_accepted_gpu_solver_production_path_events_since(events_before, state)?;
2735            self.record_accepted_gpu_candidate_state(state)?;
2736        }
2737
2738        Ok(report)
2739    }
2740
2741    /// Publish and reuse learned clauses once per accepted split/batch GPU component.
2742    ///
2743    /// The batch evidence must prove every split component reused the existing
2744    /// single-plan GPU runtime path before each component is delegated to the
2745    /// existing multi-candidate learned-clause reuse adapter.
2746    #[expect(
2747        clippy::too_many_arguments,
2748        reason = "batch-evidence reuse keeps the accepted batch proof, workspace, and both CNF decision-bound pairs explicit"
2749    )]
2750    pub fn solve_learned_clause_reuse_with_gpu_batch_execution_result(
2751        &mut self,
2752        provider: &CudaKernelProvider,
2753        evidence: GpuSolverProductionBatchExecutionEvidence<'_>,
2754        workspace: &mut GpuCdclWorkspace,
2755        source_cnf: &GpuCnf,
2756        source_branch_var_limit: &TrackedCudaSlice<u32>,
2757        target_cnf: &GpuCnf,
2758        target_branch_var_limit: &TrackedCudaSlice<u32>,
2759    ) -> Result<GpuSolverProductionLearnedClauseReuseReport> {
2760        self.with_trace_rollback_preserving_reuse_rejections(|this| {
2761            this.require_workspace_on_adapter_provider(workspace)?;
2762            this.require_learned_clause_reuse_inputs(
2763                workspace,
2764                source_cnf,
2765                source_branch_var_limit,
2766                target_cnf,
2767                target_branch_var_limit,
2768            )?;
2769            let results =
2770                this.accepted_solver_results_from_gpu_batch_execution_evidence(provider, evidence)?;
2771            let report = this
2772                .solve_multi_candidate_learned_clause_reuse_with_gpu_execution_results(
2773                    provider,
2774                    &results,
2775                    workspace,
2776                    source_cnf,
2777                    source_branch_var_limit,
2778                    target_cnf,
2779                    target_branch_var_limit,
2780                )?;
2781            this.record_accepted_gpu_batch_candidate_evidence(results.len())?;
2782            Ok(report)
2783        })
2784    }
2785
2786    fn solve_weighted_maxsat_candidates(
2787        &mut self,
2788        candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
2789        frontier_upper_bound_certificates: u64,
2790    ) -> Result<GpuSolverProductionMaxSatReport> {
2791        self.with_trace_rollback(|this| {
2792            this.solve_weighted_maxsat_candidates_impl(
2793                candidates,
2794                frontier_upper_bound_certificates,
2795            )
2796        })
2797    }
2798
2799    fn solve_weighted_maxsat_candidates_impl(
2800        &mut self,
2801        candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
2802        frontier_upper_bound_certificates: u64,
2803    ) -> Result<GpuSolverProductionMaxSatReport> {
2804        self.require_weighted_maxsat_candidates_and_artifacts(candidates)?;
2805
2806        let solves_before = self.trace.gpu_maxsat_candidate_solves;
2807        let mut optimum_score = 0u64;
2808        for candidate in candidates {
2809            let _assignment = self
2810                .solver
2811                .solve_expect_sat_with_branch_limit(candidate.cnf, candidate.branch_var_limit)?;
2812            checked_solver_trace_counter_inc!(self, gpu_cdcl_sat_solves);
2813            checked_solver_trace_counter_inc!(self, gpu_maxsat_candidate_solves);
2814            optimum_score = optimum_score.max(candidate.score);
2815        }
2816        checked_solver_trace_counter_inc!(self, gpu_maxsat_optima);
2817        let gpu_cdcl_candidate_solves = Self::checked_report_counter_delta(
2818            self.trace.gpu_maxsat_candidate_solves,
2819            solves_before,
2820            "gpu_maxsat_candidate_solves",
2821        )?;
2822        if frontier_upper_bound_certificates != 0 {
2823            self.trace.gpu_maxsat_frontier_certified_candidate_solves =
2824                Self::checked_trace_counter_add(
2825                    self.trace.gpu_maxsat_frontier_certified_candidate_solves,
2826                    gpu_cdcl_candidate_solves,
2827                    "gpu_maxsat_frontier_certified_candidate_solves",
2828                )?;
2829        }
2830
2831        Ok(GpuSolverProductionMaxSatReport {
2832            candidate_evidence_records: 0,
2833            optimum_score,
2834            candidates_checked: candidates.len() as u64,
2835            satisfiable_candidates: candidates.len() as u64,
2836            unsat_candidates_pruned: 0,
2837            gpu_cdcl_candidate_encodes: 0,
2838            gpu_cdcl_candidate_solves,
2839            frontier_upper_bound_certificates,
2840        })
2841    }
2842
2843    fn require_weighted_maxsat_candidates(
2844        candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
2845    ) -> Result<()> {
2846        if candidates.is_empty() {
2847            return Err(XlogError::UnsupportedEpistemicConstruct {
2848                construct: "GPU solver production MaxSAT".to_string(),
2849                context: "bounded MaxSAT adapter requires at least one candidate CNF".to_string(),
2850            });
2851        }
2852        Ok(())
2853    }
2854
2855    /// Solve a bounded weighted MaxSAT candidate set after accepted GPU epistemic execution.
2856    ///
2857    /// CPU orchestration is limited to launching/checking the provided candidate CNFs and
2858    /// comparing their declared scores. Each candidate is certified by the existing GPU CDCL
2859    /// SAT path; this adapter performs no CPU assignment or MaxSAT enumeration.
2860    pub fn solve_weighted_maxsat_candidates_with_gpu_execution_result(
2861        &mut self,
2862        provider: &CudaKernelProvider,
2863        result: &EpistemicGpuExecutionResult,
2864        candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
2865    ) -> Result<GpuSolverProductionMaxSatReport> {
2866        self.with_trace_rollback(|this| {
2867            this.require_weighted_maxsat_candidates_and_artifacts(candidates)?;
2868            let state = this.require_accepted_gpu_solver_evidence(provider, result)?;
2869            let events_before = this.trace.accepted_path_event_snapshot()?;
2870            let mut report = this.solve_weighted_maxsat_candidates(candidates, 0)?;
2871            report.candidate_evidence_records = 1;
2872            this.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
2873            this.record_accepted_gpu_candidate_state(&state)?;
2874            Ok(report)
2875        })
2876    }
2877
2878    fn require_maxsat_lifecycle_inputs(
2879        steps: &[GpuSolverProductionLifecycleStep<'_>],
2880        candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
2881    ) -> Result<()> {
2882        if steps.is_empty() {
2883            return Err(XlogError::UnsupportedEpistemicConstruct {
2884                construct: "GPU solver production MaxSAT lifecycle".to_string(),
2885                context: "accepted MaxSAT lifecycle requires at least one lifecycle step"
2886                    .to_string(),
2887            });
2888        }
2889        if candidates.is_empty() {
2890            return Err(XlogError::UnsupportedEpistemicConstruct {
2891                construct: "GPU solver production MaxSAT lifecycle".to_string(),
2892                context: "bounded MaxSAT adapter requires at least one candidate CNF".to_string(),
2893            });
2894        }
2895        Self::require_assumption_lifecycle_steps(steps)?;
2896        Ok(())
2897    }
2898
2899    /// Execute an accepted solver lifecycle, then a bounded MaxSAT candidate set.
2900    ///
2901    /// The same accepted GPU epistemic evidence gates both phases. The adapter
2902    /// records that evidence once, while lifecycle and MaxSAT counters prove the
2903    /// existing GPU CDCL paths handled all solver work without CPU search.
2904    pub fn solve_maxsat_lifecycle_with_gpu_execution_result(
2905        &mut self,
2906        provider: &CudaKernelProvider,
2907        result: &EpistemicGpuExecutionResult,
2908        workspace: &mut GpuCdclWorkspace,
2909        steps: &[GpuSolverProductionLifecycleStep<'_>],
2910        candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
2911    ) -> Result<GpuSolverProductionMaxSatLifecycleReport> {
2912        self.with_trace_rollback(|this| {
2913            this.solve_maxsat_lifecycle_with_gpu_execution_result_impl(
2914                provider, result, workspace, steps, candidates,
2915            )
2916        })
2917    }
2918
2919    fn solve_maxsat_lifecycle_with_gpu_execution_result_impl(
2920        &mut self,
2921        provider: &CudaKernelProvider,
2922        result: &EpistemicGpuExecutionResult,
2923        workspace: &mut GpuCdclWorkspace,
2924        steps: &[GpuSolverProductionLifecycleStep<'_>],
2925        candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
2926    ) -> Result<GpuSolverProductionMaxSatLifecycleReport> {
2927        Self::require_maxsat_lifecycle_inputs(steps, candidates)?;
2928        self.require_workspace_on_adapter_provider(workspace)?;
2929        self.require_maxsat_lifecycle_artifacts(workspace, steps, candidates)?;
2930        let state = self.require_accepted_gpu_solver_evidence(provider, result)?;
2931
2932        let events_before = self.trace.accepted_path_event_snapshot()?;
2933        let lifecycle = self.solve_assumption_lifecycle_steps(workspace, steps)?;
2934        let maxsat = self.solve_weighted_maxsat_candidates(candidates, 0)?;
2935        self.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
2936        self.record_accepted_gpu_candidate_state(&state)?;
2937
2938        Ok(GpuSolverProductionMaxSatLifecycleReport {
2939            candidate_evidence_records: 1,
2940            lifecycle,
2941            maxsat,
2942        })
2943    }
2944
2945    fn add_maxsat_lifecycle_step_report(
2946        report: &mut GpuSolverProductionMaxSatLifecycleReport,
2947        step_report: GpuSolverProductionMaxSatLifecycleReport,
2948    ) -> Result<()> {
2949        report.candidate_evidence_records = Self::checked_report_counter_add(
2950            report.candidate_evidence_records,
2951            step_report.candidate_evidence_records,
2952            "candidate_evidence_records",
2953        )?;
2954        report.lifecycle.steps = Self::checked_report_counter_add(
2955            report.lifecycle.steps,
2956            step_report.lifecycle.steps,
2957            "lifecycle.steps",
2958        )?;
2959        report.lifecycle.sat_steps = Self::checked_report_counter_add(
2960            report.lifecycle.sat_steps,
2961            step_report.lifecycle.sat_steps,
2962            "lifecycle.sat_steps",
2963        )?;
2964        report.lifecycle.unsat_steps = Self::checked_report_counter_add(
2965            report.lifecycle.unsat_steps,
2966            step_report.lifecycle.unsat_steps,
2967            "lifecycle.unsat_steps",
2968        )?;
2969        report.lifecycle.assumption_pushes = Self::checked_report_counter_add(
2970            report.lifecycle.assumption_pushes,
2971            step_report.lifecycle.assumption_pushes,
2972            "lifecycle.assumption_pushes",
2973        )?;
2974        report.lifecycle.assumption_retractions = Self::checked_report_counter_add(
2975            report.lifecycle.assumption_retractions,
2976            step_report.lifecycle.assumption_retractions,
2977            "lifecycle.assumption_retractions",
2978        )?;
2979        report.lifecycle.workspace_reuses = Self::checked_report_counter_add(
2980            report.lifecycle.workspace_reuses,
2981            step_report.lifecycle.workspace_reuses,
2982            "lifecycle.workspace_reuses",
2983        )?;
2984        report.lifecycle.unknown_steps = Self::checked_report_counter_add(
2985            report.lifecycle.unknown_steps,
2986            step_report.lifecycle.unknown_steps,
2987            "lifecycle.unknown_steps",
2988        )?;
2989        report.lifecycle.timeout_steps = Self::checked_report_counter_add(
2990            report.lifecycle.timeout_steps,
2991            step_report.lifecycle.timeout_steps,
2992            "lifecycle.timeout_steps",
2993        )?;
2994        report.maxsat.optimum_score = report
2995            .maxsat
2996            .optimum_score
2997            .max(step_report.maxsat.optimum_score);
2998        report.maxsat.candidates_checked = Self::checked_report_counter_add(
2999            report.maxsat.candidates_checked,
3000            step_report.maxsat.candidates_checked,
3001            "maxsat.candidates_checked",
3002        )?;
3003        report.maxsat.satisfiable_candidates = Self::checked_report_counter_add(
3004            report.maxsat.satisfiable_candidates,
3005            step_report.maxsat.satisfiable_candidates,
3006            "maxsat.satisfiable_candidates",
3007        )?;
3008        report.maxsat.unsat_candidates_pruned = Self::checked_report_counter_add(
3009            report.maxsat.unsat_candidates_pruned,
3010            step_report.maxsat.unsat_candidates_pruned,
3011            "maxsat.unsat_candidates_pruned",
3012        )?;
3013        report.maxsat.gpu_cdcl_candidate_encodes = Self::checked_report_counter_add(
3014            report.maxsat.gpu_cdcl_candidate_encodes,
3015            step_report.maxsat.gpu_cdcl_candidate_encodes,
3016            "maxsat.gpu_cdcl_candidate_encodes",
3017        )?;
3018        report.maxsat.gpu_cdcl_candidate_solves = Self::checked_report_counter_add(
3019            report.maxsat.gpu_cdcl_candidate_solves,
3020            step_report.maxsat.gpu_cdcl_candidate_solves,
3021            "maxsat.gpu_cdcl_candidate_solves",
3022        )?;
3023        report.maxsat.frontier_upper_bound_certificates = Self::checked_report_counter_add(
3024            report.maxsat.frontier_upper_bound_certificates,
3025            step_report.maxsat.frontier_upper_bound_certificates,
3026            "maxsat.frontier_upper_bound_certificates",
3027        )?;
3028        Ok(())
3029    }
3030
3031    /// Execute accepted solver lifecycle plus MaxSAT candidate-set work once per evidence record.
3032    pub fn solve_multi_candidate_maxsat_lifecycle_with_gpu_execution_results(
3033        &mut self,
3034        provider: &CudaKernelProvider,
3035        results: &[&EpistemicGpuExecutionResult],
3036        workspace: &mut GpuCdclWorkspace,
3037        steps: &[GpuSolverProductionLifecycleStep<'_>],
3038        candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
3039    ) -> Result<GpuSolverProductionMaxSatLifecycleReport> {
3040        self.with_trace_rollback(|this| {
3041            this.solve_multi_candidate_maxsat_lifecycle_with_gpu_execution_results_impl(
3042                provider, results, workspace, steps, candidates,
3043            )
3044        })
3045    }
3046
3047    fn solve_multi_candidate_maxsat_lifecycle_with_gpu_execution_results_impl(
3048        &mut self,
3049        provider: &CudaKernelProvider,
3050        results: &[&EpistemicGpuExecutionResult],
3051        workspace: &mut GpuCdclWorkspace,
3052        steps: &[GpuSolverProductionLifecycleStep<'_>],
3053        candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
3054    ) -> Result<GpuSolverProductionMaxSatLifecycleReport> {
3055        if results.is_empty() {
3056            return Err(XlogError::UnsupportedEpistemicConstruct {
3057                construct: "GPU solver production MaxSAT lifecycle".to_string(),
3058                context:
3059                    "multi-candidate MaxSAT lifecycle requires at least one accepted GPU result"
3060                        .to_string(),
3061            });
3062        }
3063        Self::require_maxsat_lifecycle_inputs(steps, candidates)?;
3064        self.require_workspace_on_adapter_provider(workspace)?;
3065        self.require_maxsat_lifecycle_artifacts(workspace, steps, candidates)?;
3066        let states = self.require_accepted_gpu_solver_states(provider, results)?;
3067
3068        let mut report = GpuSolverProductionMaxSatLifecycleReport::default();
3069        for state in &states {
3070            let events_before = self.trace.accepted_path_event_snapshot()?;
3071            let lifecycle = self.solve_assumption_lifecycle_steps(workspace, steps)?;
3072            let maxsat = self.solve_weighted_maxsat_candidates(candidates, 0)?;
3073            self.record_accepted_gpu_solver_production_path_events_since(events_before, state)?;
3074            self.record_accepted_gpu_candidate_state(state)?;
3075            Self::add_maxsat_lifecycle_step_report(
3076                &mut report,
3077                GpuSolverProductionMaxSatLifecycleReport {
3078                    candidate_evidence_records: 1,
3079                    lifecycle,
3080                    maxsat,
3081                },
3082            )?;
3083        }
3084
3085        Ok(report)
3086    }
3087
3088    /// Execute accepted split/batch solver lifecycle plus MaxSAT candidate-set work.
3089    pub fn solve_maxsat_lifecycle_with_gpu_batch_execution_result(
3090        &mut self,
3091        provider: &CudaKernelProvider,
3092        evidence: GpuSolverProductionBatchExecutionEvidence<'_>,
3093        workspace: &mut GpuCdclWorkspace,
3094        steps: &[GpuSolverProductionLifecycleStep<'_>],
3095        candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
3096    ) -> Result<GpuSolverProductionMaxSatLifecycleReport> {
3097        self.with_trace_rollback(|this| {
3098            Self::require_maxsat_lifecycle_inputs(steps, candidates)?;
3099            this.require_workspace_on_adapter_provider(workspace)?;
3100            this.require_maxsat_lifecycle_artifacts(workspace, steps, candidates)?;
3101            let results =
3102                this.accepted_solver_results_from_gpu_batch_execution_evidence(provider, evidence)?;
3103            let report = this.solve_multi_candidate_maxsat_lifecycle_with_gpu_execution_results(
3104                provider, &results, workspace, steps, candidates,
3105            )?;
3106            this.record_accepted_gpu_batch_candidate_evidence(results.len())?;
3107            Ok(report)
3108        })
3109    }
3110
3111    /// Solve a bounded weighted MaxSAT candidate set once per accepted GPU epistemic candidate.
3112    pub fn solve_multi_candidate_weighted_maxsat_with_gpu_execution_results(
3113        &mut self,
3114        provider: &CudaKernelProvider,
3115        results: &[&EpistemicGpuExecutionResult],
3116        candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
3117    ) -> Result<GpuSolverProductionMaxSatReport> {
3118        self.with_trace_rollback(|this| {
3119            this.solve_multi_candidate_weighted_maxsat_with_gpu_execution_results_impl(
3120                provider, results, candidates,
3121            )
3122        })
3123    }
3124
3125    fn solve_multi_candidate_weighted_maxsat_with_gpu_execution_results_impl(
3126        &mut self,
3127        provider: &CudaKernelProvider,
3128        results: &[&EpistemicGpuExecutionResult],
3129        candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
3130    ) -> Result<GpuSolverProductionMaxSatReport> {
3131        if results.is_empty() {
3132            return Err(XlogError::UnsupportedEpistemicConstruct {
3133                construct: "GPU solver production MaxSAT".to_string(),
3134                context: "multi-candidate MaxSAT requires at least one accepted GPU result"
3135                    .to_string(),
3136            });
3137        }
3138        self.require_weighted_maxsat_candidates_and_artifacts(candidates)?;
3139        let states = self.require_accepted_gpu_solver_states(provider, results)?;
3140
3141        let mut report = GpuSolverProductionMaxSatReport::default();
3142        for state in &states {
3143            let events_before = self.trace.accepted_path_event_snapshot()?;
3144            let step_report = self.solve_weighted_maxsat_candidates(candidates, 0)?;
3145            checked_solver_report_counter_inc!(report, candidate_evidence_records);
3146            report.optimum_score = report.optimum_score.max(step_report.optimum_score);
3147            checked_solver_report_counter_add!(
3148                report,
3149                candidates_checked,
3150                step_report.candidates_checked
3151            );
3152            checked_solver_report_counter_add!(
3153                report,
3154                satisfiable_candidates,
3155                step_report.satisfiable_candidates
3156            );
3157            checked_solver_report_counter_add!(
3158                report,
3159                unsat_candidates_pruned,
3160                step_report.unsat_candidates_pruned
3161            );
3162            checked_solver_report_counter_add!(
3163                report,
3164                gpu_cdcl_candidate_encodes,
3165                step_report.gpu_cdcl_candidate_encodes
3166            );
3167            checked_solver_report_counter_add!(
3168                report,
3169                gpu_cdcl_candidate_solves,
3170                step_report.gpu_cdcl_candidate_solves
3171            );
3172            checked_solver_report_counter_add!(
3173                report,
3174                frontier_upper_bound_certificates,
3175                step_report.frontier_upper_bound_certificates
3176            );
3177            self.record_accepted_gpu_solver_production_path_events_since(events_before, state)?;
3178            self.record_accepted_gpu_candidate_state(state)?;
3179        }
3180
3181        Ok(report)
3182    }
3183
3184    /// Solve a bounded weighted MaxSAT candidate set once per accepted split/batch GPU component.
3185    ///
3186    /// The batch evidence must prove every split component reused the existing
3187    /// single-plan GPU runtime path before each component is delegated to the
3188    /// existing multi-candidate MaxSAT adapter.
3189    pub fn solve_weighted_maxsat_candidates_with_gpu_batch_execution_result(
3190        &mut self,
3191        provider: &CudaKernelProvider,
3192        evidence: GpuSolverProductionBatchExecutionEvidence<'_>,
3193        candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
3194    ) -> Result<GpuSolverProductionMaxSatReport> {
3195        self.with_trace_rollback(|this| {
3196            this.require_weighted_maxsat_candidates_and_artifacts(candidates)?;
3197            let results =
3198                this.accepted_solver_results_from_gpu_batch_execution_evidence(provider, evidence)?;
3199            let report = this.solve_multi_candidate_weighted_maxsat_with_gpu_execution_results(
3200                provider, &results, candidates,
3201            )?;
3202            this.record_accepted_gpu_batch_candidate_evidence(results.len())?;
3203            Ok(report)
3204        })
3205    }
3206
3207    fn encode_weighted_maxsat_search_candidates(
3208        &mut self,
3209        weighted: &SolveInstance,
3210        selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
3211    ) -> Result<Vec<GpuSolverProductionEncodedMaxSatSearchCandidate>> {
3212        Self::require_weighted_maxsat_encoding_inputs(weighted, selections)?;
3213
3214        let weights =
3215            weighted
3216                .weights
3217                .as_ref()
3218                .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3219                    construct: "GPU solver production MaxSAT encoding".to_string(),
3220                    context: "weighted MaxSAT encoding requires explicit soft-clause weights"
3221                        .to_string(),
3222                })?;
3223
3224        let frontier =
3225            Self::complete_weighted_maxsat_frontier_selections(weighted, weights, selections)?;
3226        if frontier.completion_candidate_count != 0 {
3227            self.trace.gpu_maxsat_frontier_completion_candidate_encodes =
3228                Self::checked_trace_counter_add(
3229                    self.trace.gpu_maxsat_frontier_completion_candidate_encodes,
3230                    frontier.completion_candidate_count,
3231                    "gpu_maxsat_frontier_completion_candidate_encodes",
3232                )?;
3233        }
3234        checked_solver_trace_counter_inc!(self, gpu_maxsat_frontier_upper_bound_certificates);
3235        let mut encoded = Vec::with_capacity(frontier.selections.len());
3236        for selection in &frontier.selections {
3237            encoded.push(self.encode_weighted_maxsat_subset(
3238                weighted,
3239                weights,
3240                &selection.soft_clause_indices,
3241                selection.status,
3242            )?);
3243        }
3244
3245        Ok(encoded)
3246    }
3247
3248    fn encode_weighted_maxsat_subset(
3249        &mut self,
3250        weighted: &SolveInstance,
3251        weights: &[f64],
3252        soft_clause_indices: &[usize],
3253        status: GpuSolverProductionMaxSatSearchStatus,
3254    ) -> Result<GpuSolverProductionEncodedMaxSatSearchCandidate> {
3255        let mut score = 0u64;
3256        let mut clauses = Vec::with_capacity(soft_clause_indices.len());
3257        for &idx in soft_clause_indices {
3258            let clause = &weighted.clauses[idx];
3259            let weight = Self::soft_clause_weight_score(idx, weights[idx])?;
3260            score = score.checked_add(weight).ok_or_else(|| {
3261                XlogError::UnsupportedEpistemicConstruct {
3262                    construct: "GPU solver production MaxSAT encoding".to_string(),
3263                    context: format!(
3264                        "soft-clause selection score overflowed u64 while adding index {}",
3265                        idx
3266                    ),
3267                }
3268            })?;
3269            clauses.push(clause.clone());
3270        }
3271
3272        let candidate_instance = SolveInstance::new(weighted.num_vars, clauses);
3273        let data_plane_before = self.provider.host_transfer_stats();
3274        let launch_metadata_before = self.provider.host_launch_metadata_transfer_stats();
3275        let cnf = GpuCnf::from_host(&candidate_instance, &self.provider)?;
3276        let data_plane_after = self.provider.host_transfer_stats();
3277        let launch_metadata_after = self.provider.host_launch_metadata_transfer_stats();
3278        self.record_encoded_maxsat_cnf_upload_transfer_delta(
3279            data_plane_before,
3280            data_plane_after,
3281            launch_metadata_before,
3282            launch_metadata_after,
3283        )?;
3284        checked_solver_trace_counter_inc!(self, gpu_maxsat_candidate_encodes);
3285        Ok(GpuSolverProductionEncodedMaxSatSearchCandidate { score, cnf, status })
3286    }
3287
3288    fn record_encoded_maxsat_cnf_upload_transfer_delta(
3289        &mut self,
3290        data_plane_before: xlog_cuda::provider::HostTransferStats,
3291        data_plane_after: xlog_cuda::provider::HostTransferStats,
3292        launch_metadata_before: xlog_cuda::provider::HostLaunchMetadataTransferStats,
3293        launch_metadata_after: xlog_cuda::provider::HostLaunchMetadataTransferStats,
3294    ) -> Result<()> {
3295        let data_plane_htod_calls = Self::checked_report_counter_delta(
3296            data_plane_after.htod_calls,
3297            data_plane_before.htod_calls,
3298            "gpu_maxsat_candidate_cnf_data_plane_htod_calls",
3299        )?;
3300        let data_plane_htod_bytes = Self::checked_report_counter_delta(
3301            data_plane_after.htod_bytes,
3302            data_plane_before.htod_bytes,
3303            "gpu_maxsat_candidate_cnf_data_plane_htod_bytes",
3304        )?;
3305        let data_plane_dtoh_calls = Self::checked_report_counter_delta(
3306            data_plane_after.dtoh_calls,
3307            data_plane_before.dtoh_calls,
3308            "gpu_maxsat_candidate_cnf_data_plane_dtoh_calls",
3309        )?;
3310        let data_plane_dtoh_bytes = Self::checked_report_counter_delta(
3311            data_plane_after.dtoh_bytes,
3312            data_plane_before.dtoh_bytes,
3313            "gpu_maxsat_candidate_cnf_data_plane_dtoh_bytes",
3314        )?;
3315        let launch_metadata_htod_calls = Self::checked_report_counter_delta(
3316            launch_metadata_after.htod_calls,
3317            launch_metadata_before.htod_calls,
3318            "gpu_maxsat_candidate_cnf_launch_metadata_htod_calls",
3319        )?;
3320        let launch_metadata_htod_bytes = Self::checked_report_counter_delta(
3321            launch_metadata_after.htod_bytes,
3322            launch_metadata_before.htod_bytes,
3323            "gpu_maxsat_candidate_cnf_launch_metadata_htod_bytes",
3324        )?;
3325
3326        self.trace.gpu_maxsat_candidate_cnf_data_plane_htod_calls =
3327            Self::checked_trace_counter_add(
3328                self.trace.gpu_maxsat_candidate_cnf_data_plane_htod_calls,
3329                data_plane_htod_calls,
3330                "gpu_maxsat_candidate_cnf_data_plane_htod_calls",
3331            )?;
3332        self.trace.gpu_maxsat_candidate_cnf_data_plane_htod_bytes =
3333            Self::checked_trace_counter_add(
3334                self.trace.gpu_maxsat_candidate_cnf_data_plane_htod_bytes,
3335                data_plane_htod_bytes,
3336                "gpu_maxsat_candidate_cnf_data_plane_htod_bytes",
3337            )?;
3338        self.trace.gpu_maxsat_candidate_cnf_data_plane_dtoh_calls =
3339            Self::checked_trace_counter_add(
3340                self.trace.gpu_maxsat_candidate_cnf_data_plane_dtoh_calls,
3341                data_plane_dtoh_calls,
3342                "gpu_maxsat_candidate_cnf_data_plane_dtoh_calls",
3343            )?;
3344        self.trace.gpu_maxsat_candidate_cnf_data_plane_dtoh_bytes =
3345            Self::checked_trace_counter_add(
3346                self.trace.gpu_maxsat_candidate_cnf_data_plane_dtoh_bytes,
3347                data_plane_dtoh_bytes,
3348                "gpu_maxsat_candidate_cnf_data_plane_dtoh_bytes",
3349            )?;
3350        self.trace
3351            .gpu_maxsat_candidate_cnf_launch_metadata_htod_calls = Self::checked_trace_counter_add(
3352            self.trace
3353                .gpu_maxsat_candidate_cnf_launch_metadata_htod_calls,
3354            launch_metadata_htod_calls,
3355            "gpu_maxsat_candidate_cnf_launch_metadata_htod_calls",
3356        )?;
3357        self.trace
3358            .gpu_maxsat_candidate_cnf_launch_metadata_htod_bytes = Self::checked_trace_counter_add(
3359            self.trace
3360                .gpu_maxsat_candidate_cnf_launch_metadata_htod_bytes,
3361            launch_metadata_htod_bytes,
3362            "gpu_maxsat_candidate_cnf_launch_metadata_htod_bytes",
3363        )?;
3364        Ok(())
3365    }
3366
3367    fn solve_weighted_maxsat_search_candidates(
3368        &mut self,
3369        workspace: &mut GpuCdclWorkspace,
3370        candidates: &[GpuSolverProductionMaxSatSearchCandidate<'_>],
3371        frontier_upper_bound_certificates: u64,
3372    ) -> Result<GpuSolverProductionMaxSatReport> {
3373        self.require_workspace_on_adapter_provider(workspace)?;
3374        self.require_weighted_maxsat_search_candidates_and_artifacts(workspace, candidates)?;
3375
3376        let solves_before = self.trace.gpu_maxsat_candidate_solves;
3377        let unsat_prunes_before = self.trace.gpu_maxsat_unsat_candidate_prunes;
3378        let mut optimum_score = 0u64;
3379        let mut satisfiable_candidates = 0u64;
3380
3381        for candidate in candidates {
3382            match candidate.status {
3383                GpuSolverProductionMaxSatSearchStatus::Satisfiable => {
3384                    let _assignment = self.solver.solve_expect_sat_with_branch_limit(
3385                        candidate.cnf,
3386                        candidate.branch_var_limit,
3387                    )?;
3388                    checked_solver_trace_counter_inc!(self, gpu_cdcl_sat_solves);
3389                    checked_solver_trace_counter_inc!(self, gpu_maxsat_candidate_solves);
3390                    satisfiable_candidates = Self::checked_trace_counter_add(
3391                        satisfiable_candidates,
3392                        1,
3393                        "maxsat_satisfiable_candidates",
3394                    )?;
3395                    optimum_score = optimum_score.max(candidate.score);
3396                }
3397                GpuSolverProductionMaxSatSearchStatus::Unsatisfiable => {
3398                    self.solve_expect_unsat_with_branch_limit_ws(
3399                        workspace,
3400                        candidate.cnf,
3401                        candidate.branch_var_limit,
3402                    )?;
3403                    checked_solver_trace_counter_inc!(self, gpu_maxsat_candidate_solves);
3404                    checked_solver_trace_counter_inc!(self, gpu_maxsat_unsat_candidate_prunes);
3405                }
3406            }
3407        }
3408
3409        checked_solver_trace_counter_inc!(self, gpu_maxsat_optima);
3410        let gpu_cdcl_candidate_solves = Self::checked_report_counter_delta(
3411            self.trace.gpu_maxsat_candidate_solves,
3412            solves_before,
3413            "gpu_maxsat_candidate_solves",
3414        )?;
3415        if frontier_upper_bound_certificates != 0 {
3416            self.trace.gpu_maxsat_frontier_certified_candidate_solves =
3417                Self::checked_trace_counter_add(
3418                    self.trace.gpu_maxsat_frontier_certified_candidate_solves,
3419                    gpu_cdcl_candidate_solves,
3420                    "gpu_maxsat_frontier_certified_candidate_solves",
3421                )?;
3422        }
3423
3424        Ok(GpuSolverProductionMaxSatReport {
3425            candidate_evidence_records: 0,
3426            optimum_score,
3427            candidates_checked: candidates.len() as u64,
3428            satisfiable_candidates,
3429            unsat_candidates_pruned: Self::checked_report_counter_delta(
3430                self.trace.gpu_maxsat_unsat_candidate_prunes,
3431                unsat_prunes_before,
3432                "gpu_maxsat_unsat_candidate_prunes",
3433            )?,
3434            gpu_cdcl_candidate_encodes: 0,
3435            gpu_cdcl_candidate_solves,
3436            frontier_upper_bound_certificates,
3437        })
3438    }
3439
3440    fn require_weighted_maxsat_search_candidates(
3441        candidates: &[GpuSolverProductionMaxSatSearchCandidate<'_>],
3442    ) -> Result<()> {
3443        if candidates.is_empty() {
3444            return Err(XlogError::UnsupportedEpistemicConstruct {
3445                construct: "GPU solver production MaxSAT search".to_string(),
3446                context: "bounded MaxSAT search requires at least one candidate CNF".to_string(),
3447            });
3448        }
3449        if !candidates.iter().any(|candidate| {
3450            matches!(
3451                candidate.status,
3452                GpuSolverProductionMaxSatSearchStatus::Satisfiable
3453            )
3454        }) {
3455            return Err(XlogError::UnsupportedEpistemicConstruct {
3456                construct: "GPU solver production MaxSAT search".to_string(),
3457                context: "bounded MaxSAT search requires at least one satisfiable GPU candidate"
3458                    .to_string(),
3459            });
3460        }
3461        Ok(())
3462    }
3463
3464    fn require_weighted_maxsat_search_selections(
3465        selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
3466    ) -> Result<()> {
3467        if selections.is_empty() {
3468            return Err(XlogError::UnsupportedEpistemicConstruct {
3469                construct: "GPU solver production MaxSAT encoding".to_string(),
3470                context: "weighted MaxSAT encoding requires at least one selection".to_string(),
3471            });
3472        }
3473        Ok(())
3474    }
3475
3476    fn require_weighted_maxsat_encoding_inputs(
3477        weighted: &SolveInstance,
3478        selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
3479    ) -> Result<()> {
3480        Self::require_weighted_maxsat_search_selections(selections)?;
3481
3482        if weighted.objective != Objective::MaxSat {
3483            return Err(XlogError::UnsupportedEpistemicConstruct {
3484                construct: "GPU solver production MaxSAT encoding".to_string(),
3485                context: format!(
3486                    "weighted MaxSAT encoding requires Objective::MaxSat, got {:?}",
3487                    weighted.objective
3488                ),
3489            });
3490        }
3491        if weighted.num_vars == 0 {
3492            return Err(XlogError::UnsupportedEpistemicConstruct {
3493                construct: "GPU solver production MaxSAT encoding".to_string(),
3494                context: "weighted MaxSAT encoding requires num_vars > 0".to_string(),
3495            });
3496        }
3497
3498        let weights =
3499            weighted
3500                .weights
3501                .as_ref()
3502                .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3503                    construct: "GPU solver production MaxSAT encoding".to_string(),
3504                    context: "weighted MaxSAT encoding requires explicit soft-clause weights"
3505                        .to_string(),
3506                })?;
3507        if weights.len() != weighted.clauses.len() {
3508            return Err(XlogError::UnsupportedEpistemicConstruct {
3509                construct: "GPU solver production MaxSAT encoding".to_string(),
3510                context: format!(
3511                    "soft-clause weights length {} does not match clause count {}",
3512                    weights.len(),
3513                    weighted.clauses.len()
3514                ),
3515            });
3516        }
3517
3518        for selection in selections {
3519            if selection.soft_clause_indices.is_empty() {
3520                return Err(XlogError::UnsupportedEpistemicConstruct {
3521                    construct: "GPU solver production MaxSAT encoding".to_string(),
3522                    context:
3523                        "weighted MaxSAT search selections must include at least one soft clause"
3524                            .to_string(),
3525                });
3526            }
3527            let mut seen_indices = BTreeSet::new();
3528            for (position, &idx) in selection.soft_clause_indices.iter().enumerate() {
3529                if !seen_indices.insert(idx) {
3530                    return Err(XlogError::UnsupportedEpistemicConstruct {
3531                        construct: "GPU solver production MaxSAT encoding".to_string(),
3532                        context: format!(
3533                            "soft-clause selection duplicates index {} at position {}; \
3534                             weighted MaxSAT candidates must count each soft clause at most once",
3535                            idx, position
3536                        ),
3537                    });
3538                }
3539            }
3540            for &idx in selection.soft_clause_indices {
3541                let _clause = weighted.clauses.get(idx).ok_or_else(|| {
3542                    XlogError::UnsupportedEpistemicConstruct {
3543                        construct: "GPU solver production MaxSAT encoding".to_string(),
3544                        context: format!(
3545                            "soft-clause selection index {} is out of range for {} clauses",
3546                            idx,
3547                            weighted.clauses.len()
3548                        ),
3549                    }
3550                })?;
3551                let weight =
3552                    *weights
3553                        .get(idx)
3554                        .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3555                            construct: "GPU solver production MaxSAT encoding".to_string(),
3556                            context: format!(
3557                                "soft-clause weight index {} is out of range for {} weights",
3558                                idx,
3559                                weights.len()
3560                            ),
3561                        })?;
3562                let _ = Self::soft_clause_weight_score(idx, weight)?;
3563            }
3564        }
3565        Ok(())
3566    }
3567
3568    fn complete_weighted_maxsat_frontier_selections(
3569        weighted: &SolveInstance,
3570        weights: &[f64],
3571        selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
3572    ) -> Result<GpuSolverProductionCompletedWeightedMaxSatFrontier> {
3573        let mut completed = Vec::with_capacity(selections.len());
3574        let mut completion_candidate_count = 0u64;
3575        let mut seen = BTreeMap::new();
3576
3577        for selection in selections {
3578            let mut indices = selection.soft_clause_indices.to_vec();
3579            indices.sort_unstable();
3580            match seen.get(&indices) {
3581                Some(status) if *status != selection.status => {
3582                    return Err(XlogError::UnsupportedEpistemicConstruct {
3583                        construct: "GPU solver production MaxSAT encoding".to_string(),
3584                        context: format!(
3585                            "soft-clause selection {:?} has conflicting statuses {:?} and {:?}",
3586                            indices, status, selection.status
3587                        ),
3588                    });
3589                }
3590                Some(_) => continue,
3591                None => {
3592                    seen.insert(indices.clone(), selection.status);
3593                    completed.push(GpuSolverProductionOwnedWeightedMaxSatSelection {
3594                        soft_clause_indices: indices,
3595                        status: selection.status,
3596                    });
3597                }
3598            }
3599        }
3600
3601        let all_clause_indices: Vec<_> = (0..weighted.clauses.len()).collect();
3602        let certificates = Self::unsat_frontier_certificates(weights, &completed)?;
3603        let disjoint_frontier = certificates.len() > 1
3604            && Self::frontier_certificates_are_pairwise_disjoint(&certificates);
3605        Self::require_weighted_maxsat_frontier_completion_bound(&certificates, disjoint_frontier)?;
3606        if disjoint_frontier {
3607            let mut exclusions = Vec::with_capacity(certificates.len());
3608            Self::complete_disjoint_unsat_frontier_boundaries(
3609                &certificates,
3610                0,
3611                &mut exclusions,
3612                &all_clause_indices,
3613                &mut seen,
3614                &mut completed,
3615                &mut completion_candidate_count,
3616            )?;
3617        } else {
3618            for certificate in &certificates {
3619                for &excluded_idx in &certificate.min_weight_indices {
3620                    let boundary: Vec<_> = all_clause_indices
3621                        .iter()
3622                        .copied()
3623                        .filter(|idx| *idx != excluded_idx)
3624                        .collect();
3625                    Self::push_completed_frontier_candidate(
3626                        boundary,
3627                        &mut seen,
3628                        &mut completed,
3629                        &mut completion_candidate_count,
3630                    )?;
3631                }
3632            }
3633        }
3634
3635        Self::require_weighted_maxsat_frontier_upper_bound(weights, &completed)?;
3636        Ok(GpuSolverProductionCompletedWeightedMaxSatFrontier {
3637            selections: completed,
3638            completion_candidate_count,
3639        })
3640    }
3641
3642    fn require_weighted_maxsat_frontier_completion_bound(
3643        certificates: &[GpuSolverProductionUnsatFrontierCertificate],
3644        disjoint_frontier: bool,
3645    ) -> Result<()> {
3646        let implied_candidates = if disjoint_frontier {
3647            certificates.iter().try_fold(1u64, |acc, certificate| {
3648                acc.checked_mul(certificate.min_weight_indices.len() as u64)
3649                    .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3650                        construct: "GPU solver production MaxSAT encoding".to_string(),
3651                        context: "weighted MaxSAT disjoint frontier completion bound overflowed"
3652                            .to_string(),
3653                    })
3654            })?
3655        } else {
3656            certificates.iter().try_fold(0u64, |acc, certificate| {
3657                acc.checked_add(certificate.min_weight_indices.len() as u64)
3658                    .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3659                        construct: "GPU solver production MaxSAT encoding".to_string(),
3660                        context: "weighted MaxSAT frontier completion bound overflowed".to_string(),
3661                    })
3662            })?
3663        };
3664
3665        if implied_candidates > MAX_WEIGHTED_MAXSAT_FRONTIER_COMPLETION_CANDIDATES {
3666            return Err(XlogError::UnsupportedEpistemicConstruct {
3667                construct: "GPU solver production MaxSAT encoding".to_string(),
3668                context: format!(
3669                    "weighted MaxSAT frontier completion would require {} CPU-generated \
3670                     boundary candidates, exceeding production bound {}; provide explicit \
3671                     GPU scheduler selections",
3672                    implied_candidates, MAX_WEIGHTED_MAXSAT_FRONTIER_COMPLETION_CANDIDATES
3673                ),
3674            });
3675        }
3676        Ok(())
3677    }
3678
3679    fn unsat_frontier_certificates(
3680        weights: &[f64],
3681        selections: &[GpuSolverProductionOwnedWeightedMaxSatSelection],
3682    ) -> Result<Vec<GpuSolverProductionUnsatFrontierCertificate>> {
3683        let mut certificates = Vec::new();
3684        for selection in selections {
3685            if selection.status != GpuSolverProductionMaxSatSearchStatus::Unsatisfiable {
3686                continue;
3687            }
3688            let mut indices = selection.soft_clause_indices.clone();
3689            indices.sort_unstable();
3690            let mut min_weight = None;
3691            let mut min_weight_indices = Vec::new();
3692            for &idx in &indices {
3693                let weight = Self::soft_clause_weight_score(idx, weights[idx])?;
3694                match min_weight {
3695                    None => {
3696                        min_weight = Some(weight);
3697                        min_weight_indices.push(idx);
3698                    }
3699                    Some(current) if weight < current => {
3700                        min_weight = Some(weight);
3701                        min_weight_indices.clear();
3702                        min_weight_indices.push(idx);
3703                    }
3704                    Some(current) if weight == current => min_weight_indices.push(idx),
3705                    Some(_) => {}
3706                }
3707            }
3708            if let Some(min_weight) = min_weight {
3709                certificates.push(GpuSolverProductionUnsatFrontierCertificate {
3710                    indices,
3711                    min_weight,
3712                    min_weight_indices,
3713                });
3714            }
3715        }
3716        Ok(certificates)
3717    }
3718
3719    fn frontier_certificates_are_pairwise_disjoint(
3720        certificates: &[GpuSolverProductionUnsatFrontierCertificate],
3721    ) -> bool {
3722        let mut seen = BTreeSet::new();
3723        for certificate in certificates {
3724            for &idx in &certificate.indices {
3725                if !seen.insert(idx) {
3726                    return false;
3727                }
3728            }
3729        }
3730        true
3731    }
3732
3733    fn complete_disjoint_unsat_frontier_boundaries(
3734        certificates: &[GpuSolverProductionUnsatFrontierCertificate],
3735        depth: usize,
3736        exclusions: &mut Vec<usize>,
3737        all_clause_indices: &[usize],
3738        seen: &mut BTreeMap<Vec<usize>, GpuSolverProductionMaxSatSearchStatus>,
3739        completed: &mut Vec<GpuSolverProductionOwnedWeightedMaxSatSelection>,
3740        completion_candidate_count: &mut u64,
3741    ) -> Result<()> {
3742        if depth == certificates.len() {
3743            let exclusion_set: BTreeSet<_> = exclusions.iter().copied().collect();
3744            let boundary: Vec<_> = all_clause_indices
3745                .iter()
3746                .copied()
3747                .filter(|idx| !exclusion_set.contains(idx))
3748                .collect();
3749            return Self::push_completed_frontier_candidate(
3750                boundary,
3751                seen,
3752                completed,
3753                completion_candidate_count,
3754            );
3755        }
3756
3757        for &excluded_idx in &certificates[depth].min_weight_indices {
3758            exclusions.push(excluded_idx);
3759            Self::complete_disjoint_unsat_frontier_boundaries(
3760                certificates,
3761                depth + 1,
3762                exclusions,
3763                all_clause_indices,
3764                seen,
3765                completed,
3766                completion_candidate_count,
3767            )?;
3768            exclusions.pop();
3769        }
3770        Ok(())
3771    }
3772
3773    fn push_completed_frontier_candidate(
3774        boundary: Vec<usize>,
3775        seen: &mut BTreeMap<Vec<usize>, GpuSolverProductionMaxSatSearchStatus>,
3776        completed: &mut Vec<GpuSolverProductionOwnedWeightedMaxSatSelection>,
3777        completion_candidate_count: &mut u64,
3778    ) -> Result<()> {
3779        if boundary.is_empty() || seen.contains_key(&boundary) {
3780            return Ok(());
3781        }
3782        if *completion_candidate_count >= MAX_WEIGHTED_MAXSAT_FRONTIER_COMPLETION_CANDIDATES {
3783            return Err(XlogError::UnsupportedEpistemicConstruct {
3784                construct: "GPU solver production MaxSAT encoding".to_string(),
3785                context: format!(
3786                    "weighted MaxSAT frontier completion exceeded production bound {}; \
3787                     provide explicit GPU scheduler selections",
3788                    MAX_WEIGHTED_MAXSAT_FRONTIER_COMPLETION_CANDIDATES
3789                ),
3790            });
3791        }
3792        seen.insert(
3793            boundary.clone(),
3794            GpuSolverProductionMaxSatSearchStatus::Satisfiable,
3795        );
3796        *completion_candidate_count =
3797            completion_candidate_count.checked_add(1).ok_or_else(|| {
3798                XlogError::UnsupportedEpistemicConstruct {
3799                    construct: "GPU solver production MaxSAT encoding".to_string(),
3800                    context: "frontier completion candidate count overflowed".to_string(),
3801                }
3802            })?;
3803        completed.push(GpuSolverProductionOwnedWeightedMaxSatSelection {
3804            soft_clause_indices: boundary,
3805            status: GpuSolverProductionMaxSatSearchStatus::Satisfiable,
3806        });
3807        Ok(())
3808    }
3809
3810    fn require_weighted_maxsat_frontier_upper_bound(
3811        weights: &[f64],
3812        selections: &[GpuSolverProductionOwnedWeightedMaxSatSelection],
3813    ) -> Result<()> {
3814        let mut total_score = 0u64;
3815        for (idx, &weight) in weights.iter().enumerate() {
3816            total_score = total_score
3817                .checked_add(Self::soft_clause_weight_score(idx, weight)?)
3818                .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3819                    construct: "GPU solver production MaxSAT encoding".to_string(),
3820                    context: format!(
3821                        "weighted MaxSAT total score overflowed while adding soft clause {}",
3822                        idx
3823                    ),
3824                })?;
3825        }
3826
3827        let mut upper_bound = total_score;
3828        let mut best_satisfiable_score = 0u64;
3829        let certificates = Self::unsat_frontier_certificates(weights, selections)?;
3830
3831        for selection in selections {
3832            let mut indices = selection.soft_clause_indices.to_vec();
3833            indices.sort_unstable();
3834            let score = Self::weighted_maxsat_selection_score(weights, &indices)?;
3835            match selection.status {
3836                GpuSolverProductionMaxSatSearchStatus::Satisfiable => {
3837                    best_satisfiable_score = best_satisfiable_score.max(score);
3838                }
3839                GpuSolverProductionMaxSatSearchStatus::Unsatisfiable => {}
3840            }
3841        }
3842
3843        if certificates.len() > 1
3844            && Self::frontier_certificates_are_pairwise_disjoint(&certificates)
3845        {
3846            let certified_loss = certificates.iter().try_fold(0u64, |acc, certificate| {
3847                acc.checked_add(certificate.min_weight).ok_or_else(|| {
3848                    XlogError::UnsupportedEpistemicConstruct {
3849                        construct: "GPU solver production MaxSAT encoding".to_string(),
3850                        context: "weighted MaxSAT disjoint frontier loss overflowed".to_string(),
3851                    }
3852                })
3853            })?;
3854            upper_bound = total_score.checked_sub(certified_loss).ok_or_else(|| {
3855                XlogError::UnsupportedEpistemicConstruct {
3856                    construct: "GPU solver production MaxSAT encoding".to_string(),
3857                    context: format!(
3858                        "weighted MaxSAT disjoint frontier loss {} exceeds total score {}",
3859                        certified_loss, total_score
3860                    ),
3861                }
3862            })?;
3863        } else {
3864            for certificate in &certificates {
3865                let certificate_bound =
3866                    total_score
3867                        .checked_sub(certificate.min_weight)
3868                        .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3869                            construct: "GPU solver production MaxSAT encoding".to_string(),
3870                            context: format!(
3871                                "weighted MaxSAT UNSAT certificate {:?} has minimum weight {} above total score {}",
3872                                certificate.indices, certificate.min_weight, total_score
3873                            ),
3874                        })?;
3875                upper_bound = upper_bound.min(certificate_bound);
3876            }
3877        }
3878
3879        if best_satisfiable_score < upper_bound {
3880            return Err(XlogError::UnsupportedEpistemicConstruct {
3881                construct: "GPU solver production MaxSAT encoding".to_string(),
3882                context: format!(
3883                    "weighted MaxSAT frontier is incomplete: best GPU-certified satisfiable score {} is below the certified upper bound {}",
3884                    best_satisfiable_score, upper_bound
3885                ),
3886            });
3887        }
3888        Ok(())
3889    }
3890
3891    fn weighted_maxsat_selection_score(weights: &[f64], indices: &[usize]) -> Result<u64> {
3892        let mut score = 0u64;
3893        for &idx in indices {
3894            let weight =
3895                *weights
3896                    .get(idx)
3897                    .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3898                        construct: "GPU solver production MaxSAT encoding".to_string(),
3899                        context: format!(
3900                            "soft-clause weight index {} is out of range for {} weights",
3901                            idx,
3902                            weights.len()
3903                        ),
3904                    })?;
3905            score = score
3906                .checked_add(Self::soft_clause_weight_score(idx, weight)?)
3907                .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3908                    construct: "GPU solver production MaxSAT encoding".to_string(),
3909                    context: format!(
3910                        "soft-clause selection score overflowed u64 while adding index {}",
3911                        idx
3912                    ),
3913                })?;
3914        }
3915        Ok(score)
3916    }
3917
3918    fn soft_clause_weight_score(idx: usize, weight: f64) -> Result<u64> {
3919        if !weight.is_finite() || weight < 0.0 || weight.fract() != 0.0 {
3920            return Err(XlogError::UnsupportedEpistemicConstruct {
3921                construct: "GPU solver production MaxSAT encoding".to_string(),
3922                context: format!(
3923                    "soft-clause weight at index {} must be a finite nonnegative integer, got {}",
3924                    idx, weight
3925                ),
3926            });
3927        }
3928        if weight >= u64::MAX as f64 {
3929            return Err(XlogError::UnsupportedEpistemicConstruct {
3930                construct: "GPU solver production MaxSAT encoding".to_string(),
3931                context: format!(
3932                    "soft-clause weight at index {} exceeds u64 score range",
3933                    idx
3934                ),
3935            });
3936        }
3937        Ok(weight as u64)
3938    }
3939
3940    /// Search a bounded weighted MaxSAT candidate set after accepted GPU epistemic execution.
3941    ///
3942    /// Satisfiable candidates are scored through the existing GPU CDCL SAT path.
3943    /// Unsatisfiable candidates are pruned through the existing workspace-backed
3944    /// GPU CDCL UNSAT path. The adapter records no CPU assignment or MaxSAT
3945    /// enumeration.
3946    pub fn solve_weighted_maxsat_search_with_gpu_execution_result(
3947        &mut self,
3948        provider: &CudaKernelProvider,
3949        result: &EpistemicGpuExecutionResult,
3950        workspace: &mut GpuCdclWorkspace,
3951        candidates: &[GpuSolverProductionMaxSatSearchCandidate<'_>],
3952    ) -> Result<GpuSolverProductionMaxSatReport> {
3953        self.with_trace_rollback(|this| {
3954            this.solve_weighted_maxsat_search_with_gpu_execution_result_impl(
3955                provider, result, workspace, candidates,
3956            )
3957        })
3958    }
3959
3960    fn solve_weighted_maxsat_search_with_gpu_execution_result_impl(
3961        &mut self,
3962        provider: &CudaKernelProvider,
3963        result: &EpistemicGpuExecutionResult,
3964        workspace: &mut GpuCdclWorkspace,
3965        candidates: &[GpuSolverProductionMaxSatSearchCandidate<'_>],
3966    ) -> Result<GpuSolverProductionMaxSatReport> {
3967        self.require_workspace_on_adapter_provider(workspace)?;
3968        self.require_weighted_maxsat_search_candidates_and_artifacts(workspace, candidates)?;
3969        let state = self.require_accepted_gpu_solver_evidence(provider, result)?;
3970        let events_before = self.trace.accepted_path_event_snapshot()?;
3971        let mut report = self.solve_weighted_maxsat_search_candidates(workspace, candidates, 0)?;
3972        report.candidate_evidence_records = 1;
3973        self.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
3974        self.record_accepted_gpu_candidate_state(&state)?;
3975        Ok(report)
3976    }
3977
3978    /// Search a bounded weighted MaxSAT candidate set once per accepted split-batch component.
3979    pub fn solve_weighted_maxsat_search_with_gpu_batch_execution_result(
3980        &mut self,
3981        provider: &CudaKernelProvider,
3982        evidence: GpuSolverProductionBatchExecutionEvidence<'_>,
3983        workspace: &mut GpuCdclWorkspace,
3984        candidates: &[GpuSolverProductionMaxSatSearchCandidate<'_>],
3985    ) -> Result<GpuSolverProductionMaxSatReport> {
3986        self.with_trace_rollback(|this| {
3987            this.require_workspace_on_adapter_provider(workspace)?;
3988            this.require_weighted_maxsat_search_candidates_and_artifacts(workspace, candidates)?;
3989            let results =
3990                this.accepted_solver_results_from_gpu_batch_execution_evidence(provider, evidence)?;
3991            let report = this
3992                .solve_multi_candidate_weighted_maxsat_search_with_gpu_execution_results(
3993                    provider, &results, workspace, candidates,
3994                )?;
3995            this.record_accepted_gpu_batch_candidate_evidence(results.len())?;
3996            Ok(report)
3997        })
3998    }
3999
4000    /// Search a bounded weighted MaxSAT candidate set once per accepted GPU evidence record.
4001    pub fn solve_multi_candidate_weighted_maxsat_search_with_gpu_execution_results(
4002        &mut self,
4003        provider: &CudaKernelProvider,
4004        results: &[&EpistemicGpuExecutionResult],
4005        workspace: &mut GpuCdclWorkspace,
4006        candidates: &[GpuSolverProductionMaxSatSearchCandidate<'_>],
4007    ) -> Result<GpuSolverProductionMaxSatReport> {
4008        self.with_trace_rollback(|this| {
4009            this.solve_multi_candidate_weighted_maxsat_search_with_gpu_execution_results_impl(
4010                provider, results, workspace, candidates,
4011            )
4012        })
4013    }
4014
4015    fn solve_multi_candidate_weighted_maxsat_search_with_gpu_execution_results_impl(
4016        &mut self,
4017        provider: &CudaKernelProvider,
4018        results: &[&EpistemicGpuExecutionResult],
4019        workspace: &mut GpuCdclWorkspace,
4020        candidates: &[GpuSolverProductionMaxSatSearchCandidate<'_>],
4021    ) -> Result<GpuSolverProductionMaxSatReport> {
4022        if results.is_empty() {
4023            return Err(XlogError::UnsupportedEpistemicConstruct {
4024                construct: "GPU solver production MaxSAT search".to_string(),
4025                context: "multi-candidate MaxSAT search requires at least one accepted GPU result"
4026                    .to_string(),
4027            });
4028        }
4029        self.require_workspace_on_adapter_provider(workspace)?;
4030        self.require_weighted_maxsat_search_candidates_and_artifacts(workspace, candidates)?;
4031        let states = self.require_accepted_gpu_solver_states(provider, results)?;
4032
4033        let mut report = GpuSolverProductionMaxSatReport::default();
4034        for state in &states {
4035            let events_before = self.trace.accepted_path_event_snapshot()?;
4036            let step_report =
4037                self.solve_weighted_maxsat_search_candidates(workspace, candidates, 0)?;
4038            checked_solver_report_counter_inc!(report, candidate_evidence_records);
4039            report.optimum_score = report.optimum_score.max(step_report.optimum_score);
4040            checked_solver_report_counter_add!(
4041                report,
4042                candidates_checked,
4043                step_report.candidates_checked
4044            );
4045            checked_solver_report_counter_add!(
4046                report,
4047                satisfiable_candidates,
4048                step_report.satisfiable_candidates
4049            );
4050            checked_solver_report_counter_add!(
4051                report,
4052                unsat_candidates_pruned,
4053                step_report.unsat_candidates_pruned
4054            );
4055            checked_solver_report_counter_add!(
4056                report,
4057                gpu_cdcl_candidate_encodes,
4058                step_report.gpu_cdcl_candidate_encodes
4059            );
4060            checked_solver_report_counter_add!(
4061                report,
4062                gpu_cdcl_candidate_solves,
4063                step_report.gpu_cdcl_candidate_solves
4064            );
4065            checked_solver_report_counter_add!(
4066                report,
4067                frontier_upper_bound_certificates,
4068                step_report.frontier_upper_bound_certificates
4069            );
4070            self.record_accepted_gpu_solver_production_path_events_since(events_before, state)?;
4071            self.record_accepted_gpu_candidate_state(state)?;
4072        }
4073
4074        Ok(report)
4075    }
4076
4077    /// Encode weighted soft-clause selections, then search them after accepted GPU evidence.
4078    ///
4079    /// Candidate construction is bounded by caller-declared selections. The adapter
4080    /// builds satisfaction CNFs for those selections, uploads them through the existing
4081    /// GPU CNF layout, and dispatches SAT/UNSAT certification through GPU CDCL. It
4082    /// performs no CPU assignment or MaxSAT subset enumeration.
4083    pub fn solve_weighted_maxsat_encoded_search_with_gpu_execution_result(
4084        &mut self,
4085        provider: &CudaKernelProvider,
4086        result: &EpistemicGpuExecutionResult,
4087        workspace: &mut GpuCdclWorkspace,
4088        weighted: &SolveInstance,
4089        branch_var_limit: &TrackedCudaSlice<u32>,
4090        selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
4091    ) -> Result<GpuSolverProductionMaxSatReport> {
4092        self.with_trace_rollback(|this| {
4093            this.solve_weighted_maxsat_encoded_search_with_gpu_execution_result_impl(
4094                provider,
4095                result,
4096                workspace,
4097                weighted,
4098                branch_var_limit,
4099                selections,
4100            )
4101        })
4102    }
4103
4104    fn solve_weighted_maxsat_encoded_search_with_gpu_execution_result_impl(
4105        &mut self,
4106        provider: &CudaKernelProvider,
4107        result: &EpistemicGpuExecutionResult,
4108        workspace: &mut GpuCdclWorkspace,
4109        weighted: &SolveInstance,
4110        branch_var_limit: &TrackedCudaSlice<u32>,
4111        selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
4112    ) -> Result<GpuSolverProductionMaxSatReport> {
4113        self.require_workspace_on_adapter_provider(workspace)?;
4114        self.require_weighted_maxsat_encoded_search_inputs_and_artifacts(
4115            workspace,
4116            weighted,
4117            branch_var_limit,
4118            selections,
4119        )?;
4120        let state = self.require_accepted_gpu_solver_evidence(provider, result)?;
4121        let events_before = self.trace.accepted_path_event_snapshot()?;
4122        let encodes_before = self.trace.gpu_maxsat_candidate_encodes;
4123        let certificates_before = self.trace.gpu_maxsat_frontier_upper_bound_certificates;
4124        let encoded = self.encode_weighted_maxsat_search_candidates(weighted, selections)?;
4125        let frontier_upper_bound_certificates = Self::checked_report_counter_delta(
4126            self.trace.gpu_maxsat_frontier_upper_bound_certificates,
4127            certificates_before,
4128            "gpu_maxsat_frontier_upper_bound_certificates",
4129        )?;
4130        let search_candidates: Vec<_> = encoded
4131            .iter()
4132            .map(|candidate| GpuSolverProductionMaxSatSearchCandidate {
4133                score: candidate.score,
4134                cnf: &candidate.cnf,
4135                branch_var_limit,
4136                status: candidate.status,
4137            })
4138            .collect();
4139        let mut report = self.solve_weighted_maxsat_search_candidates(
4140            workspace,
4141            &search_candidates,
4142            frontier_upper_bound_certificates,
4143        )?;
4144        report.candidate_evidence_records = 1;
4145        report.gpu_cdcl_candidate_encodes = Self::checked_report_counter_delta(
4146            self.trace.gpu_maxsat_candidate_encodes,
4147            encodes_before,
4148            "gpu_cdcl_candidate_encodes",
4149        )?;
4150        self.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
4151        self.record_accepted_gpu_candidate_state(&state)?;
4152        Ok(report)
4153    }
4154
4155    /// Encode weighted soft-clause selections, then search once per accepted GPU evidence record.
4156    ///
4157    /// This is the multi-candidate scheduler-facing variant of the bounded encoded
4158    /// MaxSAT search adapter. It validates all accepted GPU epistemic evidence up
4159    /// front, encodes the caller-declared selections through the existing GPU CNF
4160    /// layout for each accepted record, and dispatches each candidate through GPU
4161    /// CDCL SAT/UNSAT certification without CPU assignment or MaxSAT enumeration.
4162    pub fn solve_multi_candidate_weighted_maxsat_encoded_search_with_gpu_execution_results(
4163        &mut self,
4164        provider: &CudaKernelProvider,
4165        results: &[&EpistemicGpuExecutionResult],
4166        workspace: &mut GpuCdclWorkspace,
4167        weighted: &SolveInstance,
4168        branch_var_limit: &TrackedCudaSlice<u32>,
4169        selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
4170    ) -> Result<GpuSolverProductionMaxSatReport> {
4171        self.with_trace_rollback(|this| {
4172            this.solve_multi_candidate_weighted_maxsat_encoded_search_with_gpu_execution_results_impl(
4173                provider,
4174                results,
4175                workspace,
4176                weighted,
4177                branch_var_limit,
4178                selections,
4179            )
4180        })
4181    }
4182
4183    fn solve_multi_candidate_weighted_maxsat_encoded_search_with_gpu_execution_results_impl(
4184        &mut self,
4185        provider: &CudaKernelProvider,
4186        results: &[&EpistemicGpuExecutionResult],
4187        workspace: &mut GpuCdclWorkspace,
4188        weighted: &SolveInstance,
4189        branch_var_limit: &TrackedCudaSlice<u32>,
4190        selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
4191    ) -> Result<GpuSolverProductionMaxSatReport> {
4192        if results.is_empty() {
4193            return Err(XlogError::UnsupportedEpistemicConstruct {
4194                construct: "GPU solver production MaxSAT encoding".to_string(),
4195                context:
4196                    "multi-candidate weighted MaxSAT encoded search requires at least one accepted GPU result"
4197                        .to_string(),
4198            });
4199        }
4200        self.require_workspace_on_adapter_provider(workspace)?;
4201        self.require_weighted_maxsat_encoded_search_inputs_and_artifacts(
4202            workspace,
4203            weighted,
4204            branch_var_limit,
4205            selections,
4206        )?;
4207        let states = self.require_accepted_gpu_solver_states(provider, results)?;
4208
4209        let mut report = GpuSolverProductionMaxSatReport::default();
4210        for state in &states {
4211            let events_before = self.trace.accepted_path_event_snapshot()?;
4212            let encodes_before = self.trace.gpu_maxsat_candidate_encodes;
4213            let certificates_before = self.trace.gpu_maxsat_frontier_upper_bound_certificates;
4214            let encoded = self.encode_weighted_maxsat_search_candidates(weighted, selections)?;
4215            let frontier_upper_bound_certificates = Self::checked_report_counter_delta(
4216                self.trace.gpu_maxsat_frontier_upper_bound_certificates,
4217                certificates_before,
4218                "gpu_maxsat_frontier_upper_bound_certificates",
4219            )?;
4220            let search_candidates: Vec<_> = encoded
4221                .iter()
4222                .map(|candidate| GpuSolverProductionMaxSatSearchCandidate {
4223                    score: candidate.score,
4224                    cnf: &candidate.cnf,
4225                    branch_var_limit,
4226                    status: candidate.status,
4227                })
4228                .collect();
4229            let step_report = self.solve_weighted_maxsat_search_candidates(
4230                workspace,
4231                &search_candidates,
4232                frontier_upper_bound_certificates,
4233            )?;
4234            checked_solver_report_counter_inc!(report, candidate_evidence_records);
4235            report.optimum_score = report.optimum_score.max(step_report.optimum_score);
4236            checked_solver_report_counter_add!(
4237                report,
4238                candidates_checked,
4239                step_report.candidates_checked
4240            );
4241            checked_solver_report_counter_add!(
4242                report,
4243                satisfiable_candidates,
4244                step_report.satisfiable_candidates
4245            );
4246            checked_solver_report_counter_add!(
4247                report,
4248                unsat_candidates_pruned,
4249                step_report.unsat_candidates_pruned
4250            );
4251            let encoded_delta = Self::checked_report_counter_delta(
4252                self.trace.gpu_maxsat_candidate_encodes,
4253                encodes_before,
4254                "gpu_cdcl_candidate_encodes",
4255            )?;
4256            checked_solver_report_counter_add!(report, gpu_cdcl_candidate_encodes, encoded_delta);
4257            checked_solver_report_counter_add!(
4258                report,
4259                gpu_cdcl_candidate_solves,
4260                step_report.gpu_cdcl_candidate_solves
4261            );
4262            checked_solver_report_counter_add!(
4263                report,
4264                frontier_upper_bound_certificates,
4265                step_report.frontier_upper_bound_certificates
4266            );
4267            self.record_accepted_gpu_solver_production_path_events_since(events_before, state)?;
4268            self.record_accepted_gpu_candidate_state(state)?;
4269        }
4270
4271        Ok(report)
4272    }
4273
4274    /// Encode weighted soft-clause selections, then search once per accepted split-batch component.
4275    ///
4276    /// The batch evidence must prove every split component reused the existing
4277    /// single-plan GPU runtime path before each component is delegated to the
4278    /// existing multi-candidate weighted MaxSAT encoding adapter.
4279    pub fn solve_weighted_maxsat_encoded_search_with_gpu_batch_execution_result(
4280        &mut self,
4281        provider: &CudaKernelProvider,
4282        evidence: GpuSolverProductionBatchExecutionEvidence<'_>,
4283        workspace: &mut GpuCdclWorkspace,
4284        weighted: &SolveInstance,
4285        branch_var_limit: &TrackedCudaSlice<u32>,
4286        selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
4287    ) -> Result<GpuSolverProductionMaxSatReport> {
4288        self.with_trace_rollback(|this| {
4289            this.require_workspace_on_adapter_provider(workspace)?;
4290            this.require_weighted_maxsat_encoded_search_inputs_and_artifacts(
4291                workspace,
4292                weighted,
4293                branch_var_limit,
4294                selections,
4295            )?;
4296            let results =
4297                this.accepted_solver_results_from_gpu_batch_execution_evidence(provider, evidence)?;
4298            let report = this
4299                .solve_multi_candidate_weighted_maxsat_encoded_search_with_gpu_execution_results(
4300                    provider,
4301                    &results,
4302                    workspace,
4303                    weighted,
4304                    branch_var_limit,
4305                    selections,
4306                )?;
4307            this.record_accepted_gpu_batch_candidate_evidence(results.len())?;
4308            Ok(report)
4309        })
4310    }
4311
4312    fn add_maxsat_schedule_step_report(
4313        report: &mut GpuSolverProductionMaxSatScheduleReport,
4314        step_report: GpuSolverProductionMaxSatReport,
4315    ) -> Result<()> {
4316        report.optimum_score = report.optimum_score.max(step_report.optimum_score);
4317        checked_solver_report_counter_add!(
4318            report,
4319            candidates_checked,
4320            step_report.candidates_checked
4321        );
4322        checked_solver_report_counter_add!(
4323            report,
4324            satisfiable_candidates,
4325            step_report.satisfiable_candidates
4326        );
4327        checked_solver_report_counter_add!(
4328            report,
4329            unsat_candidates_pruned,
4330            step_report.unsat_candidates_pruned
4331        );
4332        checked_solver_report_counter_add!(
4333            report,
4334            gpu_cdcl_candidate_encodes,
4335            step_report.gpu_cdcl_candidate_encodes
4336        );
4337        checked_solver_report_counter_add!(
4338            report,
4339            gpu_cdcl_candidate_solves,
4340            step_report.gpu_cdcl_candidate_solves
4341        );
4342        checked_solver_report_counter_add!(
4343            report,
4344            frontier_upper_bound_certificates,
4345            step_report.frontier_upper_bound_certificates
4346        );
4347        Ok(())
4348    }
4349
4350    fn solve_maxsat_schedule_jobs(
4351        &mut self,
4352        workspace: &mut GpuCdclWorkspace,
4353        jobs: &[GpuSolverProductionMaxSatScheduleJob<'_>],
4354    ) -> Result<GpuSolverProductionMaxSatScheduleReport> {
4355        self.require_workspace_on_adapter_provider(workspace)?;
4356        Self::require_maxsat_schedule_jobs(jobs)?;
4357        self.require_maxsat_schedule_job_artifacts(workspace, jobs)?;
4358
4359        let mut report = GpuSolverProductionMaxSatScheduleReport::default();
4360        for job in jobs {
4361            checked_solver_trace_counter_inc!(self, gpu_maxsat_scheduler_jobs);
4362            checked_solver_report_counter_inc!(report, jobs);
4363
4364            match job {
4365                GpuSolverProductionMaxSatScheduleJob::CandidateSet { candidates } => {
4366                    checked_solver_trace_counter_inc!(
4367                        self,
4368                        gpu_maxsat_scheduler_candidate_set_jobs
4369                    );
4370                    checked_solver_report_counter_inc!(report, candidate_set_jobs);
4371                    let step_report = self.solve_weighted_maxsat_candidates(candidates, 0)?;
4372                    Self::add_maxsat_schedule_step_report(&mut report, step_report)?;
4373                }
4374                GpuSolverProductionMaxSatScheduleJob::Search { candidates } => {
4375                    checked_solver_trace_counter_inc!(self, gpu_maxsat_scheduler_search_jobs);
4376                    checked_solver_report_counter_inc!(report, search_jobs);
4377                    let step_report =
4378                        self.solve_weighted_maxsat_search_candidates(workspace, candidates, 0)?;
4379                    Self::add_maxsat_schedule_step_report(&mut report, step_report)?;
4380                }
4381                GpuSolverProductionMaxSatScheduleJob::EncodedSearch {
4382                    weighted,
4383                    branch_var_limit,
4384                    selections,
4385                } => {
4386                    checked_solver_trace_counter_inc!(
4387                        self,
4388                        gpu_maxsat_scheduler_encoded_search_jobs
4389                    );
4390                    checked_solver_report_counter_inc!(report, encoded_search_jobs);
4391                    let encodes_before = self.trace.gpu_maxsat_candidate_encodes;
4392                    let certificates_before =
4393                        self.trace.gpu_maxsat_frontier_upper_bound_certificates;
4394                    let encoded =
4395                        self.encode_weighted_maxsat_search_candidates(weighted, selections)?;
4396                    let frontier_upper_bound_certificates = Self::checked_report_counter_delta(
4397                        self.trace.gpu_maxsat_frontier_upper_bound_certificates,
4398                        certificates_before,
4399                        "gpu_maxsat_frontier_upper_bound_certificates",
4400                    )?;
4401                    let search_candidates: Vec<_> = encoded
4402                        .iter()
4403                        .map(|candidate| GpuSolverProductionMaxSatSearchCandidate {
4404                            score: candidate.score,
4405                            cnf: &candidate.cnf,
4406                            branch_var_limit,
4407                            status: candidate.status,
4408                        })
4409                        .collect();
4410                    let mut step_report = self.solve_weighted_maxsat_search_candidates(
4411                        workspace,
4412                        &search_candidates,
4413                        frontier_upper_bound_certificates,
4414                    )?;
4415                    step_report.gpu_cdcl_candidate_encodes = Self::checked_report_counter_delta(
4416                        self.trace.gpu_maxsat_candidate_encodes,
4417                        encodes_before,
4418                        "gpu_cdcl_candidate_encodes",
4419                    )?;
4420                    Self::add_maxsat_schedule_step_report(&mut report, step_report)?;
4421                }
4422                GpuSolverProductionMaxSatScheduleJob::Unknown { reason } => {
4423                    if reason.trim().is_empty() {
4424                        return Err(XlogError::UnsupportedEpistemicConstruct {
4425                            construct: "GPU solver production MaxSAT scheduler".to_string(),
4426                            context: "UNKNOWN scheduler status requires a diagnostic reason"
4427                                .to_string(),
4428                        });
4429                    }
4430                    checked_solver_trace_counter_inc!(
4431                        self,
4432                        gpu_maxsat_scheduler_unknown_status_jobs
4433                    );
4434                    checked_solver_report_counter_inc!(report, unknown_jobs);
4435                }
4436                GpuSolverProductionMaxSatScheduleJob::Timeout { budget_micros } => {
4437                    if *budget_micros == 0 {
4438                        return Err(XlogError::UnsupportedEpistemicConstruct {
4439                            construct: "GPU solver production MaxSAT scheduler".to_string(),
4440                            context: "TIMEOUT scheduler status requires a nonzero budget"
4441                                .to_string(),
4442                        });
4443                    }
4444                    checked_solver_trace_counter_inc!(
4445                        self,
4446                        gpu_maxsat_scheduler_timeout_status_jobs
4447                    );
4448                    checked_solver_report_counter_inc!(report, timeout_jobs);
4449                }
4450            }
4451        }
4452
4453        Ok(report)
4454    }
4455
4456    fn require_maxsat_schedule_jobs(
4457        jobs: &[GpuSolverProductionMaxSatScheduleJob<'_>],
4458    ) -> Result<()> {
4459        if jobs.is_empty() {
4460            return Err(XlogError::UnsupportedEpistemicConstruct {
4461                construct: "GPU solver production MaxSAT scheduler".to_string(),
4462                context: "accepted MaxSAT scheduler requires at least one GPU job".to_string(),
4463            });
4464        }
4465
4466        for job in jobs {
4467            match job {
4468                GpuSolverProductionMaxSatScheduleJob::CandidateSet { candidates } => {
4469                    Self::require_weighted_maxsat_candidates(candidates)?;
4470                }
4471                GpuSolverProductionMaxSatScheduleJob::Search { candidates } => {
4472                    Self::require_weighted_maxsat_search_candidates(candidates)?;
4473                }
4474                GpuSolverProductionMaxSatScheduleJob::EncodedSearch {
4475                    weighted,
4476                    selections,
4477                    ..
4478                } => {
4479                    Self::require_weighted_maxsat_encoding_inputs(weighted, selections)?;
4480                }
4481                GpuSolverProductionMaxSatScheduleJob::Unknown { reason } => {
4482                    if reason.trim().is_empty() {
4483                        return Err(XlogError::UnsupportedEpistemicConstruct {
4484                            construct: "GPU solver production MaxSAT scheduler".to_string(),
4485                            context: "UNKNOWN scheduler status requires a diagnostic reason"
4486                                .to_string(),
4487                        });
4488                    }
4489                }
4490                GpuSolverProductionMaxSatScheduleJob::Timeout { budget_micros } => {
4491                    if *budget_micros == 0 {
4492                        return Err(XlogError::UnsupportedEpistemicConstruct {
4493                            construct: "GPU solver production MaxSAT scheduler".to_string(),
4494                            context: "TIMEOUT scheduler status requires a nonzero budget"
4495                                .to_string(),
4496                        });
4497                    }
4498                }
4499            }
4500        }
4501        Ok(())
4502    }
4503
4504    fn require_maxsat_schedule_job_artifacts(
4505        &self,
4506        workspace: &GpuCdclWorkspace,
4507        jobs: &[GpuSolverProductionMaxSatScheduleJob<'_>],
4508    ) -> Result<()> {
4509        for job in jobs {
4510            match job {
4511                GpuSolverProductionMaxSatScheduleJob::CandidateSet { candidates } => {
4512                    self.require_weighted_maxsat_candidate_artifacts(candidates)?;
4513                }
4514                GpuSolverProductionMaxSatScheduleJob::Search { candidates } => {
4515                    self.require_weighted_maxsat_search_candidates_and_artifacts(
4516                        workspace, candidates,
4517                    )?;
4518                }
4519                GpuSolverProductionMaxSatScheduleJob::EncodedSearch {
4520                    weighted,
4521                    branch_var_limit,
4522                    ..
4523                } => {
4524                    self.require_workspace_capacity_for_weighted_maxsat_encoding(
4525                        workspace,
4526                        weighted,
4527                        "GPU solver production MaxSAT scheduler",
4528                    )?;
4529                    self.require_branch_var_limit_on_adapter_provider(
4530                        branch_var_limit,
4531                        "GPU solver production MaxSAT scheduler",
4532                    )?;
4533                }
4534                GpuSolverProductionMaxSatScheduleJob::Unknown { .. }
4535                | GpuSolverProductionMaxSatScheduleJob::Timeout { .. } => {}
4536            }
4537        }
4538        Ok(())
4539    }
4540
4541    /// Execute a heterogeneous MaxSAT schedule once per accepted GPU evidence record.
4542    ///
4543    /// The scheduler is a thin production-path adapter: it validates accepted
4544    /// epistemic GPU execution up front, then dispatches candidate-set,
4545    /// search-pruning, and weighted encoded-search jobs through the existing GPU
4546    /// CNF/CDCL helpers. UNKNOWN and TIMEOUT jobs are status propagation records;
4547    /// they never fall back to CPU assignment or MaxSAT enumeration.
4548    pub fn solve_maxsat_schedule_with_gpu_execution_results(
4549        &mut self,
4550        provider: &CudaKernelProvider,
4551        results: &[&EpistemicGpuExecutionResult],
4552        workspace: &mut GpuCdclWorkspace,
4553        jobs: &[GpuSolverProductionMaxSatScheduleJob<'_>],
4554    ) -> Result<GpuSolverProductionMaxSatScheduleReport> {
4555        self.with_trace_rollback(|this| {
4556            this.solve_maxsat_schedule_with_gpu_execution_results_impl(
4557                provider, results, workspace, jobs,
4558            )
4559        })
4560    }
4561
4562    fn solve_maxsat_schedule_with_gpu_execution_results_impl(
4563        &mut self,
4564        provider: &CudaKernelProvider,
4565        results: &[&EpistemicGpuExecutionResult],
4566        workspace: &mut GpuCdclWorkspace,
4567        jobs: &[GpuSolverProductionMaxSatScheduleJob<'_>],
4568    ) -> Result<GpuSolverProductionMaxSatScheduleReport> {
4569        if results.is_empty() {
4570            return Err(XlogError::UnsupportedEpistemicConstruct {
4571                construct: "GPU solver production MaxSAT scheduler".to_string(),
4572                context: "MaxSAT scheduler requires at least one accepted GPU result".to_string(),
4573            });
4574        }
4575        Self::require_maxsat_schedule_jobs(jobs)?;
4576        self.require_workspace_on_adapter_provider(workspace)?;
4577        self.require_maxsat_schedule_job_artifacts(workspace, jobs)?;
4578        let states = self.require_accepted_gpu_solver_states(provider, results)?;
4579
4580        let mut report = GpuSolverProductionMaxSatScheduleReport::default();
4581        for state in &states {
4582            let events_before = self.trace.accepted_path_event_snapshot()?;
4583            let step_report = self.solve_maxsat_schedule_jobs(workspace, jobs)?;
4584            checked_solver_report_counter_inc!(report, candidate_evidence_records);
4585            checked_solver_report_counter_add!(report, jobs, step_report.jobs);
4586            checked_solver_report_counter_add!(
4587                report,
4588                candidate_set_jobs,
4589                step_report.candidate_set_jobs
4590            );
4591            checked_solver_report_counter_add!(report, search_jobs, step_report.search_jobs);
4592            checked_solver_report_counter_add!(
4593                report,
4594                encoded_search_jobs,
4595                step_report.encoded_search_jobs
4596            );
4597            checked_solver_report_counter_add!(report, unknown_jobs, step_report.unknown_jobs);
4598            checked_solver_report_counter_add!(report, timeout_jobs, step_report.timeout_jobs);
4599            Self::add_maxsat_schedule_step_report(
4600                &mut report,
4601                GpuSolverProductionMaxSatReport {
4602                    optimum_score: step_report.optimum_score,
4603                    candidates_checked: step_report.candidates_checked,
4604                    satisfiable_candidates: step_report.satisfiable_candidates,
4605                    unsat_candidates_pruned: step_report.unsat_candidates_pruned,
4606                    gpu_cdcl_candidate_encodes: step_report.gpu_cdcl_candidate_encodes,
4607                    gpu_cdcl_candidate_solves: step_report.gpu_cdcl_candidate_solves,
4608                    frontier_upper_bound_certificates: step_report
4609                        .frontier_upper_bound_certificates,
4610                    ..GpuSolverProductionMaxSatReport::default()
4611                },
4612            )?;
4613            self.record_accepted_gpu_solver_production_path_events_since(events_before, state)?;
4614            self.record_accepted_gpu_candidate_state(state)?;
4615        }
4616
4617        Ok(report)
4618    }
4619
4620    /// Execute a heterogeneous MaxSAT schedule once per accepted split-batch component.
4621    ///
4622    /// This preserves the scheduler's existing GPU CNF/CDCL dispatch behavior while requiring
4623    /// the typed `Gpu`/`RejectUnsupported` policy plus observed GPU dispatch and candidate
4624    /// accounting, scoped transfer accounting, and CUDA-event timing before any scheduled job
4625    /// runs.
4626    pub fn solve_maxsat_schedule_with_gpu_batch_execution_result(
4627        &mut self,
4628        provider: &CudaKernelProvider,
4629        evidence: GpuSolverProductionBatchExecutionEvidence<'_>,
4630        workspace: &mut GpuCdclWorkspace,
4631        jobs: &[GpuSolverProductionMaxSatScheduleJob<'_>],
4632    ) -> Result<GpuSolverProductionMaxSatScheduleReport> {
4633        self.with_trace_rollback(|this| {
4634            Self::require_maxsat_schedule_jobs(jobs)?;
4635            this.require_workspace_on_adapter_provider(workspace)?;
4636            this.require_maxsat_schedule_job_artifacts(workspace, jobs)?;
4637            let results =
4638                this.accepted_solver_results_from_gpu_batch_execution_evidence(provider, evidence)?;
4639            let report = this.solve_maxsat_schedule_with_gpu_execution_results(
4640                provider, &results, workspace, jobs,
4641            )?;
4642            this.record_accepted_gpu_batch_candidate_evidence(results.len())?;
4643            Ok(report)
4644        })
4645    }
4646
4647    fn solve_portfolio_jobs(
4648        &mut self,
4649        jobs: &[GpuSolverProductionPortfolioJob<'_>],
4650    ) -> Result<GpuSolverProductionPortfolioReport> {
4651        self.require_portfolio_jobs_and_artifacts(jobs)?;
4652
4653        let mut report = GpuSolverProductionPortfolioReport::default();
4654        for job in jobs {
4655            checked_solver_trace_counter_inc!(self, gpu_portfolio_jobs);
4656            checked_solver_report_counter_inc!(report, jobs);
4657
4658            match job {
4659                GpuSolverProductionPortfolioJob::Sat {
4660                    cnf,
4661                    branch_var_limit,
4662                } => {
4663                    let _assignment = self
4664                        .solver
4665                        .solve_expect_sat_with_branch_limit(cnf, branch_var_limit)?;
4666                    checked_solver_trace_counter_inc!(self, gpu_cdcl_sat_solves);
4667                    checked_solver_trace_counter_inc!(self, gpu_portfolio_sat_jobs);
4668                    checked_solver_report_counter_inc!(report, sat_jobs);
4669                }
4670                GpuSolverProductionPortfolioJob::MaxSat { candidates } => {
4671                    let maxsat = self.solve_weighted_maxsat_candidates(candidates, 0)?;
4672                    checked_solver_trace_counter_inc!(self, gpu_portfolio_maxsat_jobs);
4673                    checked_solver_report_counter_inc!(report, maxsat_jobs);
4674                    Self::add_portfolio_maxsat_report(&mut report, maxsat)?;
4675                }
4676                GpuSolverProductionPortfolioJob::EncodedMaxSat {
4677                    weighted,
4678                    branch_var_limit,
4679                    selections,
4680                } => {
4681                    let encodes_before = self.trace.gpu_maxsat_candidate_encodes;
4682                    let certificates_before =
4683                        self.trace.gpu_maxsat_frontier_upper_bound_certificates;
4684                    let encoded =
4685                        self.encode_weighted_maxsat_search_candidates(weighted, selections)?;
4686                    let frontier_upper_bound_certificates = Self::checked_report_counter_delta(
4687                        self.trace.gpu_maxsat_frontier_upper_bound_certificates,
4688                        certificates_before,
4689                        "gpu_maxsat_frontier_upper_bound_certificates",
4690                    )?;
4691                    let search_candidates: Vec<_> = encoded
4692                        .iter()
4693                        .map(|candidate| GpuSolverProductionMaxSatSearchCandidate {
4694                            score: candidate.score,
4695                            cnf: &candidate.cnf,
4696                            branch_var_limit,
4697                            status: candidate.status,
4698                        })
4699                        .collect();
4700                    let mut workspace = self.new_workspace(
4701                        weighted.num_vars,
4702                        Self::checked_workspace_clause_cap(weighted)?,
4703                    )?;
4704                    let mut maxsat = self.solve_weighted_maxsat_search_candidates(
4705                        &mut workspace,
4706                        &search_candidates,
4707                        frontier_upper_bound_certificates,
4708                    )?;
4709                    maxsat.gpu_cdcl_candidate_encodes = Self::checked_report_counter_delta(
4710                        self.trace.gpu_maxsat_candidate_encodes,
4711                        encodes_before,
4712                        "gpu_cdcl_candidate_encodes",
4713                    )?;
4714                    checked_solver_trace_counter_inc!(self, gpu_portfolio_maxsat_jobs);
4715                    checked_solver_report_counter_inc!(report, maxsat_jobs);
4716                    Self::add_portfolio_maxsat_report(&mut report, maxsat)?;
4717                }
4718                GpuSolverProductionPortfolioJob::Unknown { .. } => {
4719                    checked_solver_trace_counter_inc!(self, gpu_portfolio_unknown_status_jobs);
4720                    checked_solver_report_counter_inc!(report, unknown_jobs);
4721                }
4722                GpuSolverProductionPortfolioJob::Timeout { .. } => {
4723                    checked_solver_trace_counter_inc!(self, gpu_portfolio_timeout_status_jobs);
4724                    checked_solver_report_counter_inc!(report, timeout_jobs);
4725                }
4726            }
4727        }
4728
4729        Ok(report)
4730    }
4731
4732    fn require_portfolio_jobs(jobs: &[GpuSolverProductionPortfolioJob<'_>]) -> Result<()> {
4733        if jobs.is_empty() {
4734            return Err(XlogError::UnsupportedEpistemicConstruct {
4735                construct: "GPU solver production portfolio".to_string(),
4736                context: "accepted solver portfolio requires at least one GPU job".to_string(),
4737            });
4738        }
4739
4740        for job in jobs {
4741            match job {
4742                GpuSolverProductionPortfolioJob::Sat { .. } => {}
4743                GpuSolverProductionPortfolioJob::MaxSat { candidates } => {
4744                    Self::require_weighted_maxsat_candidates(candidates)?;
4745                }
4746                GpuSolverProductionPortfolioJob::EncodedMaxSat {
4747                    weighted,
4748                    selections,
4749                    ..
4750                } => {
4751                    Self::require_weighted_maxsat_encoding_inputs(weighted, selections)?;
4752                    Self::checked_workspace_clause_cap(weighted)?;
4753                }
4754                GpuSolverProductionPortfolioJob::Unknown { reason } => {
4755                    if reason.trim().is_empty() {
4756                        return Err(XlogError::UnsupportedEpistemicConstruct {
4757                            construct: "GPU solver production portfolio".to_string(),
4758                            context: "UNKNOWN portfolio status requires a diagnostic reason"
4759                                .to_string(),
4760                        });
4761                    }
4762                }
4763                GpuSolverProductionPortfolioJob::Timeout { budget_micros } => {
4764                    if *budget_micros == 0 {
4765                        return Err(XlogError::UnsupportedEpistemicConstruct {
4766                            construct: "GPU solver production portfolio".to_string(),
4767                            context: "TIMEOUT portfolio status requires a nonzero budget"
4768                                .to_string(),
4769                        });
4770                    }
4771                }
4772            }
4773        }
4774        Ok(())
4775    }
4776
4777    fn require_portfolio_job_artifacts(
4778        &self,
4779        jobs: &[GpuSolverProductionPortfolioJob<'_>],
4780    ) -> Result<()> {
4781        for job in jobs {
4782            match job {
4783                GpuSolverProductionPortfolioJob::Sat {
4784                    cnf,
4785                    branch_var_limit,
4786                } => {
4787                    self.require_solver_artifact_on_adapter_provider(
4788                        cnf,
4789                        branch_var_limit,
4790                        "GPU solver production portfolio",
4791                    )?;
4792                }
4793                GpuSolverProductionPortfolioJob::MaxSat { candidates } => {
4794                    self.require_weighted_maxsat_candidate_artifacts(candidates)?;
4795                }
4796                GpuSolverProductionPortfolioJob::EncodedMaxSat {
4797                    branch_var_limit, ..
4798                } => {
4799                    self.require_branch_var_limit_on_adapter_provider(
4800                        branch_var_limit,
4801                        "GPU solver production portfolio",
4802                    )?;
4803                }
4804                GpuSolverProductionPortfolioJob::Unknown { .. }
4805                | GpuSolverProductionPortfolioJob::Timeout { .. } => {}
4806            }
4807        }
4808        Ok(())
4809    }
4810
4811    fn require_portfolio_jobs_and_artifacts(
4812        &self,
4813        jobs: &[GpuSolverProductionPortfolioJob<'_>],
4814    ) -> Result<()> {
4815        Self::require_portfolio_jobs(jobs)?;
4816        self.require_portfolio_job_artifacts(jobs)
4817    }
4818
4819    fn add_portfolio_maxsat_report(
4820        report: &mut GpuSolverProductionPortfolioReport,
4821        maxsat: GpuSolverProductionMaxSatReport,
4822    ) -> Result<()> {
4823        checked_solver_report_counter_add!(report, maxsat_optimum_scores, maxsat.optimum_score);
4824        checked_solver_report_counter_add!(
4825            report,
4826            maxsat_candidates_checked,
4827            maxsat.candidates_checked
4828        );
4829        checked_solver_report_counter_add!(
4830            report,
4831            maxsat_satisfiable_candidates,
4832            maxsat.satisfiable_candidates
4833        );
4834        checked_solver_report_counter_add!(
4835            report,
4836            maxsat_unsat_candidates_pruned,
4837            maxsat.unsat_candidates_pruned
4838        );
4839        checked_solver_report_counter_add!(
4840            report,
4841            maxsat_gpu_cdcl_candidate_encodes,
4842            maxsat.gpu_cdcl_candidate_encodes
4843        );
4844        checked_solver_report_counter_add!(
4845            report,
4846            maxsat_gpu_cdcl_candidate_solves,
4847            maxsat.gpu_cdcl_candidate_solves
4848        );
4849        checked_solver_report_counter_add!(
4850            report,
4851            maxsat_frontier_upper_bound_certificates,
4852            maxsat.frontier_upper_bound_certificates
4853        );
4854        Ok(())
4855    }
4856
4857    fn add_portfolio_report(
4858        report: &mut GpuSolverProductionPortfolioReport,
4859        step_report: GpuSolverProductionPortfolioReport,
4860    ) -> Result<()> {
4861        checked_solver_report_counter_add!(report, jobs, step_report.jobs);
4862        checked_solver_report_counter_add!(report, sat_jobs, step_report.sat_jobs);
4863        checked_solver_report_counter_add!(report, maxsat_jobs, step_report.maxsat_jobs);
4864        checked_solver_report_counter_add!(report, unknown_jobs, step_report.unknown_jobs);
4865        checked_solver_report_counter_add!(report, timeout_jobs, step_report.timeout_jobs);
4866        checked_solver_report_counter_add!(
4867            report,
4868            maxsat_optimum_scores,
4869            step_report.maxsat_optimum_scores
4870        );
4871        checked_solver_report_counter_add!(
4872            report,
4873            maxsat_candidates_checked,
4874            step_report.maxsat_candidates_checked
4875        );
4876        checked_solver_report_counter_add!(
4877            report,
4878            maxsat_satisfiable_candidates,
4879            step_report.maxsat_satisfiable_candidates
4880        );
4881        checked_solver_report_counter_add!(
4882            report,
4883            maxsat_unsat_candidates_pruned,
4884            step_report.maxsat_unsat_candidates_pruned
4885        );
4886        checked_solver_report_counter_add!(
4887            report,
4888            maxsat_gpu_cdcl_candidate_encodes,
4889            step_report.maxsat_gpu_cdcl_candidate_encodes
4890        );
4891        checked_solver_report_counter_add!(
4892            report,
4893            maxsat_gpu_cdcl_candidate_solves,
4894            step_report.maxsat_gpu_cdcl_candidate_solves
4895        );
4896        checked_solver_report_counter_add!(
4897            report,
4898            maxsat_frontier_upper_bound_certificates,
4899            step_report.maxsat_frontier_upper_bound_certificates
4900        );
4901        Ok(())
4902    }
4903
4904    /// Execute a bounded SAT/MaxSAT/status-aware portfolio after accepted GPU epistemic execution.
4905    ///
4906    /// The portfolio is a production adapter over existing GPU CDCL calls. It records
4907    /// per-job counters and rejects empty portfolios without falling back to the CPU
4908    /// semantic-oracle solver.
4909    pub fn solve_portfolio_with_gpu_execution_result(
4910        &mut self,
4911        provider: &CudaKernelProvider,
4912        result: &EpistemicGpuExecutionResult,
4913        jobs: &[GpuSolverProductionPortfolioJob<'_>],
4914    ) -> Result<GpuSolverProductionPortfolioReport> {
4915        self.with_trace_rollback(|this| {
4916            this.solve_portfolio_with_gpu_execution_result_impl(provider, result, jobs)
4917        })
4918    }
4919
4920    fn solve_portfolio_with_gpu_execution_result_impl(
4921        &mut self,
4922        provider: &CudaKernelProvider,
4923        result: &EpistemicGpuExecutionResult,
4924        jobs: &[GpuSolverProductionPortfolioJob<'_>],
4925    ) -> Result<GpuSolverProductionPortfolioReport> {
4926        self.require_portfolio_jobs_and_artifacts(jobs)?;
4927        let state = self.require_accepted_gpu_solver_evidence(provider, result)?;
4928
4929        let events_before = self.trace.accepted_path_event_snapshot()?;
4930        let mut report = self.solve_portfolio_jobs(jobs)?;
4931        report.candidate_evidence_records = 1;
4932
4933        self.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
4934        self.record_accepted_gpu_candidate_state(&state)?;
4935        Ok(report)
4936    }
4937
4938    /// Execute the same bounded portfolio once per accepted GPU epistemic candidate.
4939    pub fn solve_multi_candidate_portfolio_with_gpu_execution_results(
4940        &mut self,
4941        provider: &CudaKernelProvider,
4942        results: &[&EpistemicGpuExecutionResult],
4943        jobs: &[GpuSolverProductionPortfolioJob<'_>],
4944    ) -> Result<GpuSolverProductionPortfolioReport> {
4945        self.with_trace_rollback(|this| {
4946            this.solve_multi_candidate_portfolio_with_gpu_execution_results_impl(
4947                provider, results, jobs,
4948            )
4949        })
4950    }
4951
4952    fn solve_multi_candidate_portfolio_with_gpu_execution_results_impl(
4953        &mut self,
4954        provider: &CudaKernelProvider,
4955        results: &[&EpistemicGpuExecutionResult],
4956        jobs: &[GpuSolverProductionPortfolioJob<'_>],
4957    ) -> Result<GpuSolverProductionPortfolioReport> {
4958        if results.is_empty() {
4959            return Err(XlogError::UnsupportedEpistemicConstruct {
4960                construct: "GPU solver production portfolio".to_string(),
4961                context: "multi-candidate portfolio requires at least one accepted GPU result"
4962                    .to_string(),
4963            });
4964        }
4965        self.require_portfolio_jobs_and_artifacts(jobs)?;
4966        let states = self.require_accepted_gpu_solver_states(provider, results)?;
4967
4968        let mut report = GpuSolverProductionPortfolioReport::default();
4969        for state in &states {
4970            let events_before = self.trace.accepted_path_event_snapshot()?;
4971            let step_report = self.solve_portfolio_jobs(jobs)?;
4972            checked_solver_report_counter_inc!(report, candidate_evidence_records);
4973            Self::add_portfolio_report(&mut report, step_report)?;
4974            self.record_accepted_gpu_solver_production_path_events_since(events_before, state)?;
4975            self.record_accepted_gpu_candidate_state(state)?;
4976        }
4977
4978        Ok(report)
4979    }
4980
4981    /// Execute a bounded SAT/MaxSAT/status-aware portfolio for accepted split/batch evidence.
4982    ///
4983    /// The batch evidence must prove every split component reused the existing
4984    /// single-plan GPU runtime path before each component is delegated to the
4985    /// existing multi-candidate portfolio adapter.
4986    pub fn solve_portfolio_with_gpu_batch_execution_result(
4987        &mut self,
4988        provider: &CudaKernelProvider,
4989        evidence: GpuSolverProductionBatchExecutionEvidence<'_>,
4990        jobs: &[GpuSolverProductionPortfolioJob<'_>],
4991    ) -> Result<GpuSolverProductionPortfolioReport> {
4992        self.with_trace_rollback(|this| {
4993            this.require_portfolio_jobs_and_artifacts(jobs)?;
4994            let results =
4995                this.accepted_solver_results_from_gpu_batch_execution_evidence(provider, evidence)?;
4996            let report = this.solve_multi_candidate_portfolio_with_gpu_execution_results(
4997                provider, &results, jobs,
4998            )?;
4999            this.record_accepted_gpu_batch_candidate_evidence(results.len())?;
5000            Ok(report)
5001        })
5002    }
5003}
5004
5005fn require_accepted_gpu_solver_states(
5006    provider: &CudaKernelProvider,
5007    results: &[&EpistemicGpuExecutionResult],
5008) -> Result<Vec<GpuSolverAcceptedCandidateState>> {
5009    results
5010        .iter()
5011        .map(|result| require_accepted_gpu_solver_evidence(provider, result))
5012        .collect()
5013}
5014
5015fn require_accepted_gpu_solver_evidence(
5016    provider: &CudaKernelProvider,
5017    result: &EpistemicGpuExecutionResult,
5018) -> Result<GpuSolverAcceptedCandidateState> {
5019    let provider_identity = EpistemicGpuProviderIdentity::from_provider(provider);
5020    if result.provider_identity != provider_identity {
5021        return Err(XlogError::UnsupportedEpistemicConstruct {
5022            construct: "accepted GPU solver candidate evidence".to_string(),
5023            context: format!(
5024                "solver evidence provider mismatch: result device={} provider device={} \
5025                 result_device_ptr={} provider_device_ptr={} result_memory_ptr={} \
5026                 provider_memory_ptr={}",
5027                result.provider_identity.device_ordinal,
5028                provider_identity.device_ordinal,
5029                result.provider_identity.device_ptr,
5030                provider_identity.device_ptr,
5031                result.provider_identity.memory_ptr,
5032                provider_identity.memory_ptr
5033            ),
5034        });
5035    }
5036    // The runtime preflight types currently permit only GPU execution with
5037    // reject-unsupported behavior. Positive runtime certification below is
5038    // the falsifiable eligibility gate.
5039    if result.candidate_generation.literal_count == 0
5040        || result.prepared.preflight.tuple_membership_binding_count == 0
5041    {
5042        return Err(XlogError::UnsupportedEpistemicConstruct {
5043            construct: "accepted GPU solver candidate evidence".to_string(),
5044            context: format!(
5045                "solver evidence requires at least one GPU-validated epistemic literal and \
5046                 tuple-membership binding, got literals={} bindings={}",
5047                result.candidate_generation.literal_count,
5048                result.prepared.preflight.tuple_membership_binding_count
5049            ),
5050        });
5051    }
5052    if result.prepared.preflight.solver_assumption_binding_count == 0 {
5053        return Err(XlogError::UnsupportedEpistemicConstruct {
5054            construct: "accepted GPU solver candidate evidence".to_string(),
5055            context: "solver evidence requires planner-exported solver assumption bindings"
5056                .to_string(),
5057        });
5058    }
5059    if result.prepared.preflight.solver_required_capability_count
5060        < PRODUCTION_SOLVER_REQUIRED_CAPABILITY_COUNT as usize
5061        || result.prepared.preflight.solver_required_status_count
5062            < PRODUCTION_SOLVER_REQUIRED_STATUS_COUNT as usize
5063    {
5064        return Err(XlogError::UnsupportedEpistemicConstruct {
5065            construct: "accepted GPU solver candidate evidence".to_string(),
5066            context: format!(
5067                "solver evidence requires the production capability/status contract, got \
5068                 capabilities={} statuses={}",
5069                result.prepared.preflight.solver_required_capability_count,
5070                result.prepared.preflight.solver_required_status_count
5071            ),
5072        });
5073    }
5074    result.require_runtime_dispatch_certification()?;
5075    result
5076        .model_membership
5077        .require_stable_model_tuple_source()?;
5078    if result.constraint_validation.violated_constraint_relations != 0 {
5079        return Err(XlogError::UnsupportedEpistemicConstruct {
5080            construct: "accepted GPU solver candidate evidence".to_string(),
5081            context: format!(
5082                "solver evidence requires zero reduced constraint violations, got {} across {} \
5083                 checked constraint relations",
5084                result.constraint_validation.violated_constraint_relations,
5085                result.constraint_validation.checked_constraint_relations
5086            ),
5087        });
5088    }
5089    if result.constraint_validation.row_count_device_reads as usize
5090        > result.constraint_validation.checked_constraint_relations
5091    {
5092        return Err(XlogError::UnsupportedEpistemicConstruct {
5093            construct: "accepted GPU solver candidate evidence".to_string(),
5094            context: format!(
5095                "solver evidence constraint metadata reads cannot exceed checked reduced \
5096                 constraint relations, got reads={} checked={}",
5097                result.constraint_validation.row_count_device_reads,
5098                result.constraint_validation.checked_constraint_relations
5099            ),
5100        });
5101    }
5102    require_gpu_kernel_trace(
5103        "candidate generation",
5104        result.candidate_generation.kernel_launches,
5105        result.candidate_generation.host_write_ops,
5106        result.candidate_generation.kernel_timing,
5107    )?;
5108    require_gpu_kernel_trace(
5109        "candidate propagation",
5110        result.propagation.kernel_launches,
5111        result.propagation.host_write_ops,
5112        result.propagation.kernel_timing,
5113    )?;
5114    require_gpu_kernel_trace(
5115        "candidate validation",
5116        result.candidate_validation.kernel_launches,
5117        result.candidate_validation.host_write_ops,
5118        result.candidate_validation.kernel_timing,
5119    )?;
5120    require_gpu_kernel_trace(
5121        "model membership",
5122        result.model_membership.kernel_launches,
5123        result.model_membership.host_write_ops,
5124        result.model_membership.kernel_timing,
5125    )?;
5126    require_gpu_kernel_trace(
5127        "world-view validation",
5128        result.world_view_validation.kernel_launches,
5129        result.world_view_validation.host_write_ops,
5130        result.world_view_validation.kernel_timing,
5131    )?;
5132    require_gpu_kernel_trace(
5133        "accepted-candidate materialization",
5134        result.materialization.kernel_launches,
5135        result.materialization.host_write_ops,
5136        result.materialization.kernel_timing,
5137    )?;
5138    require_gpu_kernel_trace(
5139        "final-result materialization",
5140        result.final_result_materialization.kernel_launches,
5141        result.final_result_materialization.host_write_ops,
5142        result.final_result_materialization.kernel_timing,
5143    )?;
5144    require_gpu_kernel_trace(
5145        "final tuple materialization",
5146        result.final_tuple_materialization.kernel_launches,
5147        result.final_tuple_materialization.host_write_ops,
5148        result.final_tuple_materialization.kernel_timing,
5149    )?;
5150    // The runtime has already captured this via read_device_row_count during
5151    // the bounded final-result transfer; do not re-read it in the solver gate.
5152    let accepted_rows = result.final_result_transfer.final_output_rows;
5153    result
5154        .final_tuple_materialization
5155        .require_row_filter_materialization_evidence(
5156            "accepted GPU solver candidate evidence",
5157            accepted_rows,
5158        )?;
5159    if result.transfer_budget.tracked_dtoh_calls != 0
5160        || result.transfer_budget.tracked_htod_calls != 0
5161        || result.transfer_budget.tracked_data_plane_htod_calls != 0
5162        || result.transfer_budget.per_candidate_host_round_trips != 0
5163    {
5164        return Err(XlogError::UnsupportedEpistemicConstruct {
5165            construct: "accepted GPU solver candidate evidence".to_string(),
5166            context: format!(
5167                "solver evidence requires zero hot-path transfers outside bounded launch \
5168                 metadata, got dtoh_calls={}, htod_calls={}, data_plane_htod_calls={}, \
5169                 launch_metadata_htod_calls={}, per_candidate_round_trips={}",
5170                result.transfer_budget.tracked_dtoh_calls,
5171                result.transfer_budget.tracked_htod_calls,
5172                result.transfer_budget.tracked_data_plane_htod_calls,
5173                result.transfer_budget.tracked_launch_metadata_htod_calls,
5174                result.transfer_budget.per_candidate_host_round_trips
5175            ),
5176        });
5177    }
5178    require_accepted_gpu_solver_semantic_trace(result)?;
5179
5180    if accepted_rows == 0 {
5181        return Err(XlogError::UnsupportedEpistemicConstruct {
5182            construct: "accepted GPU solver candidate evidence".to_string(),
5183            context: "solver evidence requires non-empty accepted GPU final output".to_string(),
5184        });
5185    }
5186    if result.semantic_trace.accepted_candidates == 0 {
5187        return Err(XlogError::UnsupportedEpistemicConstruct {
5188            construct: "accepted GPU solver candidate evidence".to_string(),
5189            context: "solver evidence requires at least one GPU-accepted candidate".to_string(),
5190        });
5191    }
5192
5193    Ok(GpuSolverAcceptedCandidateState::from_validated_result(
5194        result,
5195        accepted_rows,
5196    ))
5197}
5198
5199fn require_accepted_gpu_solver_semantic_trace(result: &EpistemicGpuExecutionResult) -> Result<()> {
5200    let trace = &result.semantic_trace;
5201    let accounted_candidates = trace
5202        .accepted_candidates
5203        .checked_add(trace.rejected_candidates)
5204        .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
5205            construct: "accepted GPU solver candidate evidence".to_string(),
5206            context: format!(
5207                "solver evidence semantic trace candidate accounting overflowed: accepted={} \
5208                 rejected={}",
5209                trace.accepted_candidates, trace.rejected_candidates
5210            ),
5211        })?;
5212    if trace.generated_candidates != result.candidate_generation.generated_candidates
5213        || trace.tested_candidates != result.world_view_validation.candidates_checked
5214        || trace.accepted_candidates != trace.accepted_candidate_indices.len()
5215        || trace.rejected_candidates != trace.rejected_candidate_indices.len()
5216        || trace.accepted_world_views != trace.accepted_candidates
5217        || accounted_candidates != trace.generated_candidates
5218    {
5219        return Err(XlogError::UnsupportedEpistemicConstruct {
5220            construct: "accepted GPU solver candidate evidence".to_string(),
5221            context: format!(
5222                "solver evidence requires a consistent GPU semantic trace, got \
5223                 generated={}, tested={}, expected_generated={}, \
5224                 expected_tested={}, accepted={} accepted_indices={}, accepted_world_views={}, \
5225                 rejected={} rejected_indices={}",
5226                trace.generated_candidates,
5227                trace.tested_candidates,
5228                result.candidate_generation.generated_candidates,
5229                result.world_view_validation.candidates_checked,
5230                trace.accepted_candidates,
5231                trace.accepted_candidate_indices.len(),
5232                trace.accepted_world_views,
5233                trace.rejected_candidates,
5234                trace.rejected_candidate_indices.len()
5235            ),
5236        });
5237    }
5238    Ok(())
5239}
5240
5241fn require_accepted_gpu_solver_batch_evidence<'a>(
5242    provider: &CudaKernelProvider,
5243    batch: &'a EpistemicGpuBatchExecutionResult,
5244) -> Result<Vec<&'a EpistemicGpuExecutionResult>> {
5245    if batch.results.is_empty() {
5246        return Err(XlogError::UnsupportedEpistemicConstruct {
5247            construct: "accepted GPU solver batch evidence".to_string(),
5248            context: "solver batch evidence requires at least one accepted GPU component"
5249                .to_string(),
5250        });
5251    }
5252    let trace = batch.trace;
5253    if trace.component_count != batch.results.len()
5254        || trace.gpu_runtime_component_executions != batch.results.len()
5255        || trace.tracked_dtoh_calls != 0
5256        || trace.tracked_htod_calls != 0
5257        || trace.tracked_data_plane_htod_calls != 0
5258        || trace.per_candidate_host_round_trips != 0
5259        || trace.violated_constraint_relations != 0
5260        || !trace.aggregate_kernel_timing.is_recorded()
5261    {
5262        return Err(XlogError::UnsupportedEpistemicConstruct {
5263            construct: "accepted GPU solver batch evidence".to_string(),
5264            context: format!(
5265                "solver batch evidence requires complete GPU component execution, zero \
5266                 observed hot-path transfers outside bounded launch metadata, and aggregate \
5267                 CUDA-event timing, got components={}/{}, dtoh_calls={}, \
5268                 htod_calls={}, data_plane_htod_calls={}, launch_metadata_htod_calls={}, \
5269                 round_trips={}, constraint_violations={}, aggregate_timing_recorded={}",
5270                trace.gpu_runtime_component_executions,
5271                trace.component_count,
5272                trace.tracked_dtoh_calls,
5273                trace.tracked_htod_calls,
5274                trace.tracked_data_plane_htod_calls,
5275                trace.tracked_launch_metadata_htod_calls,
5276                trace.per_candidate_host_round_trips,
5277                trace.violated_constraint_relations,
5278                trace.aggregate_kernel_timing.is_recorded()
5279            ),
5280        });
5281    }
5282    batch.require_trace_matches_components("accepted GPU solver batch evidence")?;
5283
5284    let mut results = Vec::with_capacity(batch.results.len());
5285    for result in &batch.results {
5286        require_accepted_gpu_solver_evidence(provider, result)?;
5287        results.push(result);
5288    }
5289    Ok(results)
5290}
5291
5292fn require_same_gpu_cnf_for_learned_clause_reuse(source: &GpuCnf, target: &GpuCnf) -> Result<()> {
5293    let same_shape = source.var_cap == target.var_cap
5294        && source.clause_cap == target.clause_cap
5295        && source.lit_cap == target.lit_cap;
5296    let same_buffers = source.num_vars.device_ptr_value() == target.num_vars.device_ptr_value()
5297        && source.num_clauses.device_ptr_value() == target.num_clauses.device_ptr_value()
5298        && source.num_lits.device_ptr_value() == target.num_lits.device_ptr_value()
5299        && source.clause_offsets.device_ptr_value() == target.clause_offsets.device_ptr_value()
5300        && source.literals.device_ptr_value() == target.literals.device_ptr_value();
5301    if !same_shape || !same_buffers {
5302        return Err(XlogError::UnsupportedEpistemicConstruct {
5303            construct: "GPU solver learned-clause reuse".to_string(),
5304            context: "learned-clause import is currently certified only for the same \
5305                 device-resident CNF; distinct candidate CNFs must not reuse imported clauses"
5306                .to_string(),
5307        });
5308    }
5309    Ok(())
5310}
5311
5312fn require_stable_learned_clause_arena(
5313    phase: &'static str,
5314    workspace: &GpuCdclWorkspace,
5315    learned_offsets_ptr: cudarc::driver::sys::CUdeviceptr,
5316    learned_lits_ptr: cudarc::driver::sys::CUdeviceptr,
5317    proof_offsets_ptr: cudarc::driver::sys::CUdeviceptr,
5318    proof_data_ptr: cudarc::driver::sys::CUdeviceptr,
5319    learned_count_ptr: cudarc::driver::sys::CUdeviceptr,
5320) -> Result<()> {
5321    if workspace.learned_offsets.device_ptr_value() != learned_offsets_ptr
5322        || workspace.learned_lits.device_ptr_value() != learned_lits_ptr
5323        || workspace.proof_offsets.device_ptr_value() != proof_offsets_ptr
5324        || workspace.proof_data.device_ptr_value() != proof_data_ptr
5325        || workspace.out_learned_count.device_ptr_value() != learned_count_ptr
5326    {
5327        return Err(XlogError::UnsupportedEpistemicConstruct {
5328            construct: "GPU solver learned-clause reuse".to_string(),
5329            context: format!("learned-clause {phase} must keep the reusable GPU workspace arena"),
5330        });
5331    }
5332    Ok(())
5333}
5334
5335fn require_gpu_kernel_trace(
5336    phase: &'static str,
5337    kernel_launches: u32,
5338    host_write_ops: u32,
5339    kernel_timing: EpistemicGpuKernelTimingTrace,
5340) -> Result<()> {
5341    if kernel_launches == 0 || host_write_ops != 0 || !kernel_timing.is_recorded() {
5342        return Err(XlogError::UnsupportedEpistemicConstruct {
5343            construct: "accepted GPU solver candidate evidence".to_string(),
5344            context: format!(
5345                "solver evidence requires GPU {phase} trace with nonzero launches and \
5346                 zero host writes plus CUDA-event timing, got launches={kernel_launches}, \
5347                 host_writes={host_write_ops}, timing_recorded={}",
5348                kernel_timing.is_recorded()
5349            ),
5350        });
5351    }
5352    Ok(())
5353}
5354
5355#[cfg(test)]
5356mod tests {
5357    use std::sync::Arc;
5358
5359    use xlog_core::MemoryBudget;
5360    use xlog_cuda::{CudaKernelProvider, CudaProviderBuilder};
5361
5362    use super::*;
5363    use crate::{Clause, Literal};
5364
5365    fn try_provider() -> Option<Arc<CudaKernelProvider>> {
5366        let budget = MemoryBudget::with_limit(1024 * 1024 * 1024);
5367        match CudaProviderBuilder::new(0, budget).build() {
5368            Ok(provider) => Some(Arc::new(provider)),
5369            Err(err) => {
5370                eprintln!("Skipping test: failed to create CUDA kernel provider: {err}");
5371                None
5372            }
5373        }
5374    }
5375
5376    fn alloc_u32(
5377        provider: &Arc<CudaKernelProvider>,
5378        value: u32,
5379    ) -> xlog_cuda::memory::TrackedCudaSlice<u32> {
5380        let memory = provider.memory();
5381        let mut slot = memory.alloc::<u32>(1).expect("alloc u32 scalar");
5382        provider
5383            .device()
5384            .inner()
5385            .htod_sync_copy_into(&[value], &mut slot)
5386            .expect("upload u32 scalar");
5387        slot
5388    }
5389
5390    fn assert_raw_gpu_trace_requires_accepted_evidence(trace: GpuSolverProductionTrace) {
5391        assert_eq!(trace.accepted_gpu_candidate_evidence_consumed, 0);
5392        assert_eq!(trace.accepted_gpu_solver_production_path_events, 0);
5393        let error = trace
5394            .require_production_metric_eligibility()
5395            .expect_err("raw GPU solver work must not satisfy production metrics");
5396        let XlogError::UnsupportedEpistemicConstruct { construct, context } = error else {
5397            panic!("expected accepted-evidence metric rejection, got {error:?}");
5398        };
5399        assert_eq!(construct, "GPU solver production metric gate");
5400        assert_eq!(
5401            context,
5402            "production solver metrics require accepted GPU candidate evidence"
5403        );
5404    }
5405
5406    #[test]
5407    fn weighted_maxsat_frontier_completion_fails_closed_before_cpu_expansion() {
5408        let clauses: Vec<_> = (0..18)
5409            .map(|idx| {
5410                let lit = if idx % 2 == 0 {
5411                    Literal::positive(0)
5412                } else {
5413                    Literal::negative(0)
5414                };
5415                Clause::new(vec![lit])
5416            })
5417            .collect();
5418        let weighted = SolveInstance::with_weights(1, clauses, vec![1.0; 18]);
5419        let first_frontier: Vec<_> = (0..9).collect();
5420        let second_frontier: Vec<_> = (9..18).collect();
5421        let selections = [
5422            GpuSolverProductionWeightedMaxSatSelection {
5423                soft_clause_indices: &first_frontier,
5424                status: GpuSolverProductionMaxSatSearchStatus::Unsatisfiable,
5425            },
5426            GpuSolverProductionWeightedMaxSatSelection {
5427                soft_clause_indices: &second_frontier,
5428                status: GpuSolverProductionMaxSatSearchStatus::Unsatisfiable,
5429            },
5430        ];
5431
5432        let result = GpuSolverProductionAdapter::complete_weighted_maxsat_frontier_selections(
5433            &weighted,
5434            weighted.weights.as_ref().expect("weighted MaxSAT weights"),
5435            &selections,
5436        );
5437        let Err(err) = result else {
5438            panic!("frontier completion should reject CPU combinatorial expansion");
5439        };
5440        let message = err.to_string();
5441        assert!(message.contains("frontier completion"));
5442        assert!(message.contains("explicit GPU scheduler selections"));
5443    }
5444
5445    #[test]
5446    fn raw_encoded_maxsat_search_runs_gpu_candidates_but_requires_accepted_evidence() {
5447        let Some(provider) = try_provider() else {
5448            return;
5449        };
5450
5451        let weighted = SolveInstance::with_weights(
5452            1,
5453            vec![
5454                Clause::new(vec![Literal::positive(0)]),
5455                Clause::new(vec![Literal::negative(0)]),
5456            ],
5457            vec![2.0, 1.0],
5458        );
5459        let mut adapter =
5460            GpuSolverProductionAdapter::new(provider.clone(), GpuCdclConfig::default());
5461        let mut workspace = adapter
5462            .new_workspace(weighted.num_vars, weighted.clauses.len() as u32)
5463            .expect("new MaxSAT workspace");
5464        let branch_limit = alloc_u32(&provider, weighted.num_vars);
5465        let contradictory_selection = [0usize, 1usize];
5466        let selections = [GpuSolverProductionWeightedMaxSatSelection {
5467            soft_clause_indices: &contradictory_selection,
5468            status: GpuSolverProductionMaxSatSearchStatus::Unsatisfiable,
5469        }];
5470
5471        let certificates_before = adapter.trace.gpu_maxsat_frontier_upper_bound_certificates;
5472        let encoded = adapter
5473            .encode_weighted_maxsat_search_candidates(&weighted, &selections)
5474            .expect("encode weighted MaxSAT candidates");
5475        let frontier_upper_bound_certificates =
5476            GpuSolverProductionAdapter::checked_report_counter_delta(
5477                adapter.trace.gpu_maxsat_frontier_upper_bound_certificates,
5478                certificates_before,
5479                "gpu_maxsat_frontier_upper_bound_certificates",
5480            )
5481            .expect("frontier certificate delta");
5482        let search_candidates: Vec<_> = encoded
5483            .iter()
5484            .map(|candidate| GpuSolverProductionMaxSatSearchCandidate {
5485                score: candidate.score,
5486                cnf: &candidate.cnf,
5487                branch_var_limit: &branch_limit,
5488                status: candidate.status,
5489            })
5490            .collect();
5491
5492        let report = adapter
5493            .solve_weighted_maxsat_search_candidates(
5494                &mut workspace,
5495                &search_candidates,
5496                frontier_upper_bound_certificates,
5497            )
5498            .expect("GPU weighted MaxSAT search");
5499
5500        assert_eq!(report.candidate_evidence_records, 0);
5501        assert_eq!(report.optimum_score, 2);
5502        assert_eq!(report.candidates_checked, 2);
5503        assert_eq!(report.satisfiable_candidates, 1);
5504        assert_eq!(report.unsat_candidates_pruned, 1);
5505        assert_eq!(report.gpu_cdcl_candidate_solves, 2);
5506        assert_eq!(report.frontier_upper_bound_certificates, 1);
5507
5508        let trace = adapter.trace();
5509        assert_eq!(trace.gpu_maxsat_candidate_encodes, 2);
5510        assert_eq!(trace.gpu_maxsat_frontier_completion_candidate_encodes, 1);
5511        assert_eq!(trace.gpu_maxsat_frontier_upper_bound_certificates, 1);
5512        assert_eq!(trace.gpu_maxsat_candidate_solves, 2);
5513        assert_eq!(trace.gpu_maxsat_unsat_candidate_prunes, 1);
5514        assert_eq!(trace.gpu_cdcl_sat_solves, 1);
5515        assert_eq!(trace.gpu_cdcl_workspace_unsat_solves, 1);
5516        assert_eq!(trace.gpu_maxsat_optima, 1);
5517        assert_raw_gpu_trace_requires_accepted_evidence(trace);
5518    }
5519
5520    #[test]
5521    fn raw_portfolio_dispatches_gpu_paths_but_requires_accepted_evidence() {
5522        let Some(provider) = try_provider() else {
5523            return;
5524        };
5525
5526        let sat_instance = SolveInstance::new(1, vec![Clause::new(vec![Literal::positive(0)])]);
5527        let sat_cnf = GpuCnf::from_host(&sat_instance, &provider).expect("SAT GpuCnf upload");
5528        let weighted = SolveInstance::with_weights(
5529            1,
5530            vec![
5531                Clause::new(vec![Literal::positive(0)]),
5532                Clause::new(vec![Literal::negative(0)]),
5533            ],
5534            vec![2.0, 1.0],
5535        );
5536        let branch_limit = alloc_u32(&provider, weighted.num_vars);
5537        let contradictory_selection = [0usize, 1usize];
5538        let selections = [GpuSolverProductionWeightedMaxSatSelection {
5539            soft_clause_indices: &contradictory_selection,
5540            status: GpuSolverProductionMaxSatSearchStatus::Unsatisfiable,
5541        }];
5542        let jobs = [
5543            GpuSolverProductionPortfolioJob::Sat {
5544                cnf: &sat_cnf,
5545                branch_var_limit: &branch_limit,
5546            },
5547            GpuSolverProductionPortfolioJob::EncodedMaxSat {
5548                weighted: &weighted,
5549                branch_var_limit: &branch_limit,
5550                selections: &selections,
5551            },
5552            GpuSolverProductionPortfolioJob::Unknown {
5553                reason: "bounded portfolio diagnostic",
5554            },
5555            GpuSolverProductionPortfolioJob::Timeout { budget_micros: 1 },
5556        ];
5557        let mut adapter =
5558            GpuSolverProductionAdapter::new(provider.clone(), GpuCdclConfig::default());
5559
5560        let report = adapter
5561            .solve_portfolio_jobs(&jobs)
5562            .expect("GPU production portfolio jobs");
5563
5564        assert_eq!(report.candidate_evidence_records, 0);
5565        assert_eq!(report.jobs, 4);
5566        assert_eq!(report.sat_jobs, 1);
5567        assert_eq!(report.maxsat_jobs, 1);
5568        assert_eq!(report.unknown_jobs, 1);
5569        assert_eq!(report.timeout_jobs, 1);
5570        assert_eq!(report.maxsat_optimum_scores, 2);
5571        assert_eq!(report.maxsat_candidates_checked, 2);
5572        assert_eq!(report.maxsat_satisfiable_candidates, 1);
5573        assert_eq!(report.maxsat_unsat_candidates_pruned, 1);
5574        assert_eq!(report.maxsat_gpu_cdcl_candidate_encodes, 2);
5575        assert_eq!(report.maxsat_gpu_cdcl_candidate_solves, 2);
5576        assert_eq!(report.maxsat_frontier_upper_bound_certificates, 1);
5577
5578        let trace = adapter.trace();
5579        assert_eq!(trace.gpu_portfolio_jobs, 4);
5580        assert_eq!(trace.gpu_portfolio_sat_jobs, 1);
5581        assert_eq!(trace.gpu_portfolio_maxsat_jobs, 1);
5582        assert_eq!(trace.gpu_portfolio_unknown_status_jobs, 1);
5583        assert_eq!(trace.gpu_portfolio_timeout_status_jobs, 1);
5584        assert_eq!(trace.gpu_cdcl_sat_solves, 2);
5585        assert_eq!(trace.gpu_cdcl_workspace_unsat_solves, 1);
5586        assert_eq!(trace.gpu_maxsat_candidate_encodes, 2);
5587        assert_eq!(trace.gpu_maxsat_candidate_solves, 2);
5588        assert_eq!(trace.gpu_maxsat_unsat_candidate_prunes, 1);
5589        assert_eq!(trace.gpu_maxsat_optima, 1);
5590        assert_raw_gpu_trace_requires_accepted_evidence(trace);
5591    }
5592
5593    #[test]
5594    fn raw_learned_clause_reuse_preserves_gpu_arena_but_requires_accepted_evidence() {
5595        let Some(provider) = try_provider() else {
5596            return;
5597        };
5598
5599        let unsat_instance = SolveInstance::new(
5600            1,
5601            vec![
5602                Clause::new(vec![Literal::positive(0)]),
5603                Clause::new(vec![Literal::negative(0)]),
5604            ],
5605        );
5606        let cnf = GpuCnf::from_host(&unsat_instance, &provider).expect("UNSAT GpuCnf upload");
5607        let branch_limit = alloc_u32(&provider, cnf.var_cap as u32);
5608        let mut adapter =
5609            GpuSolverProductionAdapter::new(provider.clone(), GpuCdclConfig::default());
5610        let mut workspace = adapter
5611            .new_workspace(cnf.var_cap, cnf.clause_cap)
5612            .expect("new learned-clause workspace");
5613
5614        let learned_offsets_ptr = workspace.learned_offsets.device_ptr_value();
5615        let learned_lits_ptr = workspace.learned_lits.device_ptr_value();
5616        let proof_offsets_ptr = workspace.proof_offsets.device_ptr_value();
5617        let proof_data_ptr = workspace.proof_data.device_ptr_value();
5618        let learned_count_ptr = workspace.out_learned_count.device_ptr_value();
5619
5620        let report = adapter
5621            .solve_unsat_then_reuse_learned_clauses(
5622                &mut workspace,
5623                &cnf,
5624                &branch_limit,
5625                &cnf,
5626                &branch_limit,
5627            )
5628            .expect("GPU learned-clause reuse");
5629
5630        assert_eq!(report.candidate_evidence_records, 0);
5631        assert_eq!(report.candidates, 2);
5632        assert_eq!(report.unsat_solves, 2);
5633        assert_eq!(report.gpu_learned_clause_arena_publications, 1);
5634        assert_eq!(report.gpu_learned_clause_imports, 1);
5635        assert_eq!(report.gpu_learned_clause_reused_solves, 1);
5636        assert_eq!(
5637            workspace.learned_offsets.device_ptr_value(),
5638            learned_offsets_ptr
5639        );
5640        assert_eq!(workspace.learned_lits.device_ptr_value(), learned_lits_ptr);
5641        assert_eq!(
5642            workspace.proof_offsets.device_ptr_value(),
5643            proof_offsets_ptr
5644        );
5645        assert_eq!(workspace.proof_data.device_ptr_value(), proof_data_ptr);
5646        assert_eq!(
5647            workspace.out_learned_count.device_ptr_value(),
5648            learned_count_ptr
5649        );
5650
5651        let trace = adapter.trace();
5652        assert_eq!(trace.gpu_cdcl_workspace_unsat_solves, 2);
5653        assert_eq!(trace.gpu_learned_clause_arena_publications, 1);
5654        assert_eq!(trace.gpu_learned_count_buffer_publications, 1);
5655        assert_eq!(trace.gpu_learned_clause_imports, 1);
5656        assert_eq!(trace.gpu_learned_clause_reused_solves, 1);
5657        assert_raw_gpu_trace_requires_accepted_evidence(trace);
5658    }
5659}