---
title: "Getting started with regulog"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Getting started with regulog}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
library(regulog)
```

R analyses leave no audit trail by default. `regulog` adds one — a
tamper-evident, hash-chained record of every action, change, decision,
and signature. Every entry is attributed to a named user, time-stamped
in UTC, and cryptographically linked to the previous entry so that any
modification after the fact is detectable.

This vignette walks through the complete API from session initialisation
to regulatory export.

## 1. Initialise a session

`regulog_init()` creates the session object. Every subsequent log call
is attached to this object.

| Argument | Required | Purpose |
|---|---|---|
| `app` | Yes | Application or system name |
| `version` | No | Application version (default: `"unknown"`) |
| `user` | No | Acting user (default: `Sys.info()[["user"]]`) |
| `path` | No | File path for persistent `.rlog` storage |
| `hash_algo` | No | Hashing algorithm (default: `"sha256"`) |

```{r init}
log <- regulog_init(
  app     = "primary-analysis",
  version = "1.0.0",
  user    = "jsmith"
  # Provide path = "logs/audit.rlog" in production for persistent storage
)

log
```

When `path` is omitted, the log lives in memory only — suitable for
development and testing. In production, always supply a `path` so entries
survive the R session.

The genesis record is written immediately on `regulog_init()`. Its
SHA-256 hash anchors the entire chain — see `vignette("hash-chain")` for
how the cryptographic linking works.

## 2. Log actions

`log_action()` records a discrete event. The `reason` argument is
**mandatory with no default** — undocumented entries are rejected.

```{r action-basic}
log_action(log,
  action = "data_read",
  object = "adsl.sas7bdat",
  reason = "Reading subject-level dataset for primary efficacy analysis"
)
```

The `action` and `object` fields accept any strings — choose a controlled
vocabulary that suits your organisation. Common patterns:

```{r action-examples}
# Analytical steps
log_action(log,
  action = "model_fit",
  object = "primary_ANCOVA",
  reason = "Fitting ANCOVA: CHG ~ TRT01P + BASE + SITEID per SAP section 6.1"
)

# Data exports
log_action(log,
  action = "export",
  object = "Table14_1.rtf",
  reason = "Primary efficacy table exported for clinical study report"
)

# Review and approval events
log_action(log,
  action = "approved",
  object = "primary_results_v3",
  reason = "QC review complete — all outputs match SAP-specified formats"
)

# User can override the session user for a single entry
log_action(log,
  action = "co_reviewed",
  object = "primary_results_v3",
  reason = "Independent statistical review complete",
  user   = "second.reviewer"
)
```

## 3. Log field changes

`log_change()` captures a before/after modification — the primary
mechanism for satisfying 21 CFR Part 11 §11.10(e) change documentation.

```{r change-basic}
log_change(log,
  object = "alpha",
  field  = "value",
  before = "0.05",
  after  = "0.025",
  reason = "Significance level updated per protocol amendment 2 (2026-05-01)"
)
```

The `before` and `after` arguments are coerced to character, so they
accept any R value:

```{r change-examples}
# Data correction
log_change(log,
  object = "subject_01042",
  field  = "ae_onset_date",
  before = "2026-03-01",
  after  = "2026-03-11",
  reason = "Transcription error — corrected per source CRF page 47, query Q-0192"
)

# Configuration update
log_change(log,
  object = "model_config",
  field  = "covariance_structure",
  before = "compound_symmetry",
  after  = "unstructured",
  reason = "Unstructured covariance pre-specified in SAP section 6.1.2"
)

# Population definition change
log_change(log,
  object = "analysis_population",
  field  = "SAFFL_definition",
  before = "RANDFL = 'Y'",
  after  = "RANDFL = 'Y' AND EXOCCUR = 'Y'",
  reason = "Protocol amendment 3: safety population requires confirmed dosing"
)
```

## 4. Log notes and decisions

`log_note()` captures free-text annotations — any rationale, observation,
or decision that does not fit a discrete action verb or a before/after
field change. Common uses:

```{r note-examples}
# Outlier decision
log_note(
  log,
  "Outlier identified for subject 01-042 at Week 16 (AVAL = 98.4,
   upper fence = 62.1). Discussed with medical monitor on 2026-06-20.
   Retained in primary analysis per SAP section 8.3 — no protocol
   deviation recorded. Sensitivity analysis without outlier pre-specified
   in SAP section 10.4."
)

