Skip to main content

xlog_solve/
production.rs

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