Expand description
Thin pkix_lint::Lint adapter over pkix_zlint_bridge::ZlintBridge.
pkix-policy-zlint exposes each of zlint’s per-check verdicts as a
workspace pkix_lint::Lint implementation, so callers can mix zlint
findings into a pkix_lint::LintRunner alongside the workspace’s
own RFC-conformance and -cabf reference lints, without any awareness
that the verdicts come from a subprocess.
Per AGENTS.md non-negotiable #5 (three-mode policy-class model), this
is the principled path for predicate-comprehensive CA/B Forum
coverage. zlint stays the source of truth for the checks it covers;
the workspace contributes the framework, not the policy. The hand-
authored pkix-lint-cabf reference set covers a small curated
subset of marquee BR predicates; this crate covers the whole catalog
(~400 lints at the time of writing).
§Usage
use std::sync::Arc;
use pkix_lint::LintRunner;
use pkix_zlint_bridge::{BridgeConfig, ZlintBridge};
use pkix_policy_zlint::all_lints;
// Construct one bridge per process. The bridge owns the per-cert
// verdict cache that amortises subprocess cost across all ~400
// wrapped lints.
let bridge = Arc::new(ZlintBridge::new(BridgeConfig::default())?);
// Enumerate zlint's catalog once and wrap each check as a Lint.
let lints = all_lints(bridge)?;
// Hand to the workspace LintRunner like any other Lint set.
let runner = LintRunner::new(lints);§Lint identity is leaked at adapter-construction time
pkix_lint::Lint::id and pkix_lint::Lint::citation return
&'static str. zlint’s catalog is enumerated at runtime, so the
metadata strings (check_id, citation, description) are
Strings owned by pkix_zlint_bridge::ZlintLintInfo. To satisfy
the &'static str contract, all_lints leaks each ZlintLintInfo
once at construction time via Box::leak, and the resulting
ZlintLint holds a &'static ZlintLintInfo.
The leak is bounded and intentional: ~400 leaks of small
ZlintLintInfo records (~150 bytes each, dominated by the catalog’s
description string) at startup, never growing. This trades a small
one-time program-lifetime allocation for &'static str-shaped
metadata methods on the workspace pkix_lint::Lint trait. The
alternative — broadening the trait to Cow<'static, str> or
Box<str> — was rejected because it would ripple through every
hand-authored Lint impl in pkix-lint, pkix-lint-cabf, and
external crates for the sake of a single adapter.
§Mapping zlint verdicts to pkix_lint::LintResult
pkix_zlint_bridge::Verdict | pkix_lint::LintResult |
|---|---|
NotApplicable | NotApplicable |
Pass | Pass |
Notice | Warn(detail) |
Warn | Warn(detail) |
Error | Error(detail) |
Fatal | Fatal(detail) |
Notice collapses into Warn at the LintResult level because
LintResult does not have a separate notice variant — the workspace
Severity::Notice is metadata on the lint, not on the per-cert
result. Reports that need to differentiate Notice from Warn
consult pkix_lint::Lint::severity (which forwards from
ZlintLintInfo::severity).
Per-cert bridge errors (malformed DER, zlint runtime panic for that
cert) surface as LintResult::Error with the underlying error’s
Display text as detail.
§Limitations
- One
ZlintBridgeper program. The bridge owns the per-certificate verdict cache; if you construct multiple bridges you pay the subprocess cost multiple times. Wrap the bridge inArcand share it across allZlintLintinstances (which is whatall_lintsdoes). SubjectKind::Anyfor every wrapped lint. zlint’s catalog metadata does not surface a stable “applies-to” classification (leaf vs. intermediate vs. anchor); zlint itself internally determines applicability and returns the per-certVerdict::NotApplicablewhen the rule does not fire. The adapter therefore reportsSubjectKind::Anyand lets zlint’s own logic do the filtering.Scope::Certificateonly. zlint operates on a single certificate at a time; the adapter mirrors that. Path-scope lints (chain-wide rules like algorithm consistency across intermediates) live inpkix-lintandpkix-lint-cabf, not here.- Per-check
parameters()empty. zlint exposes no externally-configurable parameters per check; the adapter does not invent a parameter surface that zlint does not have. title()anddescription()use the leakedZlintLintInfo’s fields verbatim. They are not localised, paraphrased, or reformatted — what zlint says is what callers see.