Audit Reproducibility and Compatibility Contract
This page documents two things introduced in v0.27.0 ("Reproducible Route
Audit"): what audit_manifest guarantees, and the compatibility rules every
renkin audit-route adapter (RENKIN-native, AiZynthFinder, Syntheseus,
SynPlanner, and any future one) follows. It's the general, tool-neutral
reference; adapter-specific walkthroughs like
Audit a Real AiZynthFinder Route,
Audit a Syntheseus Route, and
Audit a Real SynPlanner Route link back here
rather than repeating this content.
Audit manifest
Every renkin audit-route --output json report carries an audit_manifest
object recording what was audited and under what conditions:
{
"audit_manifest": {
"renkin_version": "0.35.0",
"report_schema_version": 1,
"source_format": "aizynthfinder",
"source_version": null,
"input_sha256": "sha256:...",
"stock_sha256": null,
"policy": "standard"
}
}
input_sha256hashes the decompressed route-input text actually audited (not the raw on-disk bytes), so a gzip vs. plain copy of identical content hashes identically.stock_sha256hashes the canonicalized--stockset actually loaded (order-independent), and isnullwhen no--stockwas given — distinct from "unknown," it means stock validation genuinely did not run.source_versionisnullwhenever the source tool doesn't self-report a version anywhere in its route output (true for AiZynthFinder JSON today) — never a guess.report_schema_version/source_formatintentionally duplicate the report's own pre-existing flatschema_version/source_formatfields (kept for backward compatibility, not removed) — see the report-schema rule in Compatibility rules below for why both exist.
Determinism: auditing the same input twice, with the same flags,
produces byte-identical output. This is a tested property
(auditing_the_same_input_twice_is_byte_identical in
tests/audit_route_cli.rs), not just a design intent.
Audit policy
All three policies are implemented as of v0.29.0 (Audit Policy
Profiles) — informational/standard/strict are all selectable via
--policy on the CLI, policy= on renkin.audit_route() in Python, and
the 4th argument to the WASM
audit_route_v2 export (also the
playground's Audit tab policy selector). standard remains the default
everywhere — omitting --policy/policy/passing "standard" explicitly
reproduces exactly the same verdict computation this project has always
had, unchanged.
The rule that constrains all three: policy never hides a finding.
Every individual finding (AuditFinding, per-step
forward_validation/stock_validation results) is always reported in
full, at every policy level. Only the derived AuditStatus
(pass/fail/partial) computation changes:
| Policy | A route with only not_evaluable checks (nothing outright fails) |
A route with a gating finding present |
|---|---|---|
informational |
partial |
partial (never fail) |
standard (the default) |
partial |
fail |
strict |
fail (not_evaluable is not good enough) |
fail |
informational is for exploratory triage where a hard stop isn't wanted;
strict is for pipelines that should treat "we couldn't fully verify this"
the same as "this is wrong."
Forward validation evidence basis
Every step's forward_validation.method is always
"declared_reaction_replay" — that field says how the check works
(replay the one declared reaction, compare to the target), not what
evidence backed it. A status: "pass" under that one method can rest on
three meaningfully different foundations, distinguished by the additive
evidence_basis field (null/absent when no SMIRKS was ever resolved to
replay in the first place, e.g. no reaction evidence at all, or an
unresolvable template_id):
evidence_basis |
What it means | When it applies |
|---|---|---|
declared_rule_template |
An independently-authored RetroRule.smirks string, resolved from the rule corpus by template_id and replayed as-is. |
RENKIN-native steps using a SMIRKS-based rule (most default and all extracted templates). |
derived_graph_rule_roundtrip |
A forward SMIRKS chem_env::declared_forward_smirks derived for this exact step, by re-running a graph-based rule's own cleavage function against the step's target and matching the outcome to its declared precursors — see docs/design/retro-rule-precision-gaps-v0.md #5. |
RENKIN-native steps using one of the 8 graph-based default rules (ester_cleavage, amide_cleavage, aryl_ether_retro, suzuki_retro, sulfonamide_retro, diaryl_sulfone_retro, boc_deprotection_retro, cbz_deprotection_retro), which have no independently-authored SMIRKS string at all. |
source_tool_reaction |
The source planner's own reaction record, replayed as declared. | AiZynthFinder, Syntheseus, and SynPlanner steps. |
derived_graph_rule_roundtrip is real evidence, but weaker evidence
than the other two — this is a from-classification, not a strength
ranking, and the difference matters for anyone aggregating pass rates.
The SMIRKS behind a declared_rule_template or source_tool_reaction
pass exists independently of any one step's claimed precursors — it was
authored (or exported) before this specific route was ever checked, so a
successful replay is genuine independent confirmation. The SMIRKS behind a
derived_graph_rule_roundtrip pass is constructed from the very outcome
it's then checked against: declared_forward_smirks only produces a
result when the graph-based rule's real output already matches the step's
declared precursors, so the subsequent forward replay is closer to a
round-trip consistency check on chematic's own reaction engine than an
independent verification. It still means something — the derivation can
fail (returns None, which flows through to evidence_basis: null and
forward_validation.status: "not_evaluable", never a false pass, and
never a false fail either — a graph-based rule's step is structurally
either pass or not_evaluable, it can't reach fail), and every
pass it does produce reproduces the exact declared target, stereo
included — but don't treat it as equivalent-strength evidence to the
other two evidence_basis values when computing an aggregate
forward-validated rate.
Compatibility rules
These apply to every adapter (RENKIN-native, AiZynthFinder, Syntheseus, and any future one), not just one:
- "Verified against" is not "supported." Documentation states an
adapter is confirmed against one specific real captured tool version
(see each adapter's own
PROVENANCE.mdundertests/fixtures/) — never phrased as broad version support inferred rather than observed. - Unknown/future input fields are tolerated, never rejected. Every
adapter's input struct (
AzfNodefor AiZynthFinder,AuditRouteInput/AuditRouteEntry/AuditRouteStepInputfor RENKIN-native,SyntheseusRouteV1for Syntheseus) derivesDeserializewithoutdeny_unknown_fieldson purpose, so a field from a future tool version — or a caller's own extra metadata — is silently ignored rather than a parse error. (unknown_extra_fields_in_renkin_input_are_tolerated_not_rejectedintests/audit_route_cli.rstests this directly.) - A corrupted/malformed tree shape fails loud, never silently coerced.
Self-loops, cycles, unparseable SMILES, a non-leaf node with no
children, an ambiguous leaf (neither a declared building block nor
another step's target), and a handful of other structural defects each
map to one of
AuditFindingCode's closed set (RawOutputNotDecodable,MultipleOrZeroRoots,CycleDetected,DegenerateSelfReferentialStep,ChildlessNonLeaf,AmbiguousLeafStatus,UnparseableSmilesInRoute, ...) — every one of these setsroute_tree_parseable: falseandstatus: fail. There is no "best-effort partial parse" path. - Report schema changes.
schema_version(top-level onAuditRouteReport) andaudit_manifest.report_schema_versionboth version the report envelope — the shape of{schema_version, source_format, audit_manifest, summary, routes}itself, not any one adapter's input format. A purely additive change (a new optional top-level or per-route field) does not bump this version; a breaking change to an existing field's meaning or an existing field's removal does. Report consumers should tolerate unknown keys in the response (the same forward-compatibility rule as rule 2, applied to output instead of input) but treat an unexpectedschema_versionas unsupported rather than guessing at the new shape. - Source-tool stock claims and RENKIN's own stock verification are
separate signals, never merged. An AiZynthFinder route's own
in_stockclaim is read only as structural input (whether a node is a leaf);stock_validation'spass/fail/not_evaluableverdict comes entirely from checking each leaf's canonical SMILES against RENKIN's own--stockfile (or reportingnot_evaluable/stock_not_providedif none was given). A leaf AiZynthFinder calls purchasable can still fail RENKIN's own stock check, and that disagreement is reported as real signal, not resolved in either direction. - Adapter fixture addition runbook. New adapters and new edge-case
fixtures for existing adapters both follow the same pattern established
in
tests/fixtures/aizynthfinder/v4.4.1/: - Prefer a real captured output from an actual run of the real tool.
Record exact capture command, tool version, and input/model/stock
file SHA-256s in a sibling
PROVENANCE.md— see that file for the level of detail expected. - If a specific edge case (e.g. a field the real tool never actually
omits under normal operation) can't be produced from real output,
a minimal, explicitly-labeled mutation of a real fixture is
acceptable — but the
PROVENANCE.mdentry must say so plainly and state it must never be cited as evidence of real tool output. Never synthesize a fixture from imagined/guessed schema. - Trim large real captures rather than hand-authoring — keep only the routes/fields needed for the test, document exactly what was removed, and never alter a field's value within a kept route.