1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
// SPDX-FileCopyrightText: 2026 Knitli Inc.
//
// SPDX-License-Identifier: LicenseRef-MarqueLicense-1.0
//! Per-call options for `Engine::lint_with_options` and
//! `Engine::fix_with_options` (spec 005).
//!
//! These types are the durable surface for runtime budgets and
//! per-call overrides. Phase 1 lands the type surface with no
//! observable behavior change — the deadline field is plumbed but
//! not yet honored. Phase 2 wires cooperative cancellation against
//! `LintOptions::deadline` / `FixOptions::deadline` per spec §R3.
//!
//! Both structs are `#[non_exhaustive]` so future fields (cancellation
//! tokens, memory budgets, per-rule deadlines) can land without a
//! semver-breaking change. From outside the engine crate, construct
//! via `Default::default()` + public field assignment:
//!
//! Use `marque_engine::Instant` (a `web_time` re-export) rather than
//! `std::time::Instant` so the example works on every supported
//! target. On native the two are the same type (literal `pub use`),
//! so this is also drop-in for native-only callers; on
//! `wasm32-unknown-unknown`, `std::time::Instant::now()` panics with
//! "time not implemented on this platform" while `web_time` polyfills
//! via `Performance.now()`.
//!
//! ```
//! use marque_engine::{Instant, LintOptions, FixOptions};
//! use std::time::Duration;
//!
//! let mut lint = LintOptions::default();
//! lint.deadline = Some(Instant::now() + Duration::from_secs(1));
//!
//! let mut fix = FixOptions::default();
//! fix.deadline = Some(Instant::now() + Duration::from_secs(1));
//! fix.threshold_override = Some(0.85);
//! ```
//!
//! In-crate code (engine internals, this crate's tests) may still use
//! struct-update syntax — `#[non_exhaustive]` only restricts
//! construction across crate boundaries.
// `web_time::Instant` is `std::time::Instant` on native targets and a
// Performance.now() polyfill on wasm32-unknown-unknown. Identical type
// on native (literal `pub use` re-export), so this is source-compatible
// with any caller that previously constructed an `Instant` from
// `std::time`.
use Instant;
/// Per-call options for [`Engine::lint_with_options`].
///
/// **Phase 1 status (current build):** the type surface ships, but
/// `Engine::lint_with_options` IGNORES `deadline`. The pass always
/// runs to completion, returns `truncated: false`, and leaves
/// `candidates_processed` / `candidates_total` at `0`. The semantics
/// below describe the *Phase 2* behavior that lands in tasks
/// T007–T009; consult the changelog (Appendix C in the security
/// whitepaper) before relying on deadline behavior in production.
///
/// `deadline` is an absolute wall-clock instant after which the
/// engine MUST abort cooperatively. Spec §R1, §R3:
///
/// - `None` (default) — no budget; lint runs to completion.
/// - `Some(d)` where `d <= Instant::now()` — pre-pass abort returns
/// immediately with `LintResult { truncated: true,
/// candidates_processed: 0, candidates_total: 0, diagnostics:
/// vec![] }`.
/// - `Some(d)` where `d > Instant::now()` — engine checks the deadline
/// at each candidate boundary; on expiry the loop breaks and
/// `LintResult.truncated` is set to `true` with partial counts.
///
/// The choice of `Instant` over `Duration` is deliberate: callers
/// stamp the deadline once at the boundary they care about
/// (request arrival, document permit acquisition for batch) and
/// the engine carries no implicit clock. This makes the budget
/// composable across `BatchEngine` permit waits and HTTP middleware.
///
/// [`Engine::lint_with_options`]: crate::Engine::lint_with_options
/// Per-call options for [`Engine::fix_with_options`].
///
/// **Phase 1 status (current build):** `Engine::fix_with_options`
/// IGNORES `deadline` (the field is plumbed but not honored), so
/// `EngineError::DeadlineExceeded` cannot be observed yet. The
/// `threshold_override` field IS active from Phase 1: invalid values
/// produce `EngineError::InvalidThreshold` immediately. Deadline
/// enforcement and the asymmetric `Err(DeadlineExceeded)` response
/// described below land in Phase 2 (tasks T010–T012).
///
/// Carries both the deadline (spec §R3) and the per-call confidence
/// threshold override that previously lived on
/// [`Engine::fix_with_threshold`]. The two are combined here so
/// future per-call concerns (per-rule overrides, dry-run-without-mode
/// flag) can join without further signature churn.
///
/// `deadline` semantics: same as [`LintOptions::deadline`], but the
/// engine returns `Err(EngineError::DeadlineExceeded { partial_lint })`
/// rather than a partial `FixResult`. Spec §R4 (asymmetric response):
/// a partial `FixResult` would commit half a fix to the audit stream,
/// which violates Constitution V Principle V (audit-record integrity).
///
/// `threshold_override`:
/// - `None` (default) — falls back to `Config::confidence_threshold`.
/// - `Some(value)` — replaces the config threshold for this call only;
/// validated against `[0.0, 1.0]`. Out-of-range / NaN values produce
/// `EngineError::InvalidThreshold` at the start of the call.
///
/// [`Engine::fix_with_options`]: crate::Engine::fix_with_options
/// [`Engine::fix_with_threshold`]: crate::Engine::fix_with_threshold
/// [`LintOptions::deadline`]: crate::LintOptions::deadline