---
title: "RENKIN Bridge: Audit Reproducibility and Compatibility Contract"
description: "What the audit_manifest guarantees, the informational/standard/strict audit-policy semantics, and the compatibility rules every RENKIN Bridge adapter follows."
---

# 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](aizynthfinder-audit-demo.md),
[Audit a Syntheseus Route](syntheseus-audit-demo.md), and
[Audit a Real SynPlanner Route](synplanner-audit-demo.md) 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:

```json
{
  "audit_manifest": {
    "renkin_version": "0.37.0",
    "report_schema_version": 1,
    "source_format": "aizynthfinder",
    "source_version": null,
    "input_sha256": "sha256:...",
    "stock_sha256": null,
    "policy": "standard"
  }
}
```

- `input_sha256` hashes 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_sha256` hashes the canonicalized `--stock` set actually loaded
  (order-independent), and is `null` when no `--stock` was given —
  distinct from "unknown," it means stock validation genuinely did not run.
- `source_version` is `null` whenever 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_format` intentionally duplicate the
  report's own pre-existing flat `schema_version`/`source_format` fields
  (kept for backward compatibility, not removed) — see the report-schema
  rule in [Compatibility rules](#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`](../api/wasm.md#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:

1. **"Verified against" is not "supported."** Documentation states an
   adapter is confirmed against one specific real captured tool version
   (see each adapter's own `PROVENANCE.md` under `tests/fixtures/`) — never
   phrased as broad version support inferred rather than observed.
2. **Unknown/future input fields are tolerated, never rejected.** Every
   adapter's input struct (`AzfNode` for AiZynthFinder,
   `AuditRouteInput`/`AuditRouteEntry`/`AuditRouteStepInput` for
   RENKIN-native, `SyntheseusRouteV1` for Syntheseus) derives `Deserialize`
   without `deny_unknown_fields` on 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_rejected` in
   `tests/audit_route_cli.rs` tests this directly.)
3. **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 sets `route_tree_parseable: false` and `status: fail`. There is no
   "best-effort partial parse" path.
4. **Report schema changes.**
   `schema_version` (top-level on `AuditRouteReport`) and
   `audit_manifest.report_schema_version` both 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 unexpected `schema_version` as unsupported rather
   than guessing at the new shape.
5. **Source-tool stock claims and RENKIN's own stock verification are
   separate signals, never merged.** An AiZynthFinder route's own
   `in_stock` claim is read only as structural input (whether a node is a
   leaf); `stock_validation`'s `pass`/`fail`/`not_evaluable` verdict comes
   entirely from checking each leaf's canonical SMILES against RENKIN's
   own `--stock` file (or reporting `not_evaluable`/`stock_not_provided`
   if 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.
6. **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.md` entry 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.
