Align release publication policy and runbook
This commit is contained in:
@@ -78,8 +78,9 @@ go test ./internal/config -run '^TestExamplesLoadAndValidate$'
|
|||||||
|
|
||||||
The documentation check verifies local Markdown links and the dependency graph
|
The documentation check verifies local Markdown links and the dependency graph
|
||||||
of the Woodpecker workflows. The configuration check loads every maintained
|
of the Woodpecker workflows. The configuration check loads every maintained
|
||||||
pipeline and session example. Tag CI reuses this validation path before its
|
pipeline and session example. Tag CI independently revalidates the tagged source
|
||||||
asynchronous asset publication; the maintainer release boundary is documented
|
before asynchronous asset delivery; it is not a second synchronous release
|
||||||
|
prerequisite. The normal maintainer entry point is the single publication command
|
||||||
in the [Release Procedure](release.md).
|
in the [Release Procedure](release.md).
|
||||||
|
|
||||||
Woodpecker also runs `go test -race -shuffle=on -count=3 ./...` on its scheduled
|
Woodpecker also runs `go test -race -shuffle=on -count=3 ./...` on its scheduled
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
## Standard Repository Policies
|
## Standard Repository Policies
|
||||||
|
|
||||||
Material: `docs/policy/agents.md`, `architecture.md`, `documentation.md`,
|
Material: `docs/policy/agents.md`, `architecture.md`, `documentation.md`,
|
||||||
`testing.md`, `meta.md`, and `oss.md`; root `AGENTS.md` and the policy reading
|
`testing.md`, `release.md`, `meta.md`, and `oss.md`; root `AGENTS.md` and the policy reading
|
||||||
guide in `docs/development.md`, adapted for this repository.
|
guide in `docs/development.md`, adapted for this repository.
|
||||||
|
|
||||||
Copyright (C) 2026 Eric Rakestraw
|
Copyright (C) 2026 Eric Rakestraw
|
||||||
|
|||||||
@@ -213,6 +213,6 @@ Common ownership of projects is neither required nor a blanket exception.
|
|||||||
|
|
||||||
Apply the formatting convention prospectively. Do not rewrite published tags,
|
Apply the formatting convention prospectively. Do not rewrite published tags,
|
||||||
notes, binaries, or checksums to retrofit it. Address actual omissions deliberately
|
notes, binaries, or checksums to retrofit it. Address actual omissions deliberately
|
||||||
under the applicable terms and the [release correction policy](release.md#immutability-and-correction).
|
under the applicable terms and the [release correction policy](release.md#immutability-and-failure-recovery).
|
||||||
Ordinary attribution maintenance and release links are preparation work, not new
|
Ordinary attribution maintenance and release links are preparation work, not new
|
||||||
release approval gates.
|
release approval gates.
|
||||||
|
|||||||
@@ -1,54 +1,60 @@
|
|||||||
# Release Policy
|
# Release Policy
|
||||||
|
|
||||||
**Base Policy Version:** 1.0
|
**Base Policy Version:** 1.4
|
||||||
|
|
||||||
**Last Updated**: 2026-09-25
|
**Last Updated**: 2026-09-28
|
||||||
|
|
||||||
|
**Upstream Source**: https://gitea.maximumdirect.net/eric/policy/src/commit/d84a38ebab26f048cafbe59a2246700c35d29843/docs/policy/release.md
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
||||||
This policy defines the minimum requirements for selecting, validating,
|
This policy defines a proportionate workflow for validating and publishing an
|
||||||
publishing, verifying, and correcting releases. Its goal is to make every
|
intended source revision under a fresh version. Releases should require little
|
||||||
release intentional, reproducible from an exact source revision, documented
|
routine intervention, avoid repeated work, and preserve published contents.
|
||||||
for consumers, and safe from retrospective replacement.
|
|
||||||
|
|
||||||
The repository-specific commands that implement these requirements belong in
|
The repository-specific commands belong in `docs/release.md`. Historical release
|
||||||
`docs/release.md`. Historical release summaries belong in `docs/releases/`.
|
summaries belong in `docs/releases/`. The procedure must agree with the adopted
|
||||||
Neither document may weaken this policy, including its explicit repository
|
policy and link to it rather than introduce additional release requirements.
|
||||||
exceptions.
|
|
||||||
|
|
||||||
## Release Model
|
## Release Model And Completion
|
||||||
|
|
||||||
A release consists of:
|
A source release identifies one exact committed source revision through an
|
||||||
|
immutable version tag and a version-matched, checked-in release note. The
|
||||||
|
primary release branch is `main` unless the adopted policy specifies otherwise.
|
||||||
|
The procedure must name the authoritative publication remote.
|
||||||
|
|
||||||
- one exact source commit reachable from the repository's primary branch;
|
A successful push of the intended tag, with its source commit available on the
|
||||||
- one immutable version tag identifying that commit;
|
authoritative primary branch, completes the release. The source commit may be
|
||||||
- one version-matched, checked-in release note; and
|
pushed before the tag or published together with it through an atomic push.
|
||||||
- every artifact, image, package, registry entry, or other deliverable the
|
The procedure must choose and document the supported approach.
|
||||||
repository declares for that release.
|
|
||||||
|
|
||||||
The repository's primary branch is `main` unless an adopted policy documents
|
Release commands must finish at successful publication. They must not wait for
|
||||||
an exception. A release candidate must be committed and pushed to that branch
|
or poll CI, require successful CI status, inspect downloadable artifacts, or
|
||||||
before its release tag is published.
|
check consumer installation or Go module availability as completion gates.
|
||||||
|
A failed or ambiguous push requires resolving the authoritative remote state
|
||||||
|
before declaring success or retrying; successful pushes do not require a
|
||||||
|
separate remote read-back gate.
|
||||||
|
|
||||||
The source tag is the authoritative release identity. Published deliverables
|
Repositories may publish source only. Additional binaries, images, packages,
|
||||||
must be traceable to the tagged source and must agree with its version. A
|
checksums, or other declared deliverables may be published asynchronously from
|
||||||
repository may publish source only. When it publishes additional deliverables,
|
the tagged source. The procedure must identify those deliverables and the
|
||||||
its release procedure must enumerate them and define how each is built,
|
responsibility of their automation. Published deliverables must be traceable
|
||||||
identified, published, and verified.
|
to that source and identify the same version.
|
||||||
|
|
||||||
Release automation may validate or publish a release only when its exact
|
Track source-release completion and artifact delivery separately. Report delivery
|
||||||
responsibility is documented. Automation must not silently change the source
|
failures and optional investigations under artifact-delivery status.
|
||||||
candidate, move a published tag, replace a published deliverable, or repair a
|
|
||||||
release in place.
|
|
||||||
|
|
||||||
## Versioning And Compatibility
|
## Versioning And Compatibility
|
||||||
|
|
||||||
Stable releases use Semantic Versioning in the form `vMAJOR.MINOR.PATCH`, with
|
Stable releases use Semantic Versioning in the form `vMAJOR.MINOR.PATCH`, with
|
||||||
no leading zeroes. Each version must be unused locally and in every configured
|
no leading zeroes. Prereleases require an explicit adopted policy defining
|
||||||
remote or publication channel before publication begins.
|
their format and use.
|
||||||
|
|
||||||
Unless an adopted policy explicitly supports and defines prereleases,
|
The selected version must be unused locally and on the authoritative remote.
|
||||||
prerelease versions are not permitted.
|
Do not require queries to every configured remote or publication service.
|
||||||
|
A version known to have already been published must not be reused, even if the
|
||||||
|
normal guards do not inspect that channel. Adding a distribution channel
|
||||||
|
requires a deliberate update to the policy and procedure where needed.
|
||||||
|
|
||||||
For releases at or after `v1.0.0`:
|
For releases at or after `v1.0.0`:
|
||||||
|
|
||||||
@@ -61,161 +67,154 @@ configuration, data, integration, or compatibility contract when the release
|
|||||||
note explains the impact and required consumer action. A patch release must
|
note explains the impact and required consumer action. A patch release must
|
||||||
not intentionally break those contracts within its minor line.
|
not intentionally break those contracts within its minor line.
|
||||||
|
|
||||||
A repository with additional compatibility promises must define them in its
|
Additional compatibility promises and deliberate exceptions belong in the
|
||||||
adopted policy or canonical contract documentation. A release note may explain
|
adopted policy or canonical contract documentation. Release notes explain
|
||||||
a compatibility change, but it must not redefine the current compatibility
|
changes; they do not redefine current compatibility contracts.
|
||||||
contract.
|
|
||||||
|
|
||||||
## Release Documentation
|
## Release Documentation And Preparation
|
||||||
|
|
||||||
Every release made under this policy must include
|
Every new release must include `docs/releases/<version>.md` in the tagged
|
||||||
`docs/releases/<version>.md` in the tagged source tree. The filename must use
|
source tree. Use the complete version tag as the filename and begin with a
|
||||||
the complete version tag, and the document must begin with a title containing
|
title containing the product name and that exact version.
|
||||||
the product name and that exact version.
|
|
||||||
|
|
||||||
Every new release note must contain these sections:
|
Include a short summary and any relevant compatibility impact or upgrade
|
||||||
|
instructions. Choose sections suited to the release and keep the note
|
||||||
|
proportionate to the change.
|
||||||
|
|
||||||
```markdown
|
Keep exact current contracts in their canonical documents and update affected
|
||||||
# <Product> vMAJOR.MINOR.PATCH
|
documentation with the behavior change. Link to those documents from the note
|
||||||
|
where useful. If annotated tags are used, derive their summary from the
|
||||||
|
checked-in note.
|
||||||
|
|
||||||
This release ...
|
Include the release footer defined by the [OSS policy](oss.md#discoverability)
|
||||||
|
in each new release document and preserve it on the hosted download page. The
|
||||||
|
release procedure defines the concrete URLs and publication mechanics. Prepare
|
||||||
|
these links with the release note; they do not add a network check or approval
|
||||||
|
gate.
|
||||||
|
|
||||||
## Summary
|
Handle version selection, release-note content, and retirement of completed
|
||||||
|
roadmaps during preparation. Preserve planned and deferred work and repair
|
||||||
|
affected links when retiring roadmaps. Reuse ordinary change review for
|
||||||
|
unintended files, credentials, and private data. Scope release-content review
|
||||||
|
to the tagged source.
|
||||||
|
|
||||||
## Compatibility
|
## Candidate Validation
|
||||||
|
|
||||||
## Upgrade
|
Provide one publication command that validates the final committed candidate,
|
||||||
|
checks publication state, and publishes the intended refs. A validation-only
|
||||||
|
mode or command may be provided for diagnosis, but must not be a required step
|
||||||
|
before publication repeats the same checks.
|
||||||
|
|
||||||
## Changes
|
Run the complete deterministic, offline default test suite once for the final
|
||||||
```
|
candidate, following the [testing policy](testing.md). For repositories that
|
||||||
|
publish builds, verify the required build targets. Select those targets for
|
||||||
|
the application's actual support commitments; native testing on each platform
|
||||||
|
and access to particular hardware are not default release prerequisites.
|
||||||
|
|
||||||
Use an explicit statement such as "No action required" when a section has no
|
Additional gates must address a concrete risk that the existing checks do not
|
||||||
substantive content.
|
adequately cover. Do not automatically require linting, race testing, module
|
||||||
|
hygiene audits, documentation audits, credential scans, example validation, or
|
||||||
|
expanded platform matrices solely because they are possible. Retain such
|
||||||
|
checks when their distinct protection justifies their cost. A one-time migration
|
||||||
|
check must not silently become a permanent release requirement.
|
||||||
|
|
||||||
Release notes are immutable historical summaries. They should link to current
|
Release tests must not require real credentials, mutable application services,
|
||||||
canonical documentation for exact behavior. Every affected canonical document
|
paid APIs, provider probes, or inference calls. The procedure may describe dependency
|
||||||
must be updated in the same candidate; a release note must not substitute for
|
preparation separately from offline validation.
|
||||||
current documentation or defer required documentation changes.
|
|
||||||
|
|
||||||
## Candidate Preparation And Validation
|
Avoid duplicate validation across release phases. Successful checks for the
|
||||||
|
same final candidate need not be repeated solely to satisfy another phase;
|
||||||
|
any reuse must clearly apply to that exact candidate. Persistent validation
|
||||||
|
caches are not required. If the candidate changes, validate the new candidate.
|
||||||
|
Cheap publication-state checks may be repeated immediately before pushing
|
||||||
|
because local or remote state may have changed during validation.
|
||||||
|
|
||||||
Each repository must provide one documented release-check command or script
|
Automate mechanical guards. Human review gates are strongly disfavored: add one
|
||||||
that exercises the complete set of automated source-candidate gates. The
|
only through an explicit project-specific policy decision explaining the
|
||||||
repository-specific procedure must state all additional manual gates.
|
concrete risk and why ordinary change review or automation is insufficient.
|
||||||
|
Do not add routine confirmations for checks the command can perform itself.
|
||||||
|
|
||||||
The required gates are:
|
## Narratio Candidate Checks
|
||||||
|
|
||||||
- dependency or module hygiene appropriate to the language and ecosystem;
|
Narratio's candidate checker adds the following checks to the one full,
|
||||||
- the complete deterministic, offline default test suite;
|
race-enabled offline suite:
|
||||||
- static analysis and linting used by the repository;
|
|
||||||
- formatting and whitespace checks;
|
- `go mod tidy -diff` detects an incomplete checked-in module graph without
|
||||||
- validation of maintained examples, configuration, schemas, generated
|
changing source.
|
||||||
sources, and documentation commands where applicable;
|
- Tracked Go formatting and diff whitespace checks catch inexpensive source
|
||||||
- review of changed local documentation links;
|
hygiene defects before publication.
|
||||||
- removal of obsolete roadmap documents under `docs/roadmap/` that describe
|
- Race instrumentation adds distinct coverage for concurrent I/O,
|
||||||
completed work included in the release; and
|
cancellation, and shared state.
|
||||||
- review for unintended files, generated output, credentials, private data,
|
- `go vet ./...` detects static issues not established by runtime tests.
|
||||||
and unrelated changes.
|
- The asset builder verifies the declared Linux amd64, Linux arm64, and
|
||||||
|
best-effort macOS arm64 binaries and checks the embedded version on the host.
|
||||||
|
|
||||||
|
The checker uses `GOWORK=off` and readonly module mode for tests, vet, and
|
||||||
|
builds. Versioned module replacements are allowed; filesystem replacements
|
||||||
|
are not release inputs. Workspace and vendor files are not prohibited solely
|
||||||
|
by their presence.
|
||||||
|
|
||||||
## Publication Guards
|
## Publication Guards
|
||||||
|
|
||||||
Immediately before publication, the maintainer or release automation must
|
Automate the checks needed to publish the intended source under a fresh version:
|
||||||
fetch the current primary branch and tags from the authoritative remote and
|
|
||||||
verify all of the following:
|
|
||||||
|
|
||||||
- the version has the required format and is not already published;
|
- the version has the adopted format and is unused locally and on the
|
||||||
- the current branch is the primary release branch;
|
authoritative remote;
|
||||||
- the worktree and index are clean;
|
- the worktree and index are clean on the primary release branch;
|
||||||
- the current commit is the recorded, validated candidate;
|
- the source being published is the exact candidate that passed validation,
|
||||||
- the authoritative remote branch points to that same commit;
|
including its matching release note;
|
||||||
- the matching release note exists in that commit and has the required title
|
- the remote primary branch already identifies that candidate, or an atomic
|
||||||
and sections;
|
source/tag push will advance it to that candidate without discarding remote
|
||||||
- all required approvals and automated gates succeeded for that commit; and
|
history; and
|
||||||
- no local or remote tag already uses the selected version.
|
- the tag identifies the validated candidate and only the intended refs are
|
||||||
|
published.
|
||||||
|
|
||||||
Create the version tag against the guarded commit and verify its target before
|
Fetch the authoritative branch and tag state as needed for these checks.
|
||||||
publishing it. Publish only the exact intended ref and deliverables. Broad
|
Do not force branch or tag updates to bypass a failed guard. Publish explicit
|
||||||
operations such as `git push --tags` are discouraged because they can publish
|
refs rather than all local tags. Do not require CI success, publication-service
|
||||||
unreviewed refs unrelated to the release.
|
availability checks, or a separate manual confirmation for each guard.
|
||||||
|
|
||||||
The repository-specific procedure must make these guards executable or give
|
## Immutability And Failure Recovery
|
||||||
copyable commands for performing them.
|
|
||||||
|
|
||||||
## Immutability And Correction
|
Published version tags and published contents are immutable. Do not move,
|
||||||
|
overwrite, recreate, or delete a published tag, or replace a published artifact,
|
||||||
|
checksum, signature, provenance statement, or release note under that version.
|
||||||
|
The checked-in note at the immutable tag is that release's historical record.
|
||||||
|
|
||||||
Published version tags and published deliverables are immutable. Do not move,
|
If validation fails before publication, correct and commit the candidate and
|
||||||
overwrite, recreate, or delete a published tag. Do not replace an artifact,
|
rerun the publication command. If a push fails or its outcome is ambiguous,
|
||||||
image, package, checksum, signature, provenance statement, or release note
|
inspect the authoritative remote before retrying. Remove an unpublished local
|
||||||
under an already published version.
|
tag only after confirming it was never published or used by a release channel.
|
||||||
|
An unavailable remote leaves publication status unknown; do not overwrite a tag
|
||||||
|
to resolve uncertainty.
|
||||||
|
|
||||||
If validation fails before publication, correct the candidate, rerun the
|
Transient network, authentication, CI, or consumer-availability failures do not
|
||||||
complete gates, and repeat the publication guards. An unpublished local tag
|
by themselves require a new version. Asynchronous delivery may be retried from
|
||||||
may be removed after confirming that it was never published or used by a
|
the same immutable tag. Missing artifacts may be published later, provided
|
||||||
release channel.
|
existing published contents are not replaced and the additions remain consistent
|
||||||
|
with the tagged source and any already-published checksum manifest. If that
|
||||||
|
cannot be achieved, publish a new version.
|
||||||
|
|
||||||
If publication or post-publication verification reveals a defect, leave the
|
A defect requiring changes to published source or contents requires a corrected
|
||||||
published release intact. Correct the defect on the primary branch, choose a
|
commit, a new semantically appropriate version, a matching release note, and
|
||||||
new patch version or other semantically appropriate new version, write a new
|
the release procedure again. Leave the original release intact. A channel's
|
||||||
matching release note, and perform the complete release procedure again.
|
yank, deprecation, or advisory mechanism may warn consumers away from a defective
|
||||||
|
release without replacing its contents or reusing its version.
|
||||||
A distribution channel's supported yank, deprecation, or advisory mechanism
|
|
||||||
may warn consumers away from a defective release, but it must not replace that
|
|
||||||
release's contents or reuse its version.
|
|
||||||
|
|
||||||
## Repository Release Procedure
|
## Repository Release Procedure
|
||||||
|
|
||||||
Every adopting repository must maintain `docs/release.md` as the canonical
|
Maintain `docs/release.md` as a short, executable maintainer runbook. It must
|
||||||
maintainer runbook for implementing this policy. It must define, in execution
|
identify:
|
||||||
order:
|
|
||||||
|
|
||||||
1. maintainer prerequisites and required access;
|
1. prerequisites, primary branch, and authoritative remote;
|
||||||
2. version selection and compatibility review;
|
2. version and release-note preparation;
|
||||||
3. release-note creation;
|
3. one publication command with its selected local checks and automated guards;
|
||||||
4. candidate preparation and the complete validation command;
|
4. declared deliverables, required build targets, and automation responsibilities;
|
||||||
5. candidate commit, push, and exact commit recording;
|
5. the successful-push completion point and separately reported delivery status;
|
||||||
6. publication guards;
|
and
|
||||||
7. exact tag and deliverable publication commands;
|
6. failure handling, ambiguous-push recovery, and asynchronous delivery retries.
|
||||||
8. authoritative remote and consumer-facing verification; and
|
|
||||||
9. failure handling and correction.
|
|
||||||
|
|
||||||
The procedure should use copyable, fail-fast commands where practical. It must
|
Use copyable commands where practical. Keep optional diagnostics and consumer
|
||||||
name the primary branch, authoritative remote, tag form, published
|
verification clearly optional. The procedure must not turn preparation work,
|
||||||
deliverables, supported build targets, release automation responsibilities,
|
ordinary change review, or post-publication investigation into additional
|
||||||
and consumer verification path. It must link to this policy instead of
|
release gates.
|
||||||
restating or redefining its requirements.
|
|
||||||
|
|
||||||
## Policy Exceptions
|
|
||||||
|
|
||||||
### Tag-push completion and asynchronous deliverables
|
|
||||||
|
|
||||||
Narratio's automated release responsibility ends when the exact intended tag is
|
|
||||||
successfully pushed to `origin`. It must not wait for, poll, or check CI/Gitea
|
|
||||||
results, download assets, or verify their checksums as a completion condition.
|
|
||||||
Binary and checksum publication is asynchronous; consumer-facing inspection is
|
|
||||||
optional follow-up, not a gate on the source release.
|
|
||||||
|
|
||||||
This deliberately qualifies the base policy's complete-deliverable and
|
|
||||||
post-publication verification requirements. CI reliability is outside the
|
|
||||||
release command's scope; an unavailable publication service must not cause an
|
|
||||||
automated releaser to wait indefinitely or claim to repair a published version.
|
|
||||||
|
|
||||||
Compensating controls are complete local candidate validation (including supported
|
|
||||||
cross-builds and a host version check), exact source/tag guards, immutable source
|
|
||||||
tags, asynchronous build validation, and non-overwriting asset publication.
|
|
||||||
Successful tag publication is not evidence that binary assets are available.
|
|
||||||
Optional inspection and corrective-release procedures belong in
|
|
||||||
[the release procedure](../release.md).
|
|
||||||
|
|
||||||
### Authoritative publication scope
|
|
||||||
|
|
||||||
Narratio's synchronous publication guards inspect the authoritative `origin`
|
|
||||||
remote and its `main` branch, not every configured Git remote or a Gitea release
|
|
||||||
entry. The command does not add release-service or registry availability checks.
|
|
||||||
This qualifies the base policy's all-channel unused-version requirement.
|
|
||||||
|
|
||||||
Other remotes are not Narratio release authorities, and Gitea asset publication
|
|
||||||
is asynchronous. The compensating controls are local and origin tag uniqueness,
|
|
||||||
exact candidate matching, and the publisher's non-overwrite settings. Any known
|
|
||||||
already-published version or conflicting deliverable remains ineligible for
|
|
||||||
reuse: choose a new version rather than bypassing guards. A future additional
|
|
||||||
distribution channel requires deliberate policy/procedure updates.
|
|
||||||
|
|||||||
217
docs/release.md
217
docs/release.md
@@ -1,167 +1,114 @@
|
|||||||
# Releasing Narratio
|
# Releasing Narratio
|
||||||
|
|
||||||
This is the maintainer runbook for the [Release Policy](policy/release.md).
|
This is the maintainer runbook for the [Release Policy](policy/release.md).
|
||||||
Its [explicit exceptions](policy/release.md#policy-exceptions) preserve the
|
The source release completes when the intended tag is successfully pushed to
|
||||||
successful tag push as the automated responsibility boundary. Binary publication
|
`origin`; binary delivery is a separate asynchronous status.
|
||||||
is asynchronous; later consumer-facing inspection is optional.
|
|
||||||
|
|
||||||
## Prerequisites and publication scope
|
## Prepare the candidate
|
||||||
|
|
||||||
Use a checkout on `main` with Git authentication permitting fetches and pushes
|
Use `main` and the authoritative Git remote `origin`. Install Git, a POSIX
|
||||||
to `origin`, the authoritative Git remote. Install Git, a POSIX shell, standard
|
shell, standard Unix build tools, and Go 1.26. Have the selected Go modules
|
||||||
Unix build tools, and the Go toolchain below. Make required modules available
|
available before offline validation. Git access must permit fetch and tag push.
|
||||||
before offline validation. No model/provider credentials are needed. Gitea
|
Application-provider credentials are not required; Gitea delivery credentials
|
||||||
publication credentials belong to CI's secret store, not the local command.
|
belong to CI.
|
||||||
|
|
||||||
Synchronous guards inspect local tags and `origin`, not every configured remote
|
Choose an unused stable `vMAJOR.MINOR.PATCH` under the
|
||||||
or Gitea release entry. Do not select a version known to have been published in
|
[compatibility rules](policy/release.md#versioning-and-compatibility). Narratio
|
||||||
any channel, even if these guards cannot see it.
|
is past v1.0.0, so intentional CLI or configuration breaks require a major
|
||||||
|
release. Update affected canonical documentation with the change. Write the
|
||||||
|
matching `docs/releases/vMAJOR.MINOR.PATCH.md` with a short summary and any
|
||||||
|
relevant compatibility impact or upgrade action; choose sections to suit the
|
||||||
|
release.
|
||||||
|
|
||||||
## Go prerequisite
|
End that checked-in note with these three links, replacing the version in
|
||||||
|
each URL with the intended tag. The tag need not exist remotely yet. Check that
|
||||||
|
`COPYRIGHT`, `LICENSE`, and `docs/oss/attribution.md` are present in the
|
||||||
|
candidate. The tag CI reuses the checked-in note as the hosted release body.
|
||||||
|
|
||||||
Release preparation requires the maintained Go 1.26 baseline. Tag CI currently
|
```markdown
|
||||||
uses the explicitly pinned `golang:1.26.7` image for validation and asset builds.
|
[COPYRIGHT](https://gitea.maximumdirect.net/eric/narratio/src/tag/vMAJOR.MINOR.PATCH/COPYRIGHT) | [LICENSE](https://gitea.maximumdirect.net/eric/narratio/src/tag/vMAJOR.MINOR.PATCH/LICENSE) | [ATTRIBUTION](https://gitea.maximumdirect.net/eric/narratio/src/tag/vMAJOR.MINOR.PATCH/docs/oss/attribution.md)
|
||||||
That asynchronous work does not change the tag-push completion boundary.
|
```
|
||||||
|
|
||||||
## Choose a version and write its note
|
Retire completed roadmaps included in the release while preserving planned or
|
||||||
|
deferred work and repairing affected links. Review the change through the
|
||||||
Select an unused stable version under the
|
ordinary development workflow for unintended files, credentials, private data,
|
||||||
[versioning rules](policy/release.md#versioning-and-compatibility). Narratio is
|
and documentation accuracy. Commit and push the candidate to `origin/main`
|
||||||
past v1.0.0; intentional CLI/configuration breaks require a major release.
|
before tagging; record its exact commit if needed:
|
||||||
|
|
||||||
Create `docs/releases/vMAJOR.MINOR.PATCH.md` using the
|
|
||||||
[required note structure](policy/release.md#release-documentation).
|
|
||||||
Describe CLI, configuration, artifact, integration, and operating-contract
|
|
||||||
changes, and the required operator action. Link to current canonical
|
|
||||||
documentation for exact behavior.
|
|
||||||
|
|
||||||
## Prepare and record the candidate
|
|
||||||
|
|
||||||
Before committing, perform these manual reviews in addition to automated checks:
|
|
||||||
|
|
||||||
- Confirm compatibility/version selection and update affected canonical
|
|
||||||
documentation and maintained examples.
|
|
||||||
- Remove completed roadmap documents covering work included in this release;
|
|
||||||
retain pending work. Their history remains in Git. Do not delete historical
|
|
||||||
release notes.
|
|
||||||
- Review the diff, staged changes, and untracked files for credentials, private
|
|
||||||
data, generated output, unintended files, and unrelated changes.
|
|
||||||
- Review changed documentation links and behavior-bearing commands. Automated
|
|
||||||
link checks supplement rather than replace review.
|
|
||||||
|
|
||||||
Commit the note and candidate changes, then push that commit to `main` through
|
|
||||||
the ordinary development workflow. Record the exact candidate:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git rev-parse HEAD
|
git rev-parse HEAD
|
||||||
```
|
```
|
||||||
|
|
||||||
Do not create a tag until `origin/main` points to that same commit. If the
|
## Publish
|
||||||
candidate changes, repeat the reviews and validation.
|
|
||||||
|
|
||||||
## Validate the candidate
|
From a clean `main` checkout at the pushed candidate, run one command:
|
||||||
|
|
||||||
From the repository root, run:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
scripts/check-release-candidate.sh vMAJOR.MINOR.PATCH
|
scripts/release.sh vMAJOR.MINOR.PATCH
|
||||||
```
|
```
|
||||||
|
|
||||||
The script also works when invoked by its absolute path from another directory.
|
It fetches `origin/main` and tags; requires local `HEAD` to equal
|
||||||
It validates the version and matching note, module hygiene, formatting,
|
`origin/main`; checks the committed note and unused version; runs the
|
||||||
whitespace, uncached tests, race tests, static checks, documentation, examples,
|
candidate checker **once**; then repeats cheap mutable-state guards before
|
||||||
and three cross-build assets. It uses `GOWORK=off`, does not contact application
|
creating an unsigned lightweight tag at the recorded commit. It verifies the
|
||||||
services, CI, or Gitea, and does not create tags or modify tracked source.
|
tag target and pushes only
|
||||||
Fix failures on `main`, commit, push normally, and rerun validation.
|
`refs/tags/vMAJOR.MINOR.PATCH:refs/tags/vMAJOR.MINOR.PATCH`. It does not
|
||||||
|
commit, push `main`, force updates, or publish all tags.
|
||||||
|
|
||||||
The macOS cross-build proves compilation, not native runtime behavior.
|
The checker runs `go mod tidy -diff`, tracked Go formatting and diff whitespace
|
||||||
|
checks, one `go test -mod=readonly -race -count=1 ./...` full suite, and
|
||||||
|
`go vet -mod=readonly ./...`. Its asset builder cross-builds Linux amd64,
|
||||||
|
Linux arm64, and best-effort macOS arm64, then checks the embedded version
|
||||||
|
on a host binary. The macOS cross-build proves compilation, not native
|
||||||
|
runtime behavior. Owned Go commands run with `GOWORK=off`; tests, vet, and
|
||||||
|
builds use readonly module mode. The deterministic suite includes documentation
|
||||||
|
and maintained-example checks. The command does not contact application
|
||||||
|
services or Gitea and does not wait for CI.
|
||||||
|
|
||||||
## Declared deliverables and automation
|
A successful tag push is the source-release completion point. The command
|
||||||
|
prints the tag and commit and exits without a separate network read-back.
|
||||||
|
For optional diagnosis before publication, run
|
||||||
|
`scripts/check-release-candidate.sh vMAJOR.MINOR.PATCH`; publication will
|
||||||
|
validate the candidate again, so this is not a required preparation step.
|
||||||
|
|
||||||
The source release is the exact version tag at the validated commit.
|
## Artifact delivery
|
||||||
The tag-triggered `.woodpecker/release.yml` validates the tagged source,
|
|
||||||
runs `scripts/build-release-assets.sh`, and publishes these Gitea assets:
|
Tag-triggered `.woodpecker/release.yml` independently validates the tagged
|
||||||
|
source, builds the same three targets, and publishes these Gitea downloads:
|
||||||
|
|
||||||
- `narratio-vMAJOR.MINOR.PATCH-linux-amd64`
|
- `narratio-vMAJOR.MINOR.PATCH-linux-amd64`
|
||||||
- `narratio-vMAJOR.MINOR.PATCH-linux-arm64`
|
- `narratio-vMAJOR.MINOR.PATCH-linux-arm64`
|
||||||
- `narratio-vMAJOR.MINOR.PATCH-darwin-arm64` (best effort)
|
- `narratio-vMAJOR.MINOR.PATCH-darwin-arm64` (best effort)
|
||||||
- `SHA256SUMS`
|
- `SHA256SUMS`
|
||||||
|
|
||||||
The builder embeds the version, disables ambient Go workspaces, and checks a
|
The builder embeds the tag version and checks host `version` output. CI
|
||||||
host binary's `version` output. The release publisher uses the matching
|
generates checksums and uses non-overwriting publication settings. It does
|
||||||
checked-in note and generates SHA-256 checksums. Existing files are skipped and
|
not select versions, change the source, or move tags. No image or package
|
||||||
overwrite is disabled. No image or package-registry deliverable is declared.
|
registry output is declared. Tag publication is not proof that these
|
||||||
CI does not choose versions, modify source candidates, or move tags.
|
asynchronous downloads are available.
|
||||||
|
|
||||||
## Publish the tag
|
## Failure and recovery
|
||||||
|
|
||||||
From a clean checkout on `main` whose `HEAD` equals `origin/main`, run:
|
If validation fails before tagging, correct and commit the candidate, push
|
||||||
|
it to `main`, and rerun the publication command. If the tag push reports
|
||||||
|
failure, its outcome may be ambiguous: keep the local tag and inspect the
|
||||||
|
exact `refs/tags/vMAJOR.MINOR.PATCH` at `origin` before declaring success
|
||||||
|
or retrying. A reachable matching remote tag at the recorded commit establishes
|
||||||
|
publication; a confirmed absent tag permits investigation of the transport
|
||||||
|
failure. If `origin` cannot be inspected, publication status is unknown.
|
||||||
|
Do not move, overwrite, or automatically retry the tag. Remove a local tag
|
||||||
|
only after confirming it was never published or used by a release channel.
|
||||||
|
|
||||||
```sh
|
A transient CI or delivery failure can be retried from the same immutable
|
||||||
scripts/release.sh vMAJOR.MINOR.PATCH
|
tag. Missing assets may be added later if they match the tagged source and
|
||||||
```
|
any published checksum manifest; do not overwrite existing binaries,
|
||||||
|
checksums, or release-note contents. A source/content defect, or delivery
|
||||||
|
that cannot be corrected without replacement, requires a new suitable
|
||||||
|
version and matching note. Leave the original release intact.
|
||||||
|
|
||||||
The command fetches and checks `origin/main`, re-runs candidate validation,
|
Optional consumer inspection may compare the tag's remote commit with the
|
||||||
then fetches and checks again before creating an explicitly unsigned lightweight
|
recorded candidate, check the Woodpecker delivery job and hosted Gitea note,
|
||||||
tag for the originally recorded commit. It refuses dirty, divergent, changed,
|
download the assets into an empty directory, run `sha256sum --check
|
||||||
or already-tagged candidates. It verifies the local tag target and pushes only:
|
SHA256SUMS` (or `shasum -a 256 --check SHA256SUMS` on macOS), and run
|
||||||
|
the matching host binary's `version` command. This is separate delivery
|
||||||
```text
|
observation, never a source-release gate.
|
||||||
refs/tags/vMAJOR.MINOR.PATCH:refs/tags/vMAJOR.MINOR.PATCH
|
|
||||||
```
|
|
||||||
|
|
||||||
It never commits changes, pushes `main`, force-pushes, moves tags, or pushes all
|
|
||||||
tags. Successful publication of that ref completes the command; it prints the
|
|
||||||
tag and commit and returns without waiting for CI, querying Gitea, downloading
|
|
||||||
assets, or checking checksums.
|
|
||||||
|
|
||||||
## Failure handling
|
|
||||||
|
|
||||||
Before tag creation, correct the candidate on `main` and repeat validation.
|
|
||||||
If a push fails after local tag creation, the local tag is retained for inspection.
|
|
||||||
Do not retry blindly or assume the remote received nothing: a connection failure
|
|
||||||
can leave publication uncertain. Inspect the exact remote ref before taking any
|
|
||||||
local-tag cleanup action. This is recovery from a failed command, not a
|
|
||||||
post-success publication gate.
|
|
||||||
|
|
||||||
After publication, follow the
|
|
||||||
[immutability and correction rules](policy/release.md#immutability-and-correction).
|
|
||||||
Correct defects or failed asynchronous publication with a new semantically
|
|
||||||
appropriate version, matching note, and the complete procedure. Never replace
|
|
||||||
existing tagged assets in place.
|
|
||||||
|
|
||||||
## Optional asynchronous inspection
|
|
||||||
|
|
||||||
A human may later inspect the tag-triggered Woodpecker job and Gitea release.
|
|
||||||
This is optional follow-up, never part of the automated releaser's completion
|
|
||||||
criteria. Do not wait for or poll CI/Gitea from the release command.
|
|
||||||
|
|
||||||
For optional source verification, substitute the released version below and
|
|
||||||
compare the remote target to the recorded candidate hash:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
git ls-remote --tags origin refs/tags/vMAJOR.MINOR.PATCH
|
|
||||||
git rev-parse 'refs/tags/vMAJOR.MINOR.PATCH^{commit}'
|
|
||||||
```
|
|
||||||
|
|
||||||
Download the three named binaries and `SHA256SUMS` from the matching Gitea release
|
|
||||||
into an empty inspection directory. In that directory, verify on Linux:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sha256sum --check SHA256SUMS
|
|
||||||
```
|
|
||||||
|
|
||||||
On macOS, use `shasum -a 256 --check SHA256SUMS`. After successful verification,
|
|
||||||
make the matching host binary executable and run its version command, for example
|
|
||||||
on Linux amd64 (replace the version in both commands):
|
|
||||||
|
|
||||||
```sh
|
|
||||||
chmod +x ./narratio-vMAJOR.MINOR.PATCH-linux-amd64
|
|
||||||
./narratio-vMAJOR.MINOR.PATCH-linux-amd64 version
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected output is `narratio vMAJOR.MINOR.PATCH`. Use only the matching architecture
|
|
||||||
on other supported hosts. Checksums establish consistency with the downloaded
|
|
||||||
checksum file, not a new signature or independent authenticity guarantee.
|
|
||||||
Missing assets are an asynchronous publication problem, not grounds to move or
|
|
||||||
reuse the successfully published source tag.
|
|
||||||
|
|||||||
@@ -1,22 +1,11 @@
|
|||||||
# Release Notes
|
# Release Notes
|
||||||
|
|
||||||
This directory contains immutable historical release notes for Narratio.
|
This directory contains immutable historical release notes for Narratio.
|
||||||
Future notes are created with the matching stable version and use this minimum
|
Future notes use the matching stable version and begin with
|
||||||
structure:
|
`# Narratio vMAJOR.MINOR.PATCH`. Include a short summary and relevant
|
||||||
|
compatibility or upgrade guidance; choose sections suited to the release.
|
||||||
```markdown
|
End each new note with the three immutable-tag notice links specified in the
|
||||||
# Narratio vMAJOR.MINOR.PATCH
|
[release procedure](../release.md).
|
||||||
|
|
||||||
This release ...
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
## Compatibility
|
|
||||||
|
|
||||||
## Upgrade
|
|
||||||
|
|
||||||
## Changes
|
|
||||||
```
|
|
||||||
|
|
||||||
See the [release procedure](../release.md) for creating a candidate and tag.
|
See the [release procedure](../release.md) for creating a candidate and tag.
|
||||||
When asynchronous publication succeeds, the corresponding Gitea release is the
|
When asynchronous publication succeeds, the corresponding Gitea release is the
|
||||||
|
|||||||
@@ -260,7 +260,18 @@ recursive real full-suite execution from within checker test fixtures.
|
|||||||
|
|
||||||
## Stage 5: Align publication behavior, policy, and operator procedure
|
## Stage 5: Align publication behavior, policy, and operator procedure
|
||||||
|
|
||||||
Status: Pending.
|
Status: Complete (2026-09-28).
|
||||||
|
|
||||||
|
Verification: Adopted pinned release policy v1.4 with only metadata and the
|
||||||
|
selected Narratio validation rationale added to common wording. Publication
|
||||||
|
reports failed/uncertain tag pushes without asserting nonpublication; offline
|
||||||
|
bare-remote tests cover one checker call, ignored files, retained local tag,
|
||||||
|
and the existing exact-ref/branch/candidate guards. The runbook now has one
|
||||||
|
normal publication command, immutable-tag footer URLs, separate asynchronous
|
||||||
|
delivery status, and ambiguity/recovery guidance. Updated attribution and
|
||||||
|
release navigation without changing historical notes. `go test
|
||||||
|
./internal/releasecheck ./internal/doccheck`, shell syntax checks, and
|
||||||
|
`git diff --check` passed. No tag, push, or release ran.
|
||||||
|
|
||||||
Read pinned release v1.4, current docs/policy/release.md and docs/release.md,
|
Read pinned release v1.4, current docs/policy/release.md and docs/release.md,
|
||||||
scripts/release.sh, and internal/releasecheck/publication_test.go.
|
scripts/release.sh, and internal/releasecheck/publication_test.go.
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ func TestReleaseCommandRejectsUnpublishedOrExistingCandidates(t *testing.T) {
|
|||||||
fixture.extraEnv = append(fixture.extraEnv, "NARRATIO_RELEASE_CHECKER_MODE=fail")
|
fixture.extraEnv = append(fixture.extraEnv, "NARRATIO_RELEASE_CHECKER_MODE=fail")
|
||||||
fixture.mustFail(t, "release candidate validation failed")
|
fixture.mustFail(t, "release candidate validation failed")
|
||||||
fixture.requireNoTag(t)
|
fixture.requireNoTag(t)
|
||||||
|
assertCheckerCalls(t, fixture, 1)
|
||||||
})
|
})
|
||||||
t.Run("local tag exists", func(t *testing.T) {
|
t.Run("local tag exists", func(t *testing.T) {
|
||||||
fixture := newReleaseFixture(t)
|
fixture := newReleaseFixture(t)
|
||||||
@@ -90,6 +91,8 @@ git push -q origin main
|
|||||||
|
|
||||||
func TestReleaseCommandPushesOnlyTheRecordedLightweightTag(t *testing.T) {
|
func TestReleaseCommandPushesOnlyTheRecordedLightweightTag(t *testing.T) {
|
||||||
fixture := newReleaseFixture(t)
|
fixture := newReleaseFixture(t)
|
||||||
|
writeReleaseFixtureFile(t, filepath.Join(fixture.worktree, ".git", "info", "exclude"), "ignored.tmp\n", 0o644)
|
||||||
|
writeReleaseFixtureFile(t, filepath.Join(fixture.worktree, "ignored.tmp"), "local-only\n", 0o644)
|
||||||
candidate := fixture.gitOutput("rev-parse", "HEAD^{commit}")
|
candidate := fixture.gitOutput("rev-parse", "HEAD^{commit}")
|
||||||
output, err := fixture.run()
|
output, err := fixture.run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -113,13 +116,14 @@ func TestReleaseCommandPushesOnlyTheRecordedLightweightTag(t *testing.T) {
|
|||||||
if got := strings.Fields(fixture.gitOutput("--git-dir", fixture.origin, "for-each-ref", "--format=%(refname)", "refs/heads", "refs/tags")); !slicesEqual(got, []string{"refs/heads/main", "refs/tags/v1.2.3"}) {
|
if got := strings.Fields(fixture.gitOutput("--git-dir", fixture.origin, "for-each-ref", "--format=%(refname)", "refs/heads", "refs/tags")); !slicesEqual(got, []string{"refs/heads/main", "refs/tags/v1.2.3"}) {
|
||||||
t.Fatalf("remote refs = %#v", got)
|
t.Fatalf("remote refs = %#v", got)
|
||||||
}
|
}
|
||||||
|
assertCheckerCalls(t, fixture, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReleaseCommandKeepsUnpublishedLocalTagAfterPushFailure(t *testing.T) {
|
func TestReleaseCommandRetainsLocalTagAfterUnconfirmedPush(t *testing.T) {
|
||||||
fixture := newReleaseFixture(t)
|
fixture := newReleaseFixture(t)
|
||||||
writeReleaseFixtureFile(t, filepath.Join(fixture.origin, "hooks", "pre-receive"), "#!/bin/sh\nexit 1\n", 0o755)
|
writeReleaseFixtureFile(t, filepath.Join(fixture.origin, "hooks", "pre-receive"), "#!/bin/sh\nexit 1\n", 0o755)
|
||||||
output, err := fixture.run()
|
output, err := fixture.run()
|
||||||
if err == nil || !strings.Contains(output, "local tag v1.2.3 was created but not published") {
|
if err == nil || !strings.Contains(output, "tag push for v1.2.3 failed or is unconfirmed") || strings.Contains(output, "release: published") || strings.Contains(output, "not published") {
|
||||||
t.Fatalf("push failure = %v\n%s", err, output)
|
t.Fatalf("push failure = %v\n%s", err, output)
|
||||||
}
|
}
|
||||||
if got := fixture.gitOutput("cat-file", "-t", "refs/tags/v1.2.3"); got != "commit" {
|
if got := fixture.gitOutput("cat-file", "-t", "refs/tags/v1.2.3"); got != "commit" {
|
||||||
@@ -128,11 +132,24 @@ func TestReleaseCommandKeepsUnpublishedLocalTagAfterPushFailure(t *testing.T) {
|
|||||||
if output, err := runReleaseGit(fixture.worktree, "--git-dir", fixture.origin, "show-ref", "--verify", "refs/tags/v1.2.3"); err == nil {
|
if output, err := runReleaseGit(fixture.worktree, "--git-dir", fixture.origin, "show-ref", "--verify", "refs/tags/v1.2.3"); err == nil {
|
||||||
t.Fatalf("remote tag exists after failed push: %s", output)
|
t.Fatalf("remote tag exists after failed push: %s", output)
|
||||||
}
|
}
|
||||||
|
assertCheckerCalls(t, fixture, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertCheckerCalls(t *testing.T, fixture *releaseFixture, want int) {
|
||||||
|
t.Helper()
|
||||||
|
data, err := os.ReadFile(fixture.checkerLog)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got := len(strings.Fields(string(data))); got != want {
|
||||||
|
t.Fatalf("candidate checker called %d times, want %d: %q", got, want, data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type releaseFixture struct {
|
type releaseFixture struct {
|
||||||
worktree string
|
worktree string
|
||||||
origin string
|
origin string
|
||||||
|
checkerLog string
|
||||||
extraEnv []string
|
extraEnv []string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +171,7 @@ func newReleaseFixture(t *testing.T) *releaseFixture {
|
|||||||
writeReleaseFixtureFile(t, filepath.Join(worktree, "docs", "releases", "v1.2.3.md"), "# Narratio v1.2.3\n", 0o644)
|
writeReleaseFixtureFile(t, filepath.Join(worktree, "docs", "releases", "v1.2.3.md"), "# Narratio v1.2.3\n", 0o644)
|
||||||
writeReleaseFixtureFile(t, filepath.Join(worktree, "README.md"), "fixture\n", 0o644)
|
writeReleaseFixtureFile(t, filepath.Join(worktree, "README.md"), "fixture\n", 0o644)
|
||||||
|
|
||||||
fixture := &releaseFixture{worktree: worktree, origin: origin}
|
fixture := &releaseFixture{worktree: worktree, origin: origin, checkerLog: filepath.Join(root, "checker.log")}
|
||||||
fixture.git("init", "-qb", "main")
|
fixture.git("init", "-qb", "main")
|
||||||
fixture.git("config", "user.email", "releasecheck@example.test")
|
fixture.git("config", "user.email", "releasecheck@example.test")
|
||||||
fixture.git("config", "user.name", "Release Check")
|
fixture.git("config", "user.name", "Release Check")
|
||||||
@@ -176,7 +193,8 @@ func (fixture *releaseFixture) runWith(args ...string) (string, error) {
|
|||||||
commandArgs := append([]string{filepath.Join(fixture.worktree, "scripts", "release.sh")}, args...)
|
commandArgs := append([]string{filepath.Join(fixture.worktree, "scripts", "release.sh")}, args...)
|
||||||
command := exec.Command("sh", commandArgs...)
|
command := exec.Command("sh", commandArgs...)
|
||||||
command.Dir = fixture.worktree
|
command.Dir = fixture.worktree
|
||||||
command.Env = append(os.Environ(), fixture.extraEnv...)
|
command.Env = append(os.Environ(), "NARRATIO_RELEASE_CHECKER_LOG="+fixture.checkerLog)
|
||||||
|
command.Env = append(command.Env, fixture.extraEnv...)
|
||||||
output, err := command.CombinedOutput()
|
output, err := command.CombinedOutput()
|
||||||
return string(output), err
|
return string(output), err
|
||||||
}
|
}
|
||||||
@@ -238,6 +256,7 @@ func writeReleaseFixtureFile(t *testing.T, path, contents string, mode os.FileMo
|
|||||||
|
|
||||||
const releaseCandidateStub = `#!/bin/sh
|
const releaseCandidateStub = `#!/bin/sh
|
||||||
set -eu
|
set -eu
|
||||||
|
printf 'called\n' >> "$NARRATIO_RELEASE_CHECKER_LOG"
|
||||||
|
|
||||||
case "${NARRATIO_RELEASE_CHECKER_MODE:-success}" in
|
case "${NARRATIO_RELEASE_CHECKER_MODE:-success}" in
|
||||||
success) ;;
|
success) ;;
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ if [ "$(git rev-parse --verify "refs/tags/$release_version^{commit}")" != "$cand
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if ! git push origin "refs/tags/$release_version:refs/tags/$release_version"; then
|
if ! git push origin "refs/tags/$release_version:refs/tags/$release_version"; then
|
||||||
printf 'release: local tag %s was created but not published; inspect it before retrying\n' "$release_version" >&2
|
printf 'release: tag push for %s failed or is unconfirmed; local tag retained; inspect origin before retrying\n' "$release_version" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
printf 'release: published %s at %s\n' "$release_version" "$candidate_commit"
|
printf 'release: published %s at %s\n' "$release_version" "$candidate_commit"
|
||||||
|
|||||||
Reference in New Issue
Block a user