Runs the checks of the rmbl GitHub Action on every capsule (a folder
holding data_provenance.json or capsule_bundle.json) under
path: each capsule file against its JSON Schema
(schemapath), then vrfcps and
cpsbndlvrf. With mode = "drift" or
"refresh" each source is fetched again and compared with the pin,
column by column (cpsdrft and benfordtst);
"refresh" then writes and pins new bytes. rmbl verify,
drift and report call it. format() renders the
Markdown report or the JSON, and ghann,
ghcmt and ghout the GitHub pieces.
Usage
cpschk(
path = ".",
mode = c("verify", "drift", "refresh", "report"),
key = "",
pubkey = "",
psiwarn = 0.1,
psifail = 0.25,
base = NULL,
cache = NULL,
allowfile = FALSE,
snapshot = FALSE,
wayback = FALSE,
glob = "",
fp = TRUE,
seenlog = "",
archive = FALSE
)
# S3 method for class 'rmbl_cpschk'
format(x, fmt = c("md", "json", "text"), failon = NULL, ...)
# S3 method for class 'rmbl_cpschk'
print(x, ...)Arguments
- path
Character scalar, the folder searched recursively (
.gitand.rmbl-cachefolders skipped).- mode
"verify"(the default),"drift","refresh"or"report"(the verify checks).- key
Character scalar: the shared secret of HMAC bundles, or
"".- pubkey
Character scalar: the public key (hex) every bundle must be signed with (for an HMAC bundle, its key id), or
"". Given, a capsule with no signed bundle fails: there is nothing the key checks.- psiwarn, psifail
Non-negative numbers,
psiwarnat mostpsifail: the PSI bands, by default 0.1 and 0.25, the bandsdrftpsidocuments.- base
NULL(no comparison); the path of one manifest file each manifest is compared with; a folder holding the base copy of the tree underpath(the same relative paths); or a function that takes a manifest's path and returns the path of its base copy, orNULLwhen the base has none (for a git ref, write whatgit showprints to a file). Its"label"attribute names it in the report.- cache
NULL(the default: the user cache folder,tools::R_user_dir("rmoriebricklayer", "cache"), in an interactive session, none otherwise),FALSE(none) or a folder.- allowfile
FALSE(the default),TRUEto acceptfile://sources and local files anywhere underpath, or an existing folder whose files may be sources as well. Without it a local source must be a file inside its own capsule; a fetched one ishttps://only (seecpsrfrsh).- snapshot
Logical: look up the latest Wayback snapshot of each http(s) source, a
snapshotrow (a note).- wayback
Logical: with
"refresh", capture each changed http(s) source in the Wayback Machine first (archive.org keys, aswbsave()reads them).- glob
Character scalar: check only the capsule files whose path matches this glob (
*also matches/), or"".- fp
Logical: measure the footprint of the checks (
co2fp) for the last lines of the report.- seenlog
Character scalar: an XMSS seen-log (
xmssidxseen;rmbl verify --seen-log) read with the bundles underpath, or"". Every XMSS bundle gets anxmss_indexrow, failed when the same key signed its index with another signature in what was seen.- archive
Logical: use archive.rmorie.com when a source cannot be fetched. In
"drift"and"refresh", a source that neither answers nor has its pinned Wayback snapshot is fetched from a copy kept there with the pinned SHA-256 (public copies need no key); withwayback = TRUE, a changed source the Wayback Machine refuses is saved there instead (wbsave(fallback = "rmorie"), which needs a key:dlogin()orMORIE_HOSTED_KEY).- x
An
rmbl_cpschkresult.- fmt
"md"(the Markdown report),"json"or"text"(the console summaryprint()draws as a box).- failon
Character scalar or
NULL: the fail-on level, named in the report's last line.- ...
Value
A list of class rmbl_cpschk: status,
mode, path, glob, rows (a data frame of
every check: capsule, part, file, check,
ok, detail, severity and line, the line
of the file where known), capsules (a list per capsule:
capsule, source, pinned_sha256,
fetched_sha256, changed, result,
checks_passed, checks_total and notes, and where
made drift with its columns table,
drift_summary, snapshot, snapshot_utc and
manifests), base (its label), psiwarn,
psifail and footprint (the co2fp() result
without its value, or NULL). The cpsdrft() result
of each capsule whose columns were compared, with its provenance and
manifest diff, is in the attribute drifts (named by capsule),
for drfthtml and drftapp.
format() returns one string ("md", "json") or
the lines of the console summary ("text"); print()
draws that summary as a box.
Details
Each check is one row with a severity: error, drift,
warn or note (a note never changes a result). A capsule's
result is the worst severity among its failed rows, and status the
worst over all capsules: ok, warn, drift or
error. Each failed row adds a note, and one more from
explerr where it finds what the row means or where its
message is raised.
Drift: when the fetched source's SHA-256 differs from the pin and the
pinned data file is a CSV, cpsdrft() runs with
psithr = psifail. A column is drift when cpsdrft()
flags it and its PSI is above psifail, and warn when it is
flagged below that or its PSI is above psiwarn; an added or
removed column is drift. New bytes whose columns all stay within the
bands are a note; new bytes that cannot be compared are a warning.
Refresh: the new bytes are written as the data file and pinned in the
provenance as cpsrfrsh does (refused when they fail the
schema the provenance pins, valsch); with
wayback = TRUE an http(s) source is first captured with
wbsave. The capsule is then verified again.
Manifests: with base, each capsule's manifest.json and
results/manifest.json is compared with its base copy: the base's
recorded results against this copy's observed values, each within the
base's tolerance (mnfrcmp), and the resources of the two
(mnfdiff). A manifest only one side has is added or
removed.
Cache: a fetched source is kept in the cache folder under
source- and the first 16 hex digits of the SHA-256 of its URL, one
file per source overwritten by each fetch, and is read only when the live
fetch fails.
Side effects: "drift" and "refresh" fetch each source
(the network for http(s) ones), as snapshot = TRUE asks the
Wayback Machine once per capsule (wbsnpurl);
"refresh" writes the capsules; a cache folder is written to.
Errors: path is not a directory; a bad argument. A capsule whose
checks stop is one failed row, not an error.
Examples
root <- tempfile("capsules-")
d <- usecpstmpl(file.path(root, "one"), ex = TRUE)
invisible(cpsrfrsh(d))
x <- cpschk(root, fp = FALSE)
x$status
#> [1] "ok"
x$rows[, c("check", "ok")]
#> check ok
#> 1 schema: data_provenance.json TRUE
#> 2 provenance_readable TRUE
#> 3 data_present TRUE
#> 4 data_sha256 TRUE
#> 5 data_size_bytes TRUE
#> 6 data_readable TRUE
#> 7 data_not_synthetic TRUE
x
#> ╭── Capsule check (verify) ───────────────────────────────────────────────────╮
#> │ ✔ VERIFIED · 7 of 7 checks passed │
#> │ │
#> │ Status ok │
#> │ Path /tmp/RtmpZpRdcE/capsules-1e6a30fc0865 │
#> │ Capsules 1 │
#> │ Checks 7 of 7 passed │
#> │ Checked rmoriebricklayer 1.0.4, R version 4.6.2 (2026-10-29) │
#> │ │
#> │ capsule result passed checks source │
#> │ ------- ------ ------ ------ ---------------------------------------- │
#> │ one ok 7 7 /tmp/RtmpZpRdcE/temp_libpath1e6a373ae... │
#> │ │
#> │ capsule sha256 size rows bounds │
#> │ ------- ------------ ------ ---- ------ │
#> │ one 6b1a0d2d11f6 0.5 KB - - │
#> │ │
#> │ Every capsule is still the one its analysis was run on. │
#> │ │
#> │ /tmp/RtmpZpRdcE/capsules-1e6a30fc0865/one: ok │
#> │ source: │
#> │ /tmp/RtmpZpRdcE/temp_libpath1e6a373ae17b/rmoriebricklayer/extdata/otis_ │
#> │ a01_individuals.csv │
#> │ pinned SHA-256: │
#> │ 6b1a0d2d11f62eaffe4a95111286753aca9afc015fc4d0ea07c9953ff3030248 │
#> │ ✔ schema: data_provenance.json valid against capsule.schema.json │
#> │ ✔ provenance_readable │
#> │ /tmp/RtmpZpRdcE/capsules-1e6a30fc0865/one/data_provenance.json │
#> │ ✔ data_present │
#> │ /tmp/RtmpZpRdcE/capsules-1e6a30fc0865/one/otis_a01_individuals.csv │
#> │ ✔ data_sha256 │
#> │ 6b1a0d2d11f62eaffe4a95111286753aca9afc015fc4d0ea07c9953ff3030248 │
#> │ ✔ data_size_bytes 462 bytes on disk │
#> │ ✔ data_readable │
#> │ /tmp/RtmpZpRdcE/capsules-1e6a30fc0865/one/otis_a01_individuals.csv │
#> │ ✔ data_not_synthetic no synthetic marker: sidecar, manifest and │
#> │ provenance all say real data │
#> │ format(x) gives the full Markdown report. │
#> ╰─────────────────────────────────────────────────────────────────────────────╯
# the data changed: the digest row fails, and the notes say what it means
p <- loadprov(file.path(d, "data_provenance.json"))
cat("1,2\n", file = file.path(d, p$resource$filename), append = TRUE)
x <- cpschk(root, fp = FALSE)
x$status
#> [1] "error"
x$capsules[[1]]$notes
#> [[1]]
#> [1] "data_sha256: expected 6b1a0d2d11f62eaffe4a95111286753aca9afc015fc4d0ea07c9953ff3030248, got e0061f4de8d524e9a8a421b502ae878a4de372a841e6bdf3ac3a5140b86682c8"
#>
#> [[2]]
#> [1] "explained: the data file's bytes differ from the pinned SHA-256: restore the file, or pin the new bytes with cpsrfrsh() (rmbl refresh)"
#>
#> [[3]]
#> [1] "data_size_bytes: 466 bytes on disk"
#>
#> [[4]]
#> [1] "explained: the data file's size differs from resource$size_bytes: its bytes changed"
#>
substr(format(x, fmt = "json"), 1, 120)
#> [1] "{\n \"status\": \"error\",\n \"mode\": \"verify\",\n \"path\": \"/tmp/RtmpZpRdcE/capsules-1e6a30fc0865\",\n \"capsules\": [\n {\n "
# drift: fetch the source again (here a local file) and compare
cpschk(root, "drift", psiwarn = 0.05, fp = FALSE)$status
#> [1] "error"
try(cpschk(tempfile()))
#> Error : cpschk: `path` is not an existing directory: /tmp/RtmpZpRdcE/file1e6a322ce7dd
try(cpschk(root, psiwarn = 1))
#> Error : cpschk: `psiwarn` (1) must not exceed `psifail` (0.25)
