# marque-engine
Pipeline orchestration for Marque — wires the scanner, parser, and rule sets into a configured, ready-to-run engine.
`marque-engine` is the middle of the pipeline. It owns the `Engine` (single
document) and `BatchEngine` (concurrent, async) APIs that the CLI, the WASM
build, and the server all sit on top of.
## Role in Marque
```
marque-config ─┐
marque-core ───┼─→ marque-engine ─→ LintResult / FixResult + audit log
marque-rules ──┘
```
The engine drives the scanner, parses each candidate, evaluates every
registered rule with a `RuleContext`, and — for `fix` mode — promotes
`FixProposal`s to `AppliedFix` records when their confidence meets the
configured threshold.
## Usage
Single-document lint:
```rust
use marque_capco::capco_rules;
use marque_config::Config;
use marque_engine::Engine;
let engine = Engine::new(Config::default(), vec![Box::new(capco_rules())]);
let result = engine.lint(b"(S) example text");
println!("errors: {}, warnings: {}", result.error_count(), result.warn_count());
```
Apply fixes with audit records:
```rust
use marque_engine::{Engine, FixMode};
# use marque_capco::capco_rules;
# use marque_config::Config;
# let engine = Engine::new(Config::default(), vec![Box::new(capco_rules())]);
let result = engine.fix_with_threshold(b"(S) example", FixMode::Apply, None)?;
for applied in &result.applied {
// serialize `applied` to the NDJSON audit stream
}
# Ok::<(), marque_engine::InvalidThreshold>(())
```
## Behavior
- **Confidence gate.** Only `FixProposal`s whose `confidence` is at or above
the configured threshold are promoted. Proposals below the gate are
surfaced as suggestions in `LintResult`, never written.
- **Reverse-span application.** Fixes are sorted and applied from the end of
the document to the start, so earlier spans remain valid as edits land.
Overlapping fixes are rejected per the C-1 invariant.
- **Page context.** The engine builds a `marque_ism::PageContext` from
preceding portion markings and hands an `Arc<PageContext>` to banner/CAB
rules through `RuleContext`. The accumulator resets at scanner-emitted
`MarkingType::PageBreak` candidates (form-feed `\f` and `\n\n\n+`),
*before* attempting to parse the page-break candidate, so a malformed
candidate cannot block the reset.
- **Audit log.** In production code paths, `AppliedFix` is constructed
exclusively by the engine via the `__engine_promote` constructor.
Rule crates must not bypass this. Test code MAY call
`__engine_promote` directly to construct synthetic fixtures for
audit-emission unit tests, scoped per Constitution V Principle V
(`#[cfg(test)]` only, never commingled with engine output, and
test-fixture construction only — not a general-purpose
convenience). See the architectural-invariants section of the
workspace [`CLAUDE.md`](../../CLAUDE.md) and the
[`__engine_promote` doc comment](../rules/src/lib.rs).
- **Clock injection.** `Engine::with_clock` accepts a `Clock` impl; a
`FixedClock` is provided for deterministic test snapshots and for the CLI
`--fixed-timestamp` debug flag.
## Batch processing
With the `batch` feature, `BatchEngine` wraps `Engine` behind `Arc` and uses
`recoco-utils` semaphores for row + byte backpressure. CPU-bound work runs
on `tokio::task::spawn_blocking`. Results stream out in **completion order**,
not submission order — correlate by the echoed `id`.
## Features
| `batch` | on | Enables `BatchEngine` and pulls in `tokio`, `futures`, and `recoco-utils`. Disable for a leaner sync-only build (e.g. WASM). |
A `cache` feature (LMDB-backed incremental cache, opt-in) is planned for v0.2
but is not yet present in `Cargo.toml`.
## WASM compatibility
The sync `Engine` path is WASM-safe; build with `default-features = false` to
drop the async/batch dependencies. `marque-wasm` consumes the engine this way.
## License
Marque License 1.0 (`LicenseRef-MarqueLicense-1.0`). See [LICENSE.md](./LICENSE.md).