implement-audit-trail

Implement audit trail functionality for R projects in regulated environments. Covers logging, provenance tracking, electronic signatures, data integrity checks, and 21 CFR Part 11 compliance. Use when an R analysis requires electronic records compliance (21 CFR Part 11), when you need to track who did what and when in an analysis, when implementing data provenance tracking, or when creating tamper-evident analysis logs for regulatory submissions.

9 stars

Best use case

implement-audit-trail is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Implement audit trail functionality for R projects in regulated environments. Covers logging, provenance tracking, electronic signatures, data integrity checks, and 21 CFR Part 11 compliance. Use when an R analysis requires electronic records compliance (21 CFR Part 11), when you need to track who did what and when in an analysis, when implementing data provenance tracking, or when creating tamper-evident analysis logs for regulatory submissions.

Teams using implement-audit-trail should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/implement-audit-trail/SKILL.md --create-dirs "https://raw.githubusercontent.com/pjt222/agent-almanac/main/i18n/caveman-lite/skills/implement-audit-trail/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/implement-audit-trail/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How implement-audit-trail Compares

Feature / Agentimplement-audit-trailStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Implement audit trail functionality for R projects in regulated environments. Covers logging, provenance tracking, electronic signatures, data integrity checks, and 21 CFR Part 11 compliance. Use when an R analysis requires electronic records compliance (21 CFR Part 11), when you need to track who did what and when in an analysis, when implementing data provenance tracking, or when creating tamper-evident analysis logs for regulatory submissions.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Implement Audit Trail

Add audit trail capabilities to R projects for regulatory compliance.

## When to Use

- R analysis requires electronic records compliance (21 CFR Part 11)
- Need to track who did what, when, and why in an analysis
- Implementing data provenance tracking
- Creating tamper-evident analysis logs

## Inputs

- **Required**: R project with data processing or analysis scripts
- **Required**: Regulatory requirements (which audit trail elements are mandatory)
- **Optional**: Existing logging infrastructure
- **Optional**: Electronic signature requirements

## Procedure

### Step 1: Set Up Structured Logging

Create `R/audit_log.R`:

```r
#' Initialize audit log for a session
#'
#' @param log_dir Directory for audit log files
#' @param analyst Name of the analyst
#' @return Path to the created log file
init_audit_log <- function(log_dir = "audit_logs", analyst = Sys.info()["user"]) {
  dir.create(log_dir, showWarnings = FALSE, recursive = TRUE)

  log_file <- file.path(log_dir, sprintf(
    "audit_%s_%s.jsonl",
    format(Sys.time(), "%Y%m%d_%H%M%S"),
    analyst
  ))

  entry <- list(
    timestamp = format(Sys.time(), "%Y-%m-%dT%H:%M:%S%z"),
    event = "SESSION_START",
    analyst = analyst,
    r_version = R.version.string,
    platform = .Platform$OS.type,
    working_directory = getwd(),
    session_id = paste0(Sys.getpid(), "-", format(Sys.time(), "%Y%m%d%H%M%S"))
  )

  write(jsonlite::toJSON(entry, auto_unbox = TRUE), log_file, append = TRUE)
  options(audit_log_file = log_file, audit_session_id = entry$session_id)

  log_file
}

#' Log an audit event
#'
#' @param event Event type (DATA_IMPORT, TRANSFORM, ANALYSIS, EXPORT, etc.)
#' @param description Human-readable description
#' @param details Named list of additional details
log_audit_event <- function(event, description, details = list()) {
  log_file <- getOption("audit_log_file")
  if (is.null(log_file)) stop("Audit log not initialized. Call init_audit_log() first.")

  entry <- list(
    timestamp = format(Sys.time(), "%Y-%m-%dT%H:%M:%S%z"),
    event = event,
    description = description,
    session_id = getOption("audit_session_id"),
    details = details
  )

  write(jsonlite::toJSON(entry, auto_unbox = TRUE), log_file, append = TRUE)
}
```

