12 KiB
Release Procedure
Release Model And Status
Scriptorium publishes annotated semantic tags and tag-triggered Linux binary
releases. The hosted
release workflow builds amd64 and arm64
executables, publishes their SHA-256 checksums, and uses the matching file
under docs/releases/ as the hosted release body.
v0.12.0 is the first published application-only release. For each later
release, select a new vMAJOR.MINOR.PATCH version according to the intended
compatibility change. A selected version remains an unreleased candidate until
its annotated tag is published, the hosted workflow succeeds, and every
published artifact is verified.
Run this procedure from the Scriptorium repository root. A release must not depend on a Go workspace, module replacement, vendor tree, sibling checkout, unpublished dependency, or unpushed source commit.
Establish The Candidate
Start a POSIX shell, choose a semantic version that has not been published, and
export it as RELEASE_VERSION. For example, if v0.12.1 is the intended next
version and remains unpublished, select:
export RELEASE_VERSION=v0.12.1
Use the version appropriate to the actual compatibility change rather than assuming that the example is the next release. Then run the following guard in that same shell:
set -eu
: "${RELEASE_VERSION:?export an unpublished vMAJOR.MINOR.PATCH version}"
if ! printf '%s\n' "$RELEASE_VERSION" |
grep -Eq '^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$'
then
printf '%s\n' "invalid release version: $RELEASE_VERSION" >&2
exit 1
fi
RELEASE_COMMIT=$(git rev-parse --verify 'HEAD^{commit}')
export RELEASE_COMMIT
check_release_candidate() {
test "$(git branch --show-current)" = main
test -z "$(git status --porcelain)"
gowork_value=$(go env GOWORK)
case "$gowork_value" in
''|off) ;;
*)
printf '%s\n' "active Go workspace: $gowork_value" >&2
return 1
;;
esac
test -z "$(git ls-files go.work go.work.sum)"
test ! -e vendor
if grep -Eq '^[[:space:]]*replace([[:space:]]|\()' go.mod
then
printf '%s\n' 'go.mod contains a replacement' >&2
return 1
fi
git fetch origin main --tags
test "$RELEASE_COMMIT" = \
"$(git rev-parse --verify 'refs/remotes/origin/main^{commit}')"
if git show-ref --verify --quiet "refs/tags/$RELEASE_VERSION"
then
printf '%s\n' "local tag already exists: $RELEASE_VERSION" >&2
return 1
fi
if test -n "$(
git ls-remote --tags origin \
"refs/tags/$RELEASE_VERSION" \
"refs/tags/$RELEASE_VERSION^{}"
)"
then
printf '%s\n' "remote tag already exists: $RELEASE_VERSION" >&2
return 1
fi
}
check_release_candidate
Do not continue unless this guard succeeds. It deliberately requires the
candidate to be the exact clean commit already published at origin/main.
Verify Modules And Repository Boundaries
Confirm the module path and declared Go version:
test "$(
GOWORK=off go list -m -f '{{.Path}} {{.GoVersion}}'
)" = 'gitea.maximumdirect.net/eric/scriptorium 1.25.5'
Require Promptkit v0.1.0 as both the direct module-graph edge and the selected
module version:
direct_promptkit=$(
GOWORK=off go mod graph |
awk '
$1 == "gitea.maximumdirect.net/eric/scriptorium" &&
$2 ~ /^gitea\.maximumdirect\.net\/eric\/promptkit@/ {
print $2
}
'
)
test "$direct_promptkit" = \
'gitea.maximumdirect.net/eric/promptkit@v0.1.0'
test "$(
GOWORK=off go list -m -f '{{.Path}}@{{.Version}}' \
gitea.maximumdirect.net/eric/promptkit
)" = 'gitea.maximumdirect.net/eric/promptkit@v0.1.0'
GOWORK=off go list -m all
Require tidy module metadata and recheck the repository exclusions:
GOWORK=off go mod tidy -diff
test -z "$(git ls-files go.work go.work.sum)"
test ! -e vendor
if grep -Eq '^[[:space:]]*replace([[:space:]]|\()' go.mod
then
printf '%s\n' 'go.mod contains a replacement' >&2
exit 1
fi
test -z "$(git status --porcelain)"
Validate The Application
Run the complete application validation:
GOWORK=off go test ./...
GOWORK=off go test -race ./...
GOWORK=off go vet ./...
validation_build_dir=$(mktemp -d)
GOWORK=off go build \
-o "$validation_build_dir/scriptorium" \
./cmd/scriptorium
The ordinary test run includes the architecture guard that rejects a root Go package, former framework package families, and imports of Promptkit internal packages. Inspect the repository for generated binaries, credentials, temporary output, sibling paths, and other files that do not belong in the tracked release source.
Check every tracked Go file. This command must produce no output:
unformatted=$(
git ls-files '*.go' |
while IFS= read -r go_file
do
gofmt -l "$go_file"
done
)
test -z "$unformatted"
Run the maintained render script and smoke-test both maintained configuration examples without a model call:
GOWORK=off ./examples/render-markdown-summary.sh
for config_file in examples/config.yml examples/config.full.yml
do
GOWORK=off go run ./cmd/scriptorium render \
--config "$config_file" \
--prompt generic.markdown_summary \
--input transcript=./examples/fixtures/transcript.md \
--input glossary=./examples/fixtures/glossary.yml \
--format json >/dev/null
done
Exercise usage output and offline rendering with the temporary native executable:
usage_output="$validation_build_dir/usage.txt"
if "$validation_build_dir/scriptorium" >"$usage_output" 2>&1
then
printf '%s\n' 'expected an invocation without a command to fail' >&2
exit 1
fi
grep -F 'usage: scriptorium' "$usage_output"
"$validation_build_dir/scriptorium" render \
--config ./examples/config.yml \
--prompt generic.markdown_summary \
--input transcript=./examples/fixtures/transcript.md \
--input glossary=./examples/fixtures/glossary.yml \
--format text >/dev/null
Follow every maintained local, Promptkit-tagged, and other external Markdown link. Confirm that all repository-relative link targets exist. Finish the application checks with:
git diff --check
test -z "$(git status --porcelain)"
Require Release Notes And Reproduce Packaging
The immutable release note must exist before tagging:
release_notes="docs/releases/$RELEASE_VERSION.md"
test -f "$release_notes"
test -s "$release_notes"
Validate every local and currently published link in the note. For links pinned to the candidate Scriptorium tag, confirm that the corresponding repository-relative path exists even though its tag URL is not live yet.
Reproduce the hosted build flags, targets, and filenames in a temporary directory:
release_dist=$(mktemp -d)
release_package='gitea.maximumdirect.net/eric/scriptorium/cmd/scriptorium'
build_release_binary() {
target_os="$1"
target_arch="$2"
output="$release_dist/scriptorium-$RELEASE_VERSION-$target_os-$target_arch"
CGO_ENABLED=0 GOOS="$target_os" GOARCH="$target_arch" GOWORK=off \
go build -trimpath -ldflags '-s -w' \
-o "$output" "$release_package"
}
build_release_binary linux amd64
build_release_binary linux arm64
test -s "$release_dist/scriptorium-$RELEASE_VERSION-linux-amd64"
test -s "$release_dist/scriptorium-$RELEASE_VERSION-linux-arm64"
file "$release_dist/scriptorium-$RELEASE_VERSION-linux-amd64"
file "$release_dist/scriptorium-$RELEASE_VERSION-linux-arm64"
Require file to identify Linux executables for x86-64 and ARM aarch64,
respectively. Inspect the
hosted workflow and confirm that it uses the
same build flags and names, copies the selected release note to
dist/RELEASE_NOTES.md, publishes only dist/scriptorium-*, and keeps
checksum generation enabled.
Create And Publish The Tag
Run the candidate guard again immediately before creating the tag:
check_release_candidate
test -f "$release_notes"
test -s "$release_notes"
Create an annotated tag explicitly bound to the validated commit, using the version-specific release note as its message:
git tag --annotate "$RELEASE_VERSION" \
--file "$release_notes" \
"$RELEASE_COMMIT"
Inspect the tag and require it to resolve to the validated source:
test "$(git cat-file -t "refs/tags/$RELEASE_VERSION")" = tag
git show --no-patch --decorate "refs/tags/$RELEASE_VERSION"
test "$(
git rev-parse --verify "refs/tags/$RELEASE_VERSION^{commit}"
)" = "$RELEASE_COMMIT"
If inspection finds an error, delete only the unpublished local tag, correct the candidate, and repeat the complete validation. Never move or recreate a published tag.
Push only the selected tag ref:
git push origin \
"refs/tags/$RELEASE_VERSION:refs/tags/$RELEASE_VERSION"
Observe And Verify Publication
Open the hosted workflow run for the selected tag. Require
build-release-assets to succeed before publish-release, then require the
publication step and hosted release to succeed. A queued, running, failed, or
partially published workflow is not a verified release.
Compare the local and remote annotated-tag objects and their source commits:
remote_tag=$(
git ls-remote --tags origin "refs/tags/$RELEASE_VERSION" |
awk 'NR == 1 { print $1 }'
)
remote_commit=$(
git ls-remote --tags origin "refs/tags/$RELEASE_VERSION^{}" |
awk 'NR == 1 { print $1 }'
)
test -n "$remote_tag"
test "$remote_tag" = \
"$(git rev-parse --verify "refs/tags/$RELEASE_VERSION")"
test "$remote_commit" = "$RELEASE_COMMIT"
Download the hosted binaries and checksum file into a temporary directory:
release_base="https://gitea.maximumdirect.net/eric/scriptorium/releases/download/$RELEASE_VERSION"
download_dir=$(mktemp -d)
(
cd "$download_dir"
for asset in \
"scriptorium-$RELEASE_VERSION-linux-amd64" \
"scriptorium-$RELEASE_VERSION-linux-arm64" \
SHA256SUMS
do
curl --fail --location --remote-name "$release_base/$asset"
done
test -s "scriptorium-$RELEASE_VERSION-linux-amd64"
test -s "scriptorium-$RELEASE_VERSION-linux-arm64"
test -s SHA256SUMS
sha256sum --check SHA256SUMS
test "$(wc -l < SHA256SUMS | tr -d ' ')" = 2
file "scriptorium-$RELEASE_VERSION-linux-amd64"
file "scriptorium-$RELEASE_VERSION-linux-arm64"
)
Require the same Linux architectures observed in the local packaging check and confirm that the hosted release contains no unexpected asset. On a compatible Linux host, make the matching downloaded binary executable and repeat the usage-output and offline-render smoke checks against it.
Only after the tag, workflow, release body, binaries, architectures, and checksums all pass verification is the candidate a verified published release.
Handle Failures
Before tag publication, correct the release commit or note and restart the complete procedure. After tag publication, never delete, move, overwrite, or recreate the tag. A transient hosted failure may be retried only against the same immutable tag and commit and only when doing so cannot overwrite or silently retain partial assets. A source, packaging, note, or artifact defect requires a new corrective semantic version from a new validated commit.
Record the selected version, validated commit, tag object, workflow result,
artifact names, checksum result, and smoke-check outcome in the release
checkpoint. Keep temporary builds and downloaded assets outside the repository,
and require a clean main synchronized with origin/main when verification
is complete.