A package that needs a four-gigabyte reference dataset cannot ship
it, cannot download it during R CMD check, and cannot
afford to fetch a different version on Tuesday than it fetched on
Monday. getaca is the layer that makes those three
constraints compatible.
There is one engine and many declarations. Packages say what they
need; getaca gets it. The declaration is a small R object
that ships inside the installed package, so retrieving a resource
requires no registration call, no load hook, and no service to be
up.
This vignette walks the whole cycle: declaring a resource, retrieving it, behaving during checks, reading its provenance, and knowing where it went. Each section links the companion article that goes deeper.
Declaring what you need
A resource record names exact bytes: one checksum, one version label, and the places those bytes live.
backbone <- resource(
name = "backbone",
version = "2026-06",
urls = c("https://primary.invalid/backbone-2026-06.zip",
"https://mirror.invalid/backbone-2026-06.zip"),
sha256 = strrep("9f", 32),
size = 4.1e9,
license = "CC-BY-4.0"
)
backbone
#> <getaca resource record>
#> name backbone
#> version 2026-06
#> sha256 9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f9f
#> size 4.1e+09
#> license CC-BY-4.0
#> urls https://primary.invalid/backbone-2026-06.zip
#> https://mirror.invalid/backbone-2026-06.zipEach field earns its place. The sha256 is what makes the
record a record rather than a bookmark: a truncated transfer, a proxy
that served an HTML error page, and a publisher who quietly recut the
file all produce a different digest, and all three are caught before the
bytes reach your code. The size is a cheap early check, so
a transfer that ends at 40% fails as a truncation rather than running a
four-gigabyte hash to discover the same thing. Extra urls
are tried in order, which turns one host’s outage into a slower first
call. The license travels into provenance, so a result can
say what terms the data came under.
Records live in a registry, which is scoped to the declaring package:
reg <- registry(package = "yourpkg", resources = list(backbone))
reg
#> <getaca registry> yourpkg (policy "bundled")
#> digest: sha256:d44183e77921
#> - backbone@2026-06 9f9f9f9f9f9f [CC-BY-4.0]Scoping matters more than it looks. Identity here is the triple
package / name / version, so two packages may both declare
something called "backbone" and never collide, in the cache
or anywhere else.
format(resource_id("yourpkg", "backbone", "2026-06"))
#> [1] "yourpkg/backbone@2026-06"Ship the registry where getaca looks for it, and there
is nothing else to wire up:
registry_write(reg, "inst/getaca/registry.rds")Discovery is a file test across the library paths.
getaca asks system.file() for
getaca/registry.rds inside each installed package, which is
why a declaring package needs no .onLoad() and no
dependency on getaca being attached.
See vignette("declaring") for the full authoring guide:
mirrors, derived artefacts, YAML authoring and the pre-ship
checklist.
Getting it
path <- getaca("backbone", package = "yourpkg")That path points to a complete file, verified against the declared
checksum, at the resolved version, in a cache slot getaca
owns. What happens on the way there depends on what is already true:
cached and intact, the path comes back after a size check
cached but past the re-verification interval, the bytes are re-hashed first
absent, the mirrors are tried in order into a temporary file, which is sized and hashed before it is moved into place
An interrupted transfer resumes rather than restarting, and can never be mistaken for a finished resource. The temporary file is named after the declared checksum and the mirror that produced it, so a resumed transfer only ever continues bytes from the same host. Sharing one partial file across mirrors would let a failed attempt at the first be resumed onto by the second, and the resulting corruption is indistinguishable from the publisher having changed the file.
Two sessions asking for the same resource at the same time take a
per-resource directory lock. The second waits, then observes the first
session’s success and returns the same path, so a four-gigabyte download
happens once. vignette("cache") covers the locking protocol
and what happens to a lock whose holder died.
Which version a bare name resolves to
getaca("backbone") asks for a name, and the registry
says which record that name means. When a package offers several
versions, it states the answer:
multi <- registry(
package = "yourpkg",
current = c(backbone = "2026-09"),
resources = list(
resource("backbone", "2026-06",
urls = "https://primary.invalid/backbone-2026-06.zip",
sha256 = strrep("9f", 32), license = "CC-BY-4.0"),
resource("backbone", "2026-09",
urls = "https://primary.invalid/backbone-2026-09.zip",
sha256 = strrep("ab", 32), license = "CC-BY-4.0")
)
)
resolve_resource("backbone", registry = multi)$id
#> <getaca resource> yourpkg/backbone@2026-09The older record stays resolvable by asking for it:
resolve_resource("backbone", registry = multi, version = "2026-06")$id
#> <getaca resource> yourpkg/backbone@2026-06Version strings here are labels rather than semantic versions.
source-2026-06_build-3 is a perfectly good label and has no
defensible ordering, so getaca never tries to rank them. A
registry that declares two versions of a name and states no head is
refused:
registry(
package = "yourpkg",
resources = list(
resource("backbone", "2026-09",
urls = "https://primary.invalid/a",
sha256 = strrep("ab", 32)),
resource("backbone", "2026-03",
urls = "https://primary.invalid/b",
sha256 = strrep("cd", 32))
)
)
#> Error:
#> ! Invalid getaca registry for package 'yourpkg'.
#> - resource 'backbone' declares 2 versions (2026-09, 2026-03) but the registry names no current one; add current = c("backbone" = "2026-03")
#>
#> Fix: the declaring package needs a correction. Report it to its maintainer.The error lands on the author’s machine, at the moment the registry is built, rather than on every user of the package.
Surviving R CMD check
Resolution collapses to offline under check, whatever
policy is set, so a check run never reaches the network. Three helpers
cover the three places that matters, and they answer three different
questions.
In tests, skip cleanly and say what is missing:
test_that("the backbone parses", {
getaca_skip_if_unavailable("backbone", package = "yourpkg")
expect_s3_class(read_backbone(getaca("backbone", package = "yourpkg")), "backbone")
})In examples and vignettes, degrade to a message rather than an error. This vignette is running with the network switched off, so the call below takes exactly the path a CRAN check machine would take:
path <- getaca_optional("backbone", registry = reg)
#> getaca: 'backbone' is not available here, so this output is abbreviated.
#> yourpkg/backbone@2026-06 is not cached and cannot be downloaded (offline mode is in effect).
#>
#> Action: on a connected machine run getaca_prefetch("backbone", package = "yourpkg"),
#> or point GETACA_CACHE at a cache that already holds it.
#>
#> Fix: this is expected during checks. Use getaca_skip_if_unavailable()
#> in tests and getaca_optional() in examples.
is.null(path)
#> [1] TRUEAnd where a plain logical reads better:
getaca_available("backbone", registry = reg)
#> [1] FALSEgetaca_available() never touches the network. It asks
whether a cached copy exists and passes its cheap integrity check, which
is what makes it safe to call in a condition that gates expensive
work.
To prepare a machine that will later be offline, or a CI job that should find everything already present:
getaca_prefetch("backbone", package = "yourpkg")
getaca_prefetch(package = "yourpkg") # everything the package declaresSetting GETACA_CACHE points any session at a cache that
has already been seeded, which is how a CI job restores a cached
directory and finds the resources waiting.
vignette("checks") has the workflow files.
When it does not work
Every failure is classed, and carries an actor field
naming who can act on it. Here is the one a check run produces, in
full:
err <- tryCatch(getaca("backbone", registry = reg), getaca_error = function(e) e)
class(err)
#> [1] "getaca_error_offline" "getaca_error_unavailable"
#> [3] "getaca_error" "error"
#> [5] "condition"
cat(conditionMessage(err))
#> yourpkg/backbone@2026-06 is not cached and cannot be downloaded (offline mode is in effect).
#>
#> Action: on a connected machine run getaca_prefetch("backbone", package = "yourpkg"),
#> or point GETACA_CACHE at a cache that already holds it.
#>
#> Fix: this is expected during checks. Use getaca_skip_if_unavailable()
#> in tests and getaca_optional() in examples.
err$actor
#> [1] "user"getaca_error_offline is a subclass of
getaca_error_unavailable, so a handler for the general case
catches it and a narrower handler can separate the two. A declaring
package usually catches the ones its users will meet and answers in its
own vocabulary:
install_backbone <- function(name = "backbone") {
path <- tryCatch(
getaca(name, package = "yourpkg"),
getaca_error_unavailable = function(e) {
stop("The backbone is not installed and no network is available.\n",
"Connect, then run: yourpkg::install_backbone(\"backbone\")", call. = FALSE)
}
)
open_backbone(path)
}Six conditions cover the failure surface, and they distinguish causes
a plain downloader reports identically.
vignette("failures") works through each one, including the
case where several independent mirrors agree with each other and
disagree with the registry.
Knowing where a file came from
getaca_info("backbone", package = "yourpkg")
#> <getaca cache entry> yourpkg/backbone@2026-06
#> path ~/.cache/R/getaca/yourpkg/backbone/2026-06/raw/backbone-2026-06.zip
#> sha256 9f9f9f...
#> size 4,100,000,000 bytes
#> license CC-BY-4.0
#> resolved by bundled registry sha256:1c4d7a90f2be (published 2026-07-20)
#> source url https://primary.invalid/backbone-2026-06.zip
#> getaca 0.0.0.9000
#> fetched 2026-07-26 11:02:13
#> verified 2026-07-26 11:09:44 (full re-hash)
#> checked 2026-07-26 15:31:02 (size and mtime)The registry digest names the exact declaration that chose these bytes, so “which version of yourpkg’s declaration was this” has an answer years later, without anyone having kept a revision number in step by hand.
Four timestamps, kept apart on purpose. “Verified” means the bytes were re-hashed then. “Checked” means size and modification time were compared against the entry, which is the cheap test run on ordinary access. “Accessed” is the clock the retention sweeps read. Collapsing them into one “last checked” field would make a resource verified in January look verified today because someone opened it this morning.
An uncached resource gives NULL from
getaca_info(), which keeps the call usable in a report
covering a machine that holds some of the set:
is.null(getaca_info("backbone", registry = reg))
#> [1] TRUEgetaca_catalogue() widens that to a data frame covering
both halves: every resource the installed packages declare, and every
copy the cache holds.
getaca_catalogue(registry = multi)[, c("name", "version", "current",
"declared", "cached")]
#> name version current declared cached
#> 1 backbone 2026-06 FALSE TRUE FALSE
#> 2 backbone 2026-09 TRUE TRUE FALSEThe three logical columns answer three different questions.
current marks the version a bare request resolves to.
declared says whether the registry in force names that
version at all. cached says whether a local copy is
recorded. A row with declared = TRUE, cached = FALSE is
work still to do on this machine. A row with
declared = FALSE is a copy of a version nothing asks for
any more, which is what the retention sweeps reclaim first.
Where things are stored
getaca_cache_dir()
#> [1] "<tempdir>/getaca-quickstart"That is the temporary directory this vignette is sandboxed in. The
default is tools::R_user_dir("getaca", "cache"), which is
what CRAN policy permits, on the condition that contents are actively
managed. getaca treats that as a retention policy rather
than a function users might find, and sweeps after every successful
retrieval. The getaca.cache option and the
GETACA_CACHE environment variable override it, in that
order.
The layout is scoped the same way identity is:
<cache>/
.locks/ per-resource locks
.tmp/ in-flight downloads, never visible as cache
<package>/
index.rds provenance for this package only
<name>/<version>/
raw/<file> verified bytes as served
proc-<processor-id>/ processed result, own provenance
Nothing about it is private. It is an ordinary directory tree, which
is what makes GETACA_CACHE and a CI cache key sufficient
for seeding.
To see what a sweep would remove without removing it:
getaca_clean(dry_run = TRUE)
#> [1] package resource reason bytes path
#> <0 rows> (or 0-length row.names)An empty result on a fresh cache. On a working one, each row names
the resource, the reason, and the bytes it would reclaim.
vignette("cache") covers the four sweeps, the clocks they
read, and what is never touched.
Choosing a policy
The registry declares a default, and a session or a single call can override it:
getaca_policy()
#> [1] "offline"
getaca_policy("current") # for this session
getaca("backbone", package = "yourpkg", policy = "bundled") # for this callbundled is the default because a dependency that
resolves differently on different days is not a dependency.
current consults an author-controlled remote registry,
which lets a dead mirror be repaired without a CRAN release.
pinned resolves through a frozen local snapshot, so an
analysis keeps resolving what it was written against.
offline never reaches for the network at all.
getaca_pin(c("yourpkg", "otherpkg")) # writes getaca.pins.rds in the projectvignette("policies") covers what a remote channel is
allowed to change, why redefining a published version is refused, and
how pinning interacts with renv.
Where to go next
vignette("declaring")for package authors: what to declare, where to put it, and the checklist before shippingvignette("policies")for channels, remote registries and pinningvignette("checks")forR CMD check, CI workflows and seeding a cachevignette("cache")for the layout, verification schedule, locking and retentionvignette("failures")for the eleven conditions and how to handle eachvignette("alternatives")for choosing betweengetaca, a companion data package, and the neighbouring tools