Provenance and drift checking for the checked-in generator baselines.
Every function is pure over a directory — nothing here shells out to a generator, so the drift check runs offline on every commit and 100% coverage never depends on having phx_new installed.
This module must NOT share a normaliser with Capstone.Hash. They require
opposite semantics: the manifest hash ignores an added comment (a
# credo:disable-for-next-line must not lock a project out of updates),
while a comment added to a baseline IS drift.
Summary
Functions
Names an archive <type>_<version>_<last 8 of sha>.tar.gz.
A single digest over the whole tree.
Selects one baseline's archive members.
Replaces phx.new's four crypto:strong_rand_bytes values with placeholders.
Reads the provenance record.
Recomputes every entry's derived fields from disk.
Builds one baseline's self-verifying snapshot.
Builds reproducible, uncompressed tar bytes from in-memory members.
Maps every relative path under dir to the sha256 of its normalised bytes.
Functions
Names an archive <type>_<version>_<last 8 of sha>.tar.gz.
type is the manifest KEY, passed through verbatim rather than checked
against a closed otp | api | web set — priv/baselines.exs already carries
a fourth (:web_layer), and a list here would have to be edited in lockstep
with that file, where forgetting yields a mislabelled archive rather than an
error.
version is an argument and never read from anywhere here, so this module
stays a pure function of what it is given — which is also what keeps it clear
of the ambient-state ban Capstone.BoundaryGuard enforces.
Mix.Tasks.Capstone.Baseline.Record supplies it, from the .version file that is
the project's source of truth. That is the version the release tag names, so
an archive and the release it is attached to agree by construction.
Only the last 8 characters of the sha: the full digest is recorded as
archive_sha256 in the manifest and again inside the archive itself, so the
filename only has to be short and distinguishing. What the prefix adds is
legibility — a directory of release assets says which baseline and which
release each file belongs to without opening any of them.
A single digest over the whole tree.
Selects one baseline's archive members.
Contents come from disk but membership comes from the entry's files map,
never from a fresh wildcard — that is what makes it structurally impossible
for the archive and the manifest to disagree about the baseline.
Names are project-relative, identical to the files keys, so each archive
unpacks as a standalone snapshot of that generated project and a member name
can be looked up in files directly.
Replaces phx.new's four crypto:strong_rand_bytes values with placeholders.
Anchors on the key name, never on a bare length match: the alphabet is
unpadded base64, so values contain + and / and may start with /.
Reads the provenance record.
Not every entry is a generator baseline. derived_from: marks one produced
by applying a plugin — mix capstone.baseline.compose — and such an entry
has no argv:, generator: or generator_version:. A test that drives a
generator must select entries that carry them rather than iterating all.
Recomputes every entry's derived fields from disk.
files and tree_digest are recomputed; every other field is passed through
untouched. generator_version above all must never be sourced from the
running toolchain — it is the pin that makes the toolchain test fail with one
line on a phx_new bump instead of a 45-file hash diff.
:file_count is dropped: files supersedes it and map_size/1 is exact.
archive_sha256 is recorded here rather than in a sidecar file. It is not
circular: the archive holds only the project's files, so the manifest is not
an input to its own recorded hash and one pass converges.
Builds one baseline's self-verifying snapshot.
The .tar.gz is an OUTER tar holding two members: baseline.tar — the
project's files — and baseline.sha256, its checksum. The checksum sits
beside the inner tar rather than inside it, which is the only placement that
terminates: a file recording a tar's hash cannot also be a member of it.
Returns {sha, gzipped} where sha is the hex sha256 of the INNER tar.
Builds reproducible, uncompressed tar bytes from in-memory members.
Members are {name_charlist, contents} and are sorted here rather than by the
caller, so member order cannot vary with map iteration.
Maps every relative path under dir to the sha256 of its normalised bytes.