**Got:** `R/audit_log.R` created with `init_audit_log()` and `log_audit_event()` functions. Calling `init_audit_log()` creates the `audit_logs/` directory and a timestamped JSONL file. Each log entry is a single JSON line with `timestamp`, `event`, `analyst`, and `session_id` fields.

**If fail:** If `jsonlite::toJSON()` fails, ensure the `jsonlite` package is installed. If the log directory cannot be created, check file system permissions. If timestamps lack timezone, verify `%z` is supported on the platform.

### Step 2: Add Data Integrity Checks

```r
#' Compute and log data hash for integrity verification
#'
#' @param data Data frame to hash
#' @param label Descriptive label for the dataset
#' @return SHA-256 hash string
hash_data <- function(data, label = "dataset") {
  hash_value <- digest::digest(data, algo = "sha256")

  log_audit_event("DATA_HASH", sprintf("Hash computed for %s", label), list(
    hash_algorithm = "sha256",
    hash_value = hash_value,
    nrow = nrow(data),
    ncol = ncol(data),
    columns = names(data)
  ))

  hash_value
}

#' Verify data integrity against a recorded hash
#'
#' @param data Data frame to verify
#' @param expected_hash Previously recorded hash
#' @return Logical indicating whether data matches
verify_data_integrity <- function(data, expected_hash) {
  current_hash <- digest::digest(data, algo = "sha256")
  match <- identical(current_hash, expected_hash)

  log_audit_event("DATA_VERIFY",
    sprintf("Data integrity check: %s", ifelse(match, "PASS", "FAIL")),
    list(expected = expected_hash, actual = current_hash))

  if (!match) warning("Data integrity check FAILED")
  match
}
```

**Got:** `hash_data()` returns a SHA-256 hash string and logs a `DATA_HASH` event. `verify_data_integrity()` compares current data against a stored hash and logs a `DATA_VERIFY` event with PASS or FAIL status.

**If fail:** If `digest::digest()` is not found, install the `digest` package. If hashes don't match for identical data, check that column order and data types are consistent between hashing and verification.

### Step 3: Track Data Transformations

```r
#' Wrap a data transformation with audit logging
#'
#' @param data Input data frame
#' @param transform_fn Function to apply
#' @param description Description of the transformation
#' @return Transformed data frame
audited_transform <- function(data, transform_fn, description) {
  input_hash <- digest::digest(data, algo = "sha256")
  input_dim <- dim(data)

  result <- transform_fn(data)

  output_hash <- digest::digest(result, algo = "sha256")
  output_dim <- dim(result)

  log_audit_event("DATA_TRANSFORM", description, list(
    input_hash = input_hash,
    input_rows = input_dim[1],
    input_cols = input_dim[2],
    output_hash = output_hash,
    output_rows = output_dim[1],
    output_cols = output_dim[2]
  ))

  result
}
```

**Got:** `audited_transform()` wraps any transformation function, logging input dimensions and hash, output dimensions and hash, and the transformation description as a `DATA_TRANSFORM` event.

**If fail:** If the transform function errors, the audit event is not logged. Wrap the transform in `tryCatch()` to log both successes and failures. Ensure the transform function accepts and returns a data frame.

### Step 4: Log Session Environment

```r
#' Log complete session information for reproducibility
log_session_info <- function() {
  si <- sessionInfo()

  log_audit_event("SESSION_INFO", "Complete session environment recorded", list(
    r_version = si$R.version$version.string,
    platform = si$platform,
    locale = Sys.getlocale(),
    base_packages = si$basePkgs,
    attached_packages = sapply(si$otherPkgs, function(p) paste(p$Package, p$Version)),
    renv_lockfile_hash = if (file.exists("renv.lock")) {
      digest::digest(file = "renv.lock", algo = "sha256")
    } else NA
  ))
}
```

**Got:** A `SESSION_INFO` event logged with R version, platform, locale, attached packages with versions, and the renv lockfile hash (if applicable).

**If fail:** If `sessionInfo()` returns incomplete package information, ensure all packages are loaded via `library()` before calling `log_session_info()`. The renv lockfile hash will be `NA` if the project does not use renv.

