Build1 publisher3 min readPublished
A release workflow hides the upgrade contract from the model that drafts the changelog
A dev.to workflow compiles commits and path churn into release_facts.json for a model to summarise, and leaves every compatibility promise to a reviewer whose unfilled row is supposed to stop the release.
The Engineer · Build desk

What happened
- The workflow splits release communication into a facts file compiled from git history and public symbols, and a second file in which a reviewer owns every upgrade claim.
- Drafted prose may summarise the facts file only, and any sentence implying compatibility, data preservation, or a supported command must already exist in the reviewer contract.
- In the gate table, rows marked draft may come from a script or a model, while rows marked own stay empty until a reviewer writes them.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint Release prose stops being where migration knowledge gets created: a model can list commits, paths and symbols, but the sentence a user follows during an upgrade has to come from somewhere else.
- decision Every tag now needs a named person who can run the migration commands, and the release waits on that person rather than on the draft.
- exposure When an unverified migration sentence ships, support and the user whose upgrade broke are the ones who absorb it.
- cost Adopting this costs two extra artifacts per tag plus a gate you write yourself, and the post offers no field report to price that against.
Step one of the workflow refuses to draft against an untagged working tree [13], and the compile script enforces that with one call. compile_facts asks git for the tag using git describe --exact-match --tags [22], and the helper wrapping every git call passes check=True [21]. A commit with no exact tag makes that call fail, the helper raises, and release_facts.json is never written. The example invocation is python compile_release_facts.py v1.4.0 HEAD [26].
The rest of the compiler stays dumb on purpose. It takes hashes and subjects from git log, and added and deleted counts from git diff --numstat, storing None where git printed "-" instead of a number [23]. Unknown churn and zero churn are therefore different values in the file. commit_count is the number of lines git log returned, while the commits list skips empty rows [27].
Step three leaves upgrade_contract.yml empty except for the tag name until a reviewer fills the owned rows [15]. An empty YAML file is a cheap way to find out whether anybody owns the upgrade. If a cell stays blank, the changelog gate is meant to fail closed [11]. The post says silence does not amount to a signed statement that nothing breaks [12].
Ordering carries more weight here than file format. The model sees the facts file in step four while the contract is still invisible to it [16], because, the post says, models that see owned claims first copy warranty tone into every paragraph [19]. Reviewer sentences get spliced in at the merge step, and the promise-verb gate runs before merge [17]. "Keep the draft step facts-only, then splice reviewer sentences in a later, boring function," the post says [20]. Of the six listed steps, the model writes in one, and that one is optional [28].
Why an inventory cannot carry an upgrade claim is the part that generalises beyond this workflow. Commit subjects describe work that landed, not work users can rely on after bumping a version, and path churn never states whether a renamed flag still accepts the old spelling [5]. A model trained to sound helpful fills that silence with migration language nobody reproduced [6]. Teams paste the draft into CHANGELOG.md, and the support queue becomes the first test of the upgrade path [8].
The script's docstring reads "Compile release_facts.json. Proposal: invoke at a tagged commit." [24] It does not classify breaking changes and does not emit migration commands [25]. Nobody in the post has shipped a release through the two-file split [30]. In my view the split only pays if the reviewer fills the owned rows by running the migration commands, which is the job the post gives the contract's owner [9]. Rows filled in by rereading the same commit subjects the model summarised would leave the same unverified claim in CHANGELOG.md, with a name beside it.
What to watch
- Whether the promise-verb gate that runs before merge is published as runnable code alongside the facts compiler.
- Whether any team reports a release shipped through the two-file split, with owned rows filled by someone who ran the migration commands.
- Whether a facts file and contract stored beside a tag get cited in a post-incident audit of a bad upgrade.