---
title: "Certifying outputs and detecting drift"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Certifying outputs and detecting drift}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment  = "#>"
)
library(reproducr)
cert_file <- tempfile() # shared across all chunks
```

This vignette covers Tier 2 of the `reproducr` workflow in depth:
`certify()`, `check_drift()`, and `list_certs()`. These three functions
together form the *baseline and drift detection* system.

## The problem they solve — a real scenario

### Scenario — The revision drift problem

You submit a paper in March. Before submission you run the analysis and note
the key results: hazard ratio 0.582 (95% CI: 0.446–0.760, p < 0.001).

In May a reviewer asks for a revision. While working on the response you
upgrade your packages — including `lme4`, which adjusted its default optimizer
tolerances between versions 1.1.29 and 1.1.30. You re-run the analysis:
hazard ratio 0.591 (95% CI: 0.452–0.768).

The numbers are slightly different. No error was thrown. The code is
identical. Without a record of what the March run produced, you would not
know whether the change came from your revision or from the package upgrade.

```
[DRIFTED] hr:       0.582 → 0.591
[DRIFTED] ci_lower: 0.446 → 0.452
[DRIFTED] ci_upper: 0.760 → 0.768
```

With `certify()` and `check_drift()`, this is caught immediately and you can
investigate before submitting to the reviewer.

More broadly, packages change hands, maintainers push silent fixes,
platform-level libraries (BLAS, LAPACK) get updated by system administrators,
and R itself changes RNG defaults between minor versions. Any of these can
alter your numerical results without producing an error.

`certify()` and `check_drift()` detect this. The idea is simple:

1. After a successful analysis run, hash the key outputs and store the hashes.
2. Later — after any change to the environment — re-run the analysis and
   compare the new hashes against the stored ones.
3. Any mismatch is reported explicitly, by output name.

---

## `certify()` — creating a baseline

### What gets hashed

Pass a fully named list of any R objects you want to protect. Common choices:

```{r certify-examples}
model <- lm(mpg ~ wt + cyl, data = mtcars)

certify(
  outputs = list(
    coefs       = coef(model),
    r_squared   = summary(model)$r.squared,
    sigma       = sigma(model),
    n_obs       = nrow(mtcars),
    n_complete  = sum(complete.cases(mtcars)),
    group_means = aggregate(mpg ~ cyl, data = mtcars, FUN = mean)
  ),
  tag = "baseline-v1",
  script = "analysis.R",
  file = cert_file
)
```

### Choosing what to certify

Certify outputs that are:

- **Conclusions** — the numbers that appear in your paper or report
- **Stable** — not random session artefacts like timestamps or row ordering
- **Interpretable** — so a drift report tells you something meaningful

Avoid certifying objects that are expected to differ across runs by design,
such as `proc.time()` outputs or `Sys.time()` values.

### Tags and the certification store

Every certification requires a `tag` — a human-readable label:

```{r tags}
certify(
  outputs = list(coefs = coef(model)),
  tag     = "pre-peer-review",
  file    = cert_file
)

certify(
  outputs = list(coefs = coef(model)),
  tag     = "post-revision",
  file    = cert_file
)
```

Passing a duplicate tag overwrites the existing record with a warning:

```{r duplicate-tag, warning = TRUE}
certify(
  outputs = list(coefs = coef(model)),
  tag     = "baseline-v1",
  file    = cert_file
)
```

---

## `list_certs()` — inspecting the store

```{r list-certs}
list_certs(file = cert_file)
```

---

## `check_drift()` — comparing against a baseline

### Basic usage

```{r drift-basic}
model2 <- lm(mpg ~ wt + cyl, data = mtcars)

result <- check_drift(
  outputs = list(
    coefs       = coef(model2),
    r_squared   = summary(model2)$r.squared,
    sigma       = sigma(model2),
    n_obs       = nrow(mtcars),
    n_complete  = sum(complete.cases(mtcars)),
    group_means = aggregate(mpg ~ cyl, data = mtcars, FUN = mean)
  ),
  against = "baseline-v1",
  file = cert_file
)
```

### The four statuses

```{r statuses}
certify(
  outputs = list(
    stays_same  = 42L,
    will_change = coef(lm(mpg ~ wt, data = mtcars)),
    will_vanish = "this output disappears next run"
  ),
  tag = "four-statuses",
  file = cert_file
)

demo_result <- check_drift(
  outputs = list(
    stays_same  = 42L,
    will_change = coef(lm(mpg ~ hp, data = mtcars)),
    brand_new   = "this output is new"
  ),
  against = "four-statuses",
  file = cert_file
)

print(demo_result)
```

| Status | Meaning |
|---|---|
| `ok` | Hash matches the baseline exactly |
| `drifted` | Hash differs — output has changed |
| `missing` | Present in baseline, not supplied to `check_drift()` |
| `new` | Supplied to `check_drift()`, not in baseline |

### Using `"latest"`

```{r latest}
certify(outputs = list(x = 1L), tag = "run-1", file = cert_file)
certify(outputs = list(x = 1L), tag = "run-2", file = cert_file)
certify(outputs = list(x = 1L), tag = "run-3", file = cert_file)

check_drift(outputs = list(x = 1L), against = "latest", file = cert_file)
```

### Using drift results programmatically

```{r drift-programmatic, eval = FALSE}
result <- check_drift(outputs = current_outputs, against = "latest")

n_drifted <- sum(result$status == "drifted")
if (n_drifted > 0L) {
  drifted_names <- result$output[result$status == "drifted"]
  stop(sprintf(
    "%d output(s) have drifted since last certification: %s",
    n_drifted,
    paste(drifted_names, collapse = ", ")
  ))
}
```

---

## Recommended workflow

### At submission

```r
certify(
  outputs = list(
    primary_coef = coef(model)[2],
    primary_pval = summary(model)$coefficients[2, 4],
    n            = nrow(data),
    effect_size  = compute_d(model)
  ),
  tag    = "submitted-2026-01-15",
  script = "main_analysis.R"
)
```

### After reviewer comments

```r
check_drift(
  outputs = list(
    primary_coef = coef(model)[2],
    primary_pval = summary(model)$coefficients[2, 4],
    n            = nrow(data),
    effect_size  = compute_d(model)
  ),
  against = "submitted-2026-01-15"
)
```

---

## Version control

Commit `.reproducr.rds` to your Git repository. This gives you a permanent,
auditable history of what every run produced, and lets you compare against any
past milestone.

Add to `.gitattributes` to prevent noisy diffs:

```
.reproducr.rds binary
```

```{r cleanup, include = FALSE}
unlink(paste0(cert_file, ".rds"))
```