### Step 5: Implement in Analysis Scripts

```r
# 01_analysis.R
library(jsonlite)
library(digest)

# Start audit trail
log_file <- init_audit_log(analyst = "Philipp Thoss")

# Import data with audit
raw_data <- read.csv("data/raw/study_data.csv")
raw_hash <- hash_data(raw_data, "raw study data")

# Transform with audit
clean_data <- audited_transform(raw_data, function(d) {
  d |>
    dplyr::filter(!is.na(primary_endpoint)) |>
    dplyr::mutate(bmi = weight / (height/100)^2)
}, "Remove missing endpoints, calculate BMI")

# Run analysis
log_audit_event("ANALYSIS_START", "Primary efficacy analysis")
model <- lm(primary_endpoint ~ treatment + age + sex, data = clean_data)
log_audit_event("ANALYSIS_COMPLETE", "Primary efficacy analysis", list(
  model_class = class(model),
  formula = deparse(formula(model)),
  n_observations = nobs(model)
))

# Log session
log_session_info()
```

**Got:** Analysis scripts initialize the audit log at the start, log each data import, transformation, and analysis step, and record session info at the end. The JSONL log file captures the complete provenance chain.

**If fail:** If `init_audit_log()` is missing, ensure `R/audit_log.R` is sourced or the package is loaded. If events are missing from the log, verify that `log_audit_event()` is called after every significant operation.

### Step 6: Git-Based Change Control

Complement the application-level audit trail with git:

```bash
# Use signed commits for non-repudiation
git config commit.gpgsign true

# Descriptive commit messages referencing change control
git commit -m "CHG-042: Add BMI calculation to data processing

Per change request CHG-042, approved by [Name] on [Date].
Validation impact assessment: Low risk - additional derived variable."
```

**Got:** Git commits are signed (GPG) and use descriptive messages referencing change control IDs. The combination of application-level JSONL audit trail and git history provides a complete change control record.

**If fail:** If GPG signing fails, configure the signing key with `git config --global user.signingkey KEY_ID`. If the key is not set up, follow `gpg --gen-key` to create one.

## Validation

- [ ] Audit log captures all required events (start, data access, transforms, analysis, export)
- [ ] Timestamps use ISO 8601 format with timezone
- [ ] Data hashes enable integrity verification
- [ ] Session information is recorded
- [ ] Logs are append-only (no deletion or modification)
- [ ] Analyst identity is captured for each session
- [ ] Log format is machine-readable (JSONL)

## Pitfalls

- **Logging too much**: Focus on regulated events. Don't log every variable assignment.
- **Mutable logs**: Audit logs must be append-only. Use JSONL (one JSON object per line).
- **Missing timestamps**: Every event needs a timestamp with timezone.
- **No session context**: Each log entry should reference the session for correlation.
- **Forgetting to initialize**: Scripts must call `init_audit_log()` before any analysis.

## Related Skills

- `setup-gxp-r-project` - project structure for validated environments
- `write-validation-documentation` - validation protocols and reports
- `validate-statistical-output` - output verification methodology
- `configure-git-repository` - version control as part of change control

Related Skills

security-audit-codebase

9
from pjt222/agent-almanac

Perform a security audit of a codebase checking for exposed secrets, vulnerable dependencies, injection vulnerabilities, insecure configurations, and OWASP Top 10 issues. Use before publishing or deploying a project, for periodic security reviews, after adding authentication or API integration, before open-sourcing a private repository, or when preparing for a security compliance audit.

implement-pharma-serialisation

9
from pjt222/agent-almanac

Implement pharmaceutical serialisation and track-and-trace systems compliant with EU FMD, US DSCSA, and other global regulations. Covers unique identifier generation, aggregation hierarchy, EPCIS data exchange, and verification endpoint integration. Use when implementing serialisation for a new product launch, integrating with the EMVS/NMVS, designing DSCSA-compliant transaction exchange, building an EPCIS event repository, or extending serialisation to additional markets (China, Brazil, Russia).

implement-gitops-workflow

9
from pjt222/agent-almanac