# Protocol deviation
log_note(
  log,
  "Subject 01-007: visit window deviation at Week 8 (visited Day 61,
   window Day 50-58). Classified as minor deviation per deviation
   assessment log entry DEV-0031. Subject retained in ITT population."
)

# Query resolved
log_note(
  log,
  "Data query Q-0047 resolved 2026-06-15: lab value for subject 01-019
   at Screening confirmed as 4.2 mmol/L per site laboratory report.
   Original value 42.0 was a decimal error."
)

# Analysis assumption documented
log_note(
  log,
  "Missing baseline value for subject 01-033: LOCF imputation applied
   per SAP section 7.2 — previous non-missing value (Visit 1) used.
   Imputed value: 24.6."
)
```

## 5. Logging data reads

Manually calling `log_action()` for every file read is error-prone and
easy to forget. `regulog` provides two ways to log reads explicitly:
`rl_read()` for a single call, and `with_log()` for a scoped block where
multiple reads share the same logging context.

### Single reads with `rl_read()`

`rl_read(log, reader, ...)` calls `reader(...)` and logs the result as a
`data_read` ACTION entry — capturing the resolved file path, row count,
and column count automatically.

```{r rl-read, eval = FALSE}
adsl <- rl_read(log, haven::read_sas, "data/adsl.sas7bdat")
adae <- rl_read(log, haven::read_sas, "data/adae.sas7bdat")
```

`rl_read()` works with any reader function — `haven::read_sas`,
`readr::read_csv`, `data.table::fread`, `utils::read.csv`, or a custom
function — since it wraps the call explicitly rather than depending on a
fixed list of patched functions.

The file path is resolved from a named argument (`file`, `path`,
`data_file`, or `input`) if present, falling back to the first unnamed
argument — so reordered named calls still record the correct path:

```{r rl-read-named, eval = FALSE}
adae <- rl_read(log, readr::read_csv, col_types = "ccd", file = "data/adae.csv")
```

### Scoped logging with `with_log()`

For a block containing several reads, `with_log()` provides a local
`read()` binding so the `log` argument doesn't need to be repeated at
every call:

```{r with-log, eval = FALSE}
with_log(log, {
  adsl   <- read(haven::read_sas, "data/adsl.sas7bdat")
  adae   <- read(haven::read_sas, "data/adae.sas7bdat")
  adlb   <- read(haven::read_sas, "data/adlb.sas7bdat")
  params <- read(readr::read_csv, "config/parameters.csv")
})
```

`read()` is only available inside the `with_log()` block — calling a
reader function bare (without `read(...)`) is not logged. This is
deliberate: every logged read is visible at its call site, with no
implicit or hidden logging behaviour.

Each logged entry captures the file path, row count, and column count.
For example:

```
action: data_read
object: data/adsl.sas7bdat
reason: haven::read_sas("data/adsl.sas7bdat") — 298 rows, 47 cols
```

`with_log()` guarantees `expr` is evaluated in an isolated scope: the
`read()` binding for one `with_log()` call cannot interfere with another,
even across concurrent sessions (for example, two users in the same
Shiny application). If `expr` errors, the error propagates normally and
any entries logged before the error remain intact in the chain.

## 6. Electronic signatures

`log_signature()` records a named, dated, meaningful sign-off. Two things
happen automatically — no user input required:

- **Signer identity** is resolved from the session user set at
  `regulog_init()` — it cannot be overridden at signing time
- **Entries covered** is captured as the count of prior entries in the
  session at the moment of signing

```{r signature-basic}
log_signature(
  log,
  "I certify that this primary analysis is accurate and complete,
   conducted in accordance with SAP version 2.0 dated 2026-05-01"
)
```

Multiple signatures are supported — for example, a lead statistician and
an independent reviewer:

```{r signature-multiple, eval = FALSE}
log_signature(
  log,
  "Statistical analysis complete and accurate per SAP v2.0.
   All deviations documented."
)

# Second reviewer — create a new log or log against the same path with
# a different session user
log2 <- regulog_init(
  app = "primary-analysis", version = "1.0.0",
  user = "second.reviewer",
  path = "logs/trial001_audit.rlog"
)

