Same-Code-Path Preview

Same-code-path preview

A preview that doesn't share its code path with execution will eventually lie. Sharing the selection query is necessary, but it is not enough. The durable version is execution minus the external side effect: the same resolved inputs, action, validation and selection flow through one plan.

The rule

Preview and execution must consume the same resolved operation. No parallel "preview" implementation, no "approximate" preview, no read-only mirror of the write path. One function decides both what the operation will touch and what it will do. Preview renders that plan; execution applies it.

Why this is non-negotiable

A bulk-archive bug in early mxr: preview said "1,200 messages." Execution archived 12,000. The preview query and the execute query had diverged — different joins, different limits, different ordering. The preview was correct for its own query; the query was wrong. The user saw a number, trusted it, ran the operation, lost a decade of mail.

After that the rule went in the decision log: same code, same selection, no exceptions. Any deviation is the architecture being wrong, not the rule.

The rule needed a correction on 28 July 2026. An mxr reply-all dry run explicitly described saving a draft, but the real command sent the message. The recipients and reply context were right; the action was wrong. --yes was doing two jobs: skipping confirmation and selecting send rather than save. The remediation added an explicit --draft mode that conflicts with --yes, then derived one sending value for validation, preview text and the final send-or-save branch. Verification was still running when this evidence was consolidated.

That incident widens the guarantee. A preview can share the same selection path and still lie about the verb. The preview contract includes action mode, destination, scope and other resolved intent, not only the rows or recipients.

What this looks like in practice

A typed plan captures the action as well as "what set of rows are we operating on." Both --dry-run and --yes build it once. --dry-run renders the plan; --yes renders it and then mutates. The mutation function takes the plan, not a query plus a second interpretation of the flags, so execution cannot silently expand the set or change the verb.

fn run_mutation(args: &Args) -> Result<()> {
    let plan = build_plan(args)?;
    if args.dry_run {
        print_preview(&plan);
        return Ok(());
    }
    confirm(&plan)?;
    apply(&plan)
}

The shape is what matters. Compute once; render or execute; never recompute under execute.

Why this generalizes beyond mutations

Any operation with a preview mode is vulnerable to drift between preview and execute. Schema migrations, mass tag updates, billing recalculations, A/B test rollouts. If preview and execute take different paths to "what will change," eventually they disagree under load or edge cases, and the disagreement is invisible until it isn't.

The frame: preview is the resolved execution plan minus the side effect. Not "execute, parallel-but-similar." Not "approximate." Minus the side effect, full stop.

The boundary of the guarantee

Same-code-path preview proves what the application will attempt. It does not
predict a remote provider's decision. An email dry-run can resolve the exact
account and From address through the same local path as send, but Zoho or Gmail
can still reject an alias that was never authorized there.

That distinction keeps the promise honest without weakening it. The preview
covers the application's selection and validation. The provider owns the next
boundary. See Local Identity Registration Is Not Provider Authorization.

Where this connects

See also