Implement GitOps continuous delivery using Argo CD or Flux with app-of-apps pattern, automated sync policies, drift detection, and multi-environment promotion. Manage Kubernetes deployments declaratively from Git with automated reconciliation. Use when implementing declarative infrastructure management, migrating from imperative kubectl commands to Git-driven deployments, setting up multi-environment promotion workflows, enforcing code review gates for production, or meeting audit and compliance requirements.

implement-electronic-signatures

9
from pjt222/agent-almanac

Implement electronic signatures compliant with 21 CFR Part 11 Subpart C and EU Annex 11. Covers signature manifestation (signer, date/time, meaning), signature-to-record binding, biometric vs non-biometric controls, policy creation, and user certification requirements. Use when a computerized system requires legally binding electronic signatures for GxP records, when replacing wet-ink signatures in regulated workflows, when implementing batch release or document approval workflows, or when a regulatory gap reveals missing signature controls.

implement-diffusion-network

9
from pjt222/agent-almanac

Implement a generative diffusion model (DDPM or score-based) with noise scheduling, U-Net architecture, training loop, and sampling procedures including DDIM acceleration. Use when building a generative model for image, audio, or molecular synthesis; implementing DDPM from a research paper; adding a custom noise schedule or conditioning mechanism; replacing a GAN-based generator with a diffusion alternative; or prototyping before scaling with production frameworks like diffusers.

implement-a2a-server

9
from pjt222/agent-almanac

Implement a JSON-RPC 2.0 A2A server with full task lifecycle management (submitted/working/completed/failed/canceled/input-required), SSE streaming, and push notifications. Use when implementing an agent that participates in multi-agent A2A workflows, building a backend for an Agent Card, adding A2A protocol support to an existing agent or service, or deploying an agent that must interoperate with other A2A-compliant agents.

conduct-gxp-audit

9
from pjt222/agent-almanac

Conduct a GxP audit of computerized systems and processes. Covers audit planning, opening meetings, evidence collection, finding classification (critical/major/minor), CAPA generation, closing meetings, report writing, and follow-up verification. Use for scheduled internal audits, supplier qualification audits, pre-inspection readiness assessments, for-cause audits triggered by deviations or data integrity concerns, or periodic compliance posture reviews of validated systems.

audit-icon-pipeline

9
from pjt222/agent-almanac

Detect missing glyphs, icons, and HD variants by comparing registries against glyph mapping files, icon directories, and manifests. Reports gaps for skills, agents, and teams across all palettes.

audit-discovery-symlinks

9
from pjt222/agent-almanac

Audit and repair Claude Code discovery symlinks for skills, agents, and teams. Compares registries against .claude/ directories at project and global levels, detects missing, broken, and extraneous symlinks, distinguishes almanac content from external projects, and optionally repairs gaps. Use after adding new skills or agents, after a repository rename or move, when slash commands stop working, or as a periodic health check.

audit-dependency-versions

9
from pjt222/agent-almanac

Audit project dependencies for version staleness, security vulnerabilities, and compatibility issues. Covers lock file analysis, upgrade path planning, and breaking change assessment. Use before a release to ensure dependencies are current and secure, during periodic maintenance reviews, after receiving a security advisory, when upgrading to a new language version, before submitting to CRAN or npm, or when inheriting a project to assess its dependency health.

assess-trail-conditions

9
from pjt222/agent-almanac

Evaluate current trail conditions including weather, snow line, river crossings, exposure, and trail maintenance status for safety decision-making. Produces a GREEN/YELLOW/RED safety rating with actionable go/no-go recommendations. Use the day before or morning of a planned hike, during tour planning to assess seasonal viability, after unexpected weather changes on a multi-day tour, when reports suggest trail damage or closures, or before committing to an alpine or exposed route.

skill-name-here

9
from pjt222/agent-almanac

One to three sentences describing what this skill accomplishes, followed by key activation triggers. This field is the primary mechanism agents use to decide whether to activate the skill — it is read during discovery before the full body is loaded. Start with a verb. Include the most important "when to use" conditions inline. Max 1024 characters.