feat(freeze): crafter-review freeze, and --verify that names a git upgrade

Freeze: identity, AI trailers by the consumer's rule (the pattern with
case-insensitive folds and its \s* that crosses lines), a clean tree, no
merges, a fast-forward, then format-patch, the hash, the dispositions
register and the optional detached worktree; --verify at push time. The
command line takes --opt=value, unique prefixes and the last of a repeat,
with a usage error at exit 3 like every command that cannot start; the
config is loaded and its warnings and problems reported first. The run
line it prints names this binary.

freeze.json records gitVersion. format-patch ends every patch with the
git version, so a fresh export under another git hashes differently;
--verify's MISMATCH says which git exported the artifact and which the
fresh copy, reading the patches' own signature when freeze.json does not
record it. freeze becomes a subreaper and takes down whatever gpg left
running for %G?.

tests/Freeze: 18 checks, plus the gitVersion record and both MISMATCH
messages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jorijn van der Graaf 2026-10-31 07:03:48 +01:00
commit 61acd6a72e
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
6 changed files with 943 additions and 13 deletions

View file

@ -13,7 +13,7 @@ using namespace Crafter;
namespace {
// The umbrella, then the partitions in import order: each after the ones
// it imports, which is also the order the module map in AGENTS.md uses.
constexpr std::array<std::string_view, 21> Interfaces = {
constexpr std::array<std::string_view, 22> Interfaces = {
"interfaces/Crafter.Review",
"interfaces/Crafter.Review-Unicode",
"interfaces/Crafter.Review-Json",
@ -34,9 +34,10 @@ namespace {
"interfaces/Crafter.Review-Stream",
"interfaces/Crafter.Review-Prompt",
"interfaces/Crafter.Review-Config",
"interfaces/Crafter.Review-Freeze",
"interfaces/Crafter.Review-Driver",
};
constexpr std::array<std::string_view, 18> Implementations = {
constexpr std::array<std::string_view, 19> Implementations = {
"implementations/Crafter.Review-Text",
"implementations/Crafter.Review-Crypto",
"implementations/Crafter.Review-Platform",
@ -54,6 +55,7 @@ namespace {
"implementations/Crafter.Review-Stream",
"implementations/Crafter.Review-Prompt",
"implementations/Crafter.Review-Config",
"implementations/Crafter.Review-Freeze",
"implementations/Crafter.Review-Driver",
};
@ -64,7 +66,7 @@ namespace {
// One per directory under tests/, each an ordinary main() that links the
// library: exit 0 passes, nonzero fails, 77 skips.
constexpr std::array<std::string_view, 12> Tests = {
constexpr std::array<std::string_view, 13> Tests = {
"Crypto",
"Text",
"Platform",
@ -77,6 +79,7 @@ namespace {
"Config",
"CommitsMode",
"CodeKeys",
"Freeze",
};
// The version the binary reports: CI sets it from the tag, a checkout