log_signature(
  log2,
  "Independent QC review complete. Results independently verified."
)
```

## 7. Verify chain integrity

`verify_log()` recomputes every entry hash and confirms each `prev_hash`
links correctly to its predecessor. Works on both a live `regulog` object
and a `.rlog` file path.

```{r verify}
verify_log(log)
```

The return value carries structured results:

```{r verify-result}
result <- verify_log(log, verbose = FALSE)
cat("Intact:        ", result$intact, "\n")
cat("Entries checked:", result$n_entries, "\n")
cat("First broken:  ", result$first_broken, "\n")
```

Tampering is reliably detected:

```{r tamper}
saved <- log$entries[[2L]]$reason
log$entries[[2L]]$reason <- "ALTERED REASON"

tamper_result <- suppressWarnings(verify_log(log, verbose = FALSE))
cat("Intact after tamper:", tamper_result$intact, "\n")
cat("First broken entry: ", tamper_result$first_broken, "\n")

log$entries[[2L]]$reason <- saved # restore
```

Verification from a file path requires no live session:

```{r verify-file, eval = FALSE}
verify_log("logs/trial001_audit.rlog")
```

## 8. Query the log

`filter_log()` returns log entries as a `data.frame`. All arguments are
optional — omitting all returns every entry.

```{r filter-all}
all_entries <- filter_log(log)
all_entries[, c("entry_id", "type", "action", "user", "reason")]
```

Filter by entry type:

```{r filter-type}
filter_log(log, type = "SIGNATURE")[, c("type", "user", "reason", "after")]
```

Filter by action value:

```{r filter-action}
filter_log(log, action = "approved")[, c("action", "object", "reason")]
```

Filter by user:

```{r filter-user}
filter_log(log, user = "jsmith")[, c("type", "action", "object")]
```

Filter by date range — useful when querying a long-running shared log:

```{r filter-date}
# Entries from today onwards
filter_log(log, from = format(Sys.Date(), "%Y-%m-%d"))[, c("type", "action")]

# Entries before a cutoff (empty for new log)
filter_log(log, to = "2025-12-31")
```

Combine filters:

```{r filter-combined}
filter_log(log,
  type   = c("ACTION", "NOTE"),
  user   = "jsmith",
  from   = "2026-01-01"
)[, c("type", "action", "reason")]
```

`filter_log()` also accepts a `.rlog` file path directly — no live
session or `regulog` object required:

```{r filter-file, eval = FALSE}
filter_log("logs/trial001_audit.rlog",
  type = "SIGNATURE",
  user = "jsmith"
)
```

## 9. Convert to data frame

`as.data.frame()` converts all non-genesis entries to a flat data frame —
same column layout as `export_audit_trail(format = "csv")`:

```{r as-df}
df <- as.data.frame(log)
names(df)
nrow(df)
```

## 10. Export the audit trail

`export_audit_trail()` serialises the log to CSV or JSON. Use
`signed = TRUE` to run verification and stamp `chain_intact` and
`verified_at` on every row.

```{r export-csv}
df_export <- export_audit_trail(log, format = "csv", signed = TRUE)
df_export[, c("entry_id", "type", "action", "user", "chain_intact", "verified_at")]
```

```{r export-json, eval = FALSE}
# JSON envelope with metadata header
export_audit_trail(log,
  format = "json",
  signed = TRUE,
  path   = "outputs/audit_trail.json"
)

