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.