Expand description
Vendor-to-playbook auto-resolution (T90).
Bridges the vendor classifier and the playbook resolver,
with multi-vendor precedence, merge rules, and a Manual
fallback that keeps existing manual mode selection working
unchanged.
Vendor-to-playbook auto-resolution (T90).
This module bridges the vendor_classifier
(T89) and the playbooks resolver (T85):
given a VendorClassification, it picks the right codified
Playbook for the runner and
ships a rationale bundle the diagnostic payload can render.
§Resolution rule table
The baseline rule bundle lives in
crates/stygian-charon/data/vendor_playbook_rules/ and is
embedded into the binary at compile time via include_str!.
The four baseline rules and their precedence are:
| Priority | Rule id | Vendors | Resolves to | Merge strategy |
|---|---|---|---|---|
0 | tier2-hostile | DataDome, PerimeterX, Akamai, Kasada, Imperva, ShapeSecurity | tier2-hostile / high_security | StrongestVendor |
10 | tier1-js-cloudflare | Cloudflare, Hcaptcha, Recaptcha, FingerprintCom | tier1-js / content_site | StrongestVendor |
100 | tier1-static | Unknown (require_unknown_vendor = true) | tier1-static / content_site | Single |
1000 | default-manual | (catch-all) | Manual strategy marker | Manual |
Lower priority numbers win. When a rule’s
ResolutionRule::min_confidence
gate passes and at least one of its listed vendors is in the
classifier’s ranked scoreboard, the rule fires.
§Multi-vendor precedence + merge
The classifier emits a ranked scoreboard (top vendor first,
ties broken by VendorId
discriminant order). When the scoreboard lists multiple
vendors that match the fired rule, the
MergeStrategy
determines how the rule consolidates them into a single
decision:
MergeStrategy | Behaviour |
|---|---|
StrongestVendor | Pick the listed vendor with the highest per-rule weight. Used by the two high-priority rules. |
Single | Pick the single listed vendor (ties broken by VendorId discriminant order). Used by tier1-static. |
Manual | Defer to manual mode — return StrategyMarker::Manual. Used by the default-manual sentinel. |
§Low-confidence fallback
When no specific rule fires, the resolver falls through to the
default-manual sentinel and returns
StrategyMarker::Manual. The existing manual mode
selection is not modified by the resolver — the caller keeps
whatever mode it had in effect. This is the
“non-breaking integration with existing manual mode selection”
guarantee from the T90 spec.
§Determinism
The resolver is fully deterministic:
- Rules are sorted by
(priority ASC, id ASC)on construction, so two rules with the same priority are tie-broken by their stableid. - The vendor scoreboard is supplied by the classifier, which
is itself deterministic (T89 —
VendorIddiscriminant order on ties). - The
rationale.contributing_vendorslist is sorted by(score DESC, VendorId ASC)so the JSON form is byte-stable.
§Backward compatibility
The resolver is additive only — no existing public type or
method gains a new field. The new module lives at
crates/stygian-charon/src/vendor_resolver/ and is exposed via
the vendor_resolver re-exports below. No new feature gate is
introduced (per the T90 spec — ## Feature flag).
§Feature flag
The module is default-on. It is compiled into every build
of stygian-charon (which already defaults to the caching
feature). No new feature gate is introduced because the new
surface is purely additive — no existing public type gains a
new field, no existing behaviour changes, and the manual
fallback is non-breaking with the pre-T90 acquisition runner.
§Example
use stygian_charon::types::TargetClass;
use stygian_charon::vendor_classifier::{VendorClassifier, VendorId};
use stygian_charon::vendor_resolver::{StrategyMarker, VendorResolver};
use std::collections::BTreeMap;
let resolver = VendorResolver::with_builtin_defaults();
let classifier = VendorClassifier::with_builtin_defaults();
let cookies = vec!["datadome=abc; Path=/".to_string()];
let mut headers = BTreeMap::new();
headers.insert("x-datadome".to_string(), "protected".to_string());
headers.insert("x-datadome-cid".to_string(), "abc".to_string());
let classification =
classifier.classify(&cookies, &headers, None, "https://example.com/");
let resolution = resolver.resolve(&classification);
assert!(resolution.is_resolved());
match resolution.strategy {
StrategyMarker::Resolved { playbook_id, target_class } => {
assert_eq!(playbook_id, "tier2-hostile");
assert_eq!(target_class, TargetClass::HighSecurity);
}
StrategyMarker::Manual => panic!("DataDome should resolve, not defer"),
}Structs§
- Applied
Rule - One rule that contributed to the resolver’s decision.
- Resolution
Rationale - Full rationale bundle the resolver returns alongside the strategy marker.
- Resolution
Rule - Single codified rule mapping vendor patterns to a playbook.
- Vendor
Resolution - Full vendor-to-playbook resolution result.
- Vendor
Resolver - Vendor-to-playbook resolver.
- Vendor
Rule Match - One vendor entry inside a
ResolutionRule::vendorslist.
Enums§
- Merge
Strategy - How the resolver should combine multiple matched vendors into a single playbook decision.
- Strategy
Marker - What the resolver decided to do with the vendor classification.
- Vendor
Resolver Error - Errors returned by vendor-resolver rule validation and loading.
Traits§
- Playbook
Resolver Ext - Extension trait that surfaces the resolved
Playbookfrom aPlaybookResolverwithout going through the full precedence ladder.
Functions§
- parse_
resolution_ rule - Parse a raw TOML payload into a
ResolutionRule.