# CSV for regulatory submission or spreadsheet review
export_audit_trail(log,
  format = "csv",
  signed = TRUE,
  path   = "outputs/audit_trail_TRIAL001_PRIMARY.csv"
)
```

Date filtering is available on export too:

```{r export-filtered, eval = FALSE}
# Only entries from a specific analysis phase
export_audit_trail(log,
  format = "csv",
  from   = "2026-06-01",
  to     = "2026-06-30",
  signed = TRUE,
  path   = "outputs/audit_june2026.csv"
)
```

## 11. Entry type reference

| Type | Created by | Mandatory fields | Regulatory purpose |
|---|---|---|---|
| `ACTION` | `log_action()` | `action`, `object`, `reason` | Discrete events |
| `CHANGE` | `log_change()` | `object`, `field`, `before`, `after`, `reason` | Field modifications |
| `NOTE` | `log_note()` | `text` | Decisions and rationale |
| `SIGNATURE` | `log_signature()` | `meaning` | Sign-off |

## 12. Validation (regulated environments)

Any software used in a regulated environment — under 21 CFR Part 11,
EU Annex 11, or GAMP 5 — must be formally qualified before it can be
used to generate or sign electronic records that regulators may inspect.
`regulog` ships pre-written, executable IQ/OQ/PQ qualification protocols
that cover all three phases.

### Running the protocols

Run each script in sequence in the target environment — the R installation
that will be used for regulated work:

```{r validation, eval = FALSE}
# Phase 1: Installation Qualification (10 tests)
# Verifies R version, package installation, dependency integrity,
# file system access, and namespace exports.
source(system.file("validation/IQ_regulog.R", package = "regulog"))

# Phase 2: Operational Qualification (26 tests)
# Tests every 21 CFR §11.10 requirement: hash chain integrity,
# tamper detection, user attribution, timestamps, export format,
# electronic signatures, and error isolation.
source(system.file("validation/OQ_regulog.R", package = "regulog"))

# Phase 3: Performance Qualification (7 tests)
# End-to-end clinical workflows: data review, regulatory export,
# multi-user session independence, 500-entry load test, and
# inspector query simulation.
source(system.file("validation/PQ_regulog.R", package = "regulog"))
```

### Capturing the qualification record

Retain the output of each run as documented evidence of system
qualification. The simplest approach is to capture it to a file:

```{r capture, eval = FALSE}
sink("IQ_execution_record.txt")
source(system.file("validation/IQ_regulog.R", package = "regulog"))
sink()

sink("OQ_execution_record.txt")
source(system.file("validation/OQ_regulog.R", package = "regulog"))
sink()

sink("PQ_execution_record.txt")
source(system.file("validation/PQ_regulog.R", package = "regulog"))
sink()
```

Each execution record includes the timestamp, R version, platform, and
the pass/fail result of every test against its acceptance criterion.

### Requirements traceability

The RTM maps every OQ test to the regulatory clause it addresses:

```{r rtm, eval = FALSE}
read.csv(system.file("validation/RTM_regulog.csv", package = "regulog"))
```

### Logging the qualification itself

The qualification run is itself an activity in a regulated environment
and should be logged. Using `regulog` to audit its own qualification
produces a Part 11-compliant record of who ran it, when, and the outcome:

```{r self-audit, eval = FALSE}
log <- regulog_init(
  app     = "regulog-qualification",
  version = "0.2.0",
  user    = "val.lead",
  path    = "qualification/audit_trail.rlog"
)

log_action(log,
  action = "qualification_start",
  object = "regulog 0.2.0",
  reason = "IQ/OQ/PQ qualification initiated per SOP-VAL-007"
)

source(system.file("validation/IQ_regulog.R", package = "regulog"))
log_action(log,
  action = "IQ_complete",
  object = "IQ_regulog.R",
  reason = "10 tests passed. Proceeding to OQ."
)

source(system.file("validation/OQ_regulog.R", package = "regulog"))
log_action(log,
  action = "OQ_complete",
  object = "OQ_regulog.R",
  reason = "26 tests passed. Proceeding to PQ."
)

source(system.file("validation/PQ_regulog.R", package = "regulog"))
log_action(log,
  action = "PQ_complete",
  object = "PQ_regulog.R",
  reason = "7 tests passed. Qualification complete."
)

log_signature(log,
  "I certify that regulog 0.2.0 has been qualified in this environment
   per SOP-VAL-007 and is approved for use in regulated R workflows."
)

verify_log(log)
export_audit_trail(log,
  format = "csv",
  signed = TRUE,
  path   = "qualification/audit_trail_export.csv"
)
```

### Re-qualification

Any significant change — a new package version, a change to the R
environment, or a platform migration — requires re-qualification. Re-run
the three protocols in the updated environment and retain the new
execution records as evidence that the qualified state has been
re-established.

See also `vignette("hash-chain")` for a detailed explanation of the
tamper detection mechanism, and the [qualification guide on
reprostats.org](https://reprostats.org/blog/qualifying-regulog-validated-environment.html)
for a fuller discussion of the regulatory context.
