getaca_policy() and getaca_progress()
return what their option held before the call, invisibly, so a caller
that has to change one for the duration of a function can restore it
from on.exit(). Querying is unchanged: with no argument
each still reports what is in effect.src/ed25519.c
derives its Curve25519 arithmetic, are listed in Authors@R
as contributors. The provenance and what the derived work adds stay in
inst/COPYRIGHTS.keep = TRUE still writes it
to the cache, where a later getaca() call finds it.registry_draft(local = ) hashes a copy already on your
machine and transfers nothing. The record names the location, since that
is where users fetch from; where you measured leaves no trace.registry_draft(sha256 = ) declares a checksum you
already hold and retrieves nothing at all.try_one() gained a streaming shape,
dest = NULL, which hashes a response as it arrives and
reports the digest and the byte count in place of a path.registry_draft() takes locations and returns a
registry() with every checksum filled in. It retrieves each
file once and hashes it locally, which is the part of authoring that
cannot be done by hand.https:// URL or an identifier for
a data archive, and which one it is is read off the string. Zenodo,
figshare and Dataverse are covered, along with their landing-page URLs.
Zenodo and figshare register their own DOI prefixes and are recognised
without a lookup; a bare Dataverse DOI is resolved through
doi.org, since instances are self-hosted.keep = TRUE leaves the
drafted bytes in the cache instead of transferring them again on the
first getaca() call.https://
locations.jsonlite moves no closer than Suggests,
where it already was, and is needed only for the archive handlers.auth_host() binding a bearer() or
basic() scheme to one host. Both name environment
variables: nothing secret enters a registry, a manifest, a digest, a
provenance record or an error message. The credential is read at the
moment of the request and never stored.Authorization header, which libcurl
withholds from a redirect to another host, and to the declared host
only. Hosts match exactly, so a declaration cannot widen where a
credential is sent, and credentials are read from the registry the
package ships, never from a remote one, for the reason signing
keys are.getaca_error_credentials is raised when every mirror
that failed answered 401 or 403, naming the variable wanted, whether it
is set, and where to register. Those failures previously produced
getaca_error_unavailable, whose advice is to connect to a
network. Where a host refuses and the declaration names no credential
for it, there is no variable to set and the condition’s
actor is "author" instead.getaca_credentials() reports every variable a package’s
declaration reads and whether it is set, without touching the
network.resource(doi = ) records the DOI for an artefact. It
renders in the manifest, so a signature covers it, and travels into
provenance: getaca_info() shows it and
getaca_catalogue() gains a doi column. It
routes nothing; locations stay in urls. A
https://doi.org/ or doi: prefix is accepted
and stripped.human_bytes() chooses the unit and the decimal on the
value as it will be rounded rather than as it arrives.
sprintf rounds afterwards, so 9.99 kB rendered
10.0 kB against the 10 kB that follows it, a
two-column step, and 999.6 kB rendered 1000 kB rather than
the megabyte it rounds to.00:00 takes and two short
of 4:37:05, so a transfer long enough to want the hours
form lost its estimate entirely rather than its bar width.REGISTRY_SCHEMA goes to 4, since an older getaca would
fetch an authenticated host with no credential and report the refusal as
an outage. MANIFEST_FORMAT stays at 1: both new fields
render nothing when absent, so every digest already recorded still
identifies the state that produced it.dev_notes/adr-010-authenticated-transport.md for
why authentication was taken up as transport and DOI resolution was not
taken up as a resolver, and
dev_notes/adr-011-drafting-registries.md for the archives
being read at authoring time instead.unpack() is a stock processor() for the
transformation nearly every declaration of an archive was writing by
hand. Attach it with processor = unpack() and
getaca() returns the unpacked directory;
processed = FALSE still returns the archive it was built
from.format = "auto" reads the format from the cached file’s
name and covers .zip, the tarballs under any compression
(.tar, .tar.gz, .tgz,
.tar.bz2, .tbz2, .tar.xz,
.txz) and a single compressed file (.gz,
.bz2, .xz). A compressed file is written under
its own name with the extension dropped, so
backbone-2026-06.csv.gz unpacks to
backbone-2026-06.csv. Name the format for a file whose name
does not carry one: unpack("gzip").unpack(members = "tables") takes one subtree of a large
archive. A member names a file, or a directory and everything under it.
A member matching nothing in the archive is an error: getaca hashes the
archive and never reads what came out of it, so an empty processed slot
would pass every later check.unpack() is
"unpack", unpack("zip") is
"unpack-zip", and naming members appends a
digest of them, so two records asking for different subsets of one
archive cannot resolve to one slot.utils::unzip(),
utils::untar() and the
gzfile()/bzfile()/xzfile()
connections are all base R, so Imports is still
curl plus three base packages.dev_notes/adr-009-stock-processors.md, which also
records what was left out of the equivalent Python API and why.getaca_progress() chooses what a transfer looks like:
"bar" redraws a line with the share, the rate and what is
left, "line" writes one line to start and one to finish for
a log, "none" says nothing, and "auto" picks
between the first and the third by whether the session is interactive.
Also readable from getaca.progress and
GETACA_PROGRESS, and quiet = TRUE on a single
getaca() call still overrides all of it.reporter() builds your own out of a function of one
argument, so a declaring package can report a download in its own voice,
or into a Shiny session, or as a row in a log. No dependency:
Imports is still curl plus three base
packages.yourpkg/backbone@2026-09 (part 2 of 3) for a series, the
size the declaration states rather than whatever content length the
mirror sent, which mirror is being tried, and how much was already on
disk when an interrupted transfer resumed. The share a bar shows is
therefore right before the first byte arrives and stays right for a host
that sends no content length at all.curl::multi_download(), which is what
makes the byte counts reachable at all: multi_download()
sets noprogress on the handle after the caller’s options,
so a progress callback attached to it never runs. See
dev_notes/adr-008-own-transfer-loop.md.identity encoding. A
range request counts bytes of the decoded stream and a compressing
server counts encoded ones, and libcurl reports the combination as an
error rather than as bytes: against
raw.githubusercontent.com a resumed request failed with
curl_error_bad_content_encoding and transferred nothing.
Every resume from a compressing mirror was silently starting the
download over.HTTP 416 is distinguished from other refusals. It says
the offset asked for is past the end of the file the server holds, so
the partial disagrees with upstream and is dropped; any other refusal
leaves the bytes an earlier attempt did get.new_handle_for() never set the URL or reached the transfer
path at all; the multi interface takes both from the handle.part() and combiner(): a record names
either locations for the whole file or the ordered series it is composed
from, and sha256 describes the artefact either way. A host
that caps file size, and a publisher issuing deltas against a base
release, both produce a resource that arrives as a series.combiner(), which is what a delta format needs. The
composed result is held to the record’s own checksum before anything
sees it, so a combiner cannot produce bytes the declaration did not
already name. That is also why assert_immutable() compares
only that checksum: re-splitting a series or moving a piece to another
host is a change of route, not of identity.getaca_error_composition: every part arrived and
matched its own checksum, and combining them produced something else.
Nothing failed in transit, so the actor is the author rather than the
network.resource(file = ): the name the artefact is cached
under, for a URL with no useful basename and for composed records, where
each URL names a piece rather than the result.getaca_catalogue() gains a parts column,
0 where a version is served whole, so what an update costs
to fetch is visible before it is fetched.part, combiner and file
render nothing when absent, so every registry digest recorded before
they existed still identifies the state that produced it. The manifest
format is unchanged.First release.
curl (>= 5.0.0),
with stats, tools and utils from
base R. Zero non-base transitive dependencies, and no
LinkingTo. The floor on curl is
multi_download(), which is what makes a mirror attempt
resumable.src/sha256.c, covering
both the artefact on disk and the manifest in memory. On x86-64 with the
SHA extensions and on ARMv8 with the SHA-256 extensions the block
compression runs in hardware, which on an i9-14900K hashes at 1.43 GB/s
against 0.20 GB/s for tools::sha256sum(): a 4 GB resource
is verified in 2.8 s rather than 20 s. Machines without either extension
use a portable path that is no slower than what R provides. A digest is
a single specified value, so this changes only the time a verification
takes; registry digests already recorded stay valid.resource(): immutable record of exact bytes, with
version, mirrors, SHA-256, size, license, upstream identity and optional
processor.registry(), registry_write(),
registry_read(), registry_for():
package-scoped declarations, discovered by convention at
inst/getaca/registry.rds.registry(current = ): names the channel head, the
version a bare request for each resource resolves to. Required for any
name declaring more than one version, since version strings are labels
and declaration order is not an ordering. A registry that offers a
choice and names no head is refused.registry_digest(): a registry state is identified by a
digest of its own declaration, as "sha256:3f9ac2...". There
is no revision number to keep in step, so identity cannot be typed wrong
and two states that differ cannot claim to be the same one.registry_manifest(): the canonical text the digest is
taken over, exported so a digest is never a black box. Two registries
that disagree are diffable on the lines that produced the disagreement.
Hashing the object itself is not an option, since a
processor() closure digests differently on every
machine.registry_write() stamps created, which
orders two states in time. Deliberately outside the digest, so
republishing an unchanged registry leaves its identity alone; pass a
fixed value to keep a build byte-reproducible.as_registry(): YAML and JSON accepted as authoring
formats, gated at call time so neither becomes a hard dependency.processor(): post-verification transformation with a
stable id, so the derived result gets its own cache slot and
provenance.getaca_refresh(): forget cached registry state, so a
reinstalled declaring package or an updated remote registry is picked up
without restarting the session.getaca_policy(): bundled,
current, pinned and offline
policies. Resolution collapses to offline under
R CMD check.resolve_resource() reports the policy in force, and
getaca() reads it, so policy = "offline" on a
single call keeps that call off the network whatever the session is set
to.getaca_pin(): freeze current resolution into a local
snapshot.registry(keys = ) declares the Ed25519 public keys
allowed to sign a package’s remote registry, and
registry_keygen(), registry_sign() and
registry_verify() are the author-side workflow. Declaring a
key makes a signature mandatory under the current policy;
declaring none leaves resolution exactly as it was, and no signature is
ever fetched.registry_manifest(), plus the
publication time and an expiry. Binding the time is what stops an old
genuine declaration being replayed indefinitely; a state older than the
installed one is refused as a rollback.getaca_error_signature instead, including when the
registry arrives and its signature does not.src/ed25519.c and src/sha512.c, checked
against RFC 8032 and FIPS 180-4. No new dependency: Imports
remains curl plus three base packages.getaca(): the single retrieval verb. Returns a local
path.getaca_error_incomplete, whose action is to retry; mixed
causes keep getaca_error_unavailable and list each mirror’s
reason.getaca.verify_days.verified_at forward for a check
that never ran would put the entry beyond re-verification for good,
since every later access would do the same.verified_at from every other slot naming that digest, so
each re-hashes against its own copy on next access. Previously a second
package holding the same corrupt bytes kept passing the cheap size check
and kept being handed them as verified for the remainder of its own
window, up to 90 days. A slot’s own copy and a processed tree indict
nothing but themselves.getaca_error_redeclared naming both
checksums, rather than resolving quietly to the copy already held.
verify = TRUE asks the same question, so a forced re-hash
can no longer confirm bytes against a declaration they no longer
match.fetched_at,
verified_at, checked_at and
accessed_at apart.registry_digest), when that state was published
(registry_created) and which getaca acted on it
(getaca_version).getaca_available(), getaca_optional(),
getaca_skip_if_unavailable().getaca_prefetch() and the GETACA_CACHE
environment variable for seeding CI and check runs.blobs/sha256/, and a version slot holds a name for them.
Two packages declaring the same file keep one copy and separate
dependency records. The store holds no metadata: what is still needed is
derived from the package indexes rather than counted beside them.processor(), which gets its own slot.getaca_clean() and automatic collection after
retrieval: broken material, abandoned transfers, superseded versions
past retention, LRU eviction only above the size ceiling, and finally
bytes no declaration references.getaca_keep() to exempt a resource.getaca_catalogue(): one table covering what packages
declare and what the cache holds, including declared resources never
downloaded and cached versions no longer declared. A
current column marks the version a bare request resolves
to.getaca_info(): full provenance for one cached
resource.