Worked example · October 2026

One small analysis, with an audit trail and a replay check.

A real run of regulog and reproducr on public teaching data, with the output shown as it came out, including the parts that are less impressive than a demo would be.

What this is, and is not. This is a worked example, not a client engagement and not a validation. The data is R's built-in ToothGrowth set (60 guinea pigs, three vitamin C doses, two delivery methods), which is public teaching data and not clinical data. The user name jsmith is a placeholder. Nothing here shows compliance with any regulation.

The set-up

Run on 28 October 2026 with R 4.4.2 on macOS (Apple silicon), regulog 0.2.1 and reproducr 0.2.0, both from CRAN. The whole script is a single file you can run yourself: case-study.R. It needs no internet access and writes its output to a text file.

The question: does tooth length depend on dose and supplement type? The analysis plan, written down first, says: a linear model with dose as a numeric variable.

Part 1. The audit trail

The analyst opens a log, records the question and plan, reads the data through a logging wrapper, and fits the planned model.

log <- regulog_init(app = "toothgrowth-example", version = "1.0",
                    user = "jsmith", path = rlog)
log_note(log, paste(
  "Question: does tooth length depend on dose and supplement type?",
  "Analysis plan v1: linear model, dose as numeric."))
tg <- rl_read(log, utils::read.csv, data_csv)
fit1 <- lm(len ~ dose * supp, data = tg)
log_action(log, action = "run", object = "model_1",
           reason = "lm(len ~ dose * supp), dose numeric, per plan v1")

Then the analyst notices that dose takes only three values and decides to treat it as a factor. That departs from the plan, so the change is logged with the reason before the second model is run.

log_change(log, object = "model_spec", field = "dose",
           before = "numeric", after = "factor",
           reason = "Dose takes three discrete levels; modelled as a factor")
fit2 <- lm(len ~ dose_f * supp, data = tg)
log_action(log, action = "run", object = "model_2",
           reason = "lm(len ~ dose_f * supp) after the specification change")
log_signature(log, paste(
  "I reviewed model_2 against analysis plan v1 and the documented",
  "change, and accept it."))

The resulting trail has six entries. Each one carries a SHA-256 hash that includes the hash of the entry before it. The table shows the first twelve characters of each hash.

#TypeActionObjectWhat it recordsEntry hash
1NOTEnote–Question and analysis plan v1 (dose as numeric)f3611bfd205e…
2ACTIONdata_read(temp CSV)60 rows, 3 columns, read through rl_read()334edf54d76f…
3ACTIONrunmodel_1lm(len ~ dose * supp), dose numeric216e8c8e976b…
4CHANGE–model_specdose: numeric to factor. Reason: three discrete dose levels276c1c28069f…
5ACTIONrunmodel_2lm(len ~ dose_f * supp) after the change446eb8a28664…
6SIGNATUREsignaturejsmithReviewed model_2 against plan v1 and the change; accepted. Covers 5 entries387d5fddaf59…

Verifying it, and breaking it

Verification reads the log file and recomputes the chain:

verify_log(log)
#> regulog: Log intact: 6 entries, chain unbroken

To test the claim of tamper-evidence, a copy of the file was edited by hand: in entry 4, "three discrete levels" was changed to "two discrete levels". Verifying the copy gave:

intact = FALSE ; first broken = 4
#> Entry #4: entry_hash mismatch - content may have been modified

So a changed entry is caught and located. The limit is worth saying plainly: a hash chain shows that something was altered, not who did it, and on its own it cannot stop someone who recomputes every later hash as well. Keeping a copy of the latest hash somewhere separate, and exporting the trail with a signature, closes that gap. The process around the log matters as much as the log.

Part 2. A replay check

The second package answers a different question: if you run this again, do you get the same numbers? The three headline outputs of the final model (coefficients, R squared and number of observations) were certified as a baseline.

base_out <- list(coefs = coef(fit2),
                 r_squared = summary(fit2)$r.squared,
                 n_obs = nrow(tg))
certify(outputs = base_out, tag = "baseline-v1",
        script = "analysis.R", file = cert)

Re-running on the same data gave the verdict ALL OUTPUTS MATCH (3 of 3). Then one row of the data was quietly dropped and the model refitted:

-- reproducr drift check vs 'baseline-v1' --
  Verdict  : DRIFT DETECTED
  OK       : 0
  Drifted  : 3
    - coefs
    - r_squared
    - n_obs

One missing row out of 60 changed all three outputs, and the check said so. It also said, honestly, Hash mismatch (numeric tolerance check requires stored values): the comparison here is by hash, so it tells you that something changed, not by how much or why. For that you pair it with the audit log.

The same package also audited a three-line script (it found two packages and three calls, both from base R) and scored no risks. That verdict is accurate but unsurprising for a script that only uses base R. It becomes more informative on a script with real dependencies.

Part 3. With an AI draft in the loop

A second script puts the pieces together for an AI-assisted analysis: record the context of use, log the AI draft, log the human's decision (accept, edit or reject), run the reviewed code, certify the outputs, replay them, and sign. The run produced a nine-entry log that verified as intact, and a copy edited by hand was flagged at entry 4.

An important caveat: this run used a fixed, offline stand-in instead of a real model, so it demonstrates the logging and review flow and not any model's behaviour. The script is not published here yet.

What this shows, and what it does not

It shows that the documented functions work together as described on a small, concrete analysis: decisions and changes leave a verifiable trail, and a quiet change to the data is detected on replay.

It does not show that any of this satisfies a regulator, that it scales to a real study, or that it has been through independent validation. It involves one analyst, one machine and one small public dataset. Whether a given process is adequate is a judgement for your quality group.

Software: regulog and reproducr on CRAN. Output excerpts are lightly shortened; temporary file paths are removed.

Want to see this on your own workflow?

A short conversation can show which of these records you already keep and which you do not.