diff --git a/docs/release.md b/docs/release.md index 87e7f4a..c8c9f9e 100644 --- a/docs/release.md +++ b/docs/release.md @@ -106,7 +106,7 @@ go list -m go.maximumdirect.net/releasekit "$releasekit_bin" version ``` -This repository selects ReleaseKit v0.6.0. Use the compiled program above for +This repository selects ReleaseKit v0.8.0. Use the compiled program above for publication so its exit status and publication report reach you unchanged. `go run` maps a nonzero child exit status to 1. ReleaseKit's child Go commands use the `go` found on `PATH` with `GOTOOLCHAIN=local`; keep the selected @@ -269,53 +269,80 @@ version through the normal release procedure. ## 6. Recover from a failed or uncertain tag push -The executable reports the publication state, phase, version, candidate commit, -tag-object ID, effective endpoint, and error on standard error. After a failed -tag push, it makes one bounded check of the exact publication remote to find out -whether the tag arrived. Exit status 3 -means the tag outcome remains unknown; do not blindly retry or move a tag. +ReleaseKit attempts to save publication evidence before pushing a tag and +reports the absolute record path, or a recording warning, separately from the +publication outcome. A recording failure does not undo a successful publication +or change its exit status. Confirmed publication still completes the source +release; hosted delivery remains a separate operation. -An annotated tag is a Git object containing the release note and a pointer to -the source commit. Its tag-object ID identifies that complete object. Checking -only the commit ID would not establish that the intended tag object arrived. +Records default to a private store under the repository's Git common directory, +shared by linked worktrees. `publish --record-dir DIR` can select another +location; relative paths resolve from the source repository root. Records can +contain exact endpoints, including credentials. Keep them private and remove +them when their recovery value expires. There is no new tracked storage, +credential-preflight gate, or required recording-success check. -For an uncertain result, use the exact endpoint (remote URL), candidate commit, -version, and tag-object ID from the report. Copy the captured endpoint into the variable -below using normal shell quoting. Check that Git still resolves it to itself, -then query the authoritative refs once: +After a failed push, ReleaseKit makes one bounded observation of the original +publication endpoint. If the outcome remains unknown, use the explicitly saved +record rather than rerunning publication. Copy the recovery command printed by +ReleaseKit, or use the same compiled executable: ```sh -authoritative='COPY_CAPTURED_ENDPOINT' -release_version='vMAJOR.MINOR.PATCH' -test "$(git ls-remote --get-url -- "$authoritative")" = "$authoritative" && - git ls-remote --refs -- "$authoritative" refs/heads/main "refs/tags/$release_version" +publication_record='/absolute/path/from/the/publication/report.json' +"$releasekit_bin" reconcile --record "$publication_record" ``` -A failed URL-equality check or remote query leaves the outcome unresolved. -Correct transport configuration before continuing; a rewritten destination -cannot establish absence at the captured endpoint. - -Compare the `main` object ID with the reported candidate commit and the tag -object ID with the reported tag-object ID. `--refs` returns the annotated tag -object itself. A peeled commit is the source commit reached by following that -tag; it is a different identity. If the tag object matches, the -release is published. If the query fails, the result is conflicting, or the -endpoint or candidate differs, stop and resolve the state before retrying. If -the endpoint is reachable, the tag is confirmed absent, and `main` still equals -the reported candidate, remove a retained local tag only after confirming it -was not published or used by another channel; then rerun `publish`, which -revalidates the candidate: +If the path was lost, discover local attempts without contacting a remote: ```sh +"$releasekit_bin" publication-attempts +``` + +Select the record for the intended attempt from its reported version, candidate, +and tag evidence; discovery does not automatically choose the latest attempt. +For an alternate store, supply `publication-attempts --record-dir DIR`. +Both commands support optional `--json` output. Treat saved records as private +regardless of the CLI's credential-redacted diagnostic output. + +Reconciliation observes the captured endpoint and exact tag object using current +supported Git transport settings. It does not load the current profile, require +the original `HEAD`, rerun candidate validation, change tags, or contact Gitea +hosted delivery. It needs no clean candidate checkout. Use ReleaseKit v0.8.0 or +another CLI supporting the saved record format, including format 2 where exact +non-UTF-8 endpoint/path bytes were retained. + +| Reconciliation status | Meaning and next action | +| --- | --- | +| `0` | The intended tag object is published. The source release is complete; continue with hosted delivery from its clean tagged checkout. | +| `1` | The tag is currently absent or conflicting. Read the reported state. Investigate conflicts; absence alone does not prove that the version was never published. | +| `2` | The record cannot be read/decoded, or the invocation is invalid. Correct the path, access, CLI compatibility, or command. | +| `3` | Observation remains unresolved, including insufficient evidence or cancellation. Correct the transport or other reported cause before another explicit observation; do not blindly republish. | + +These are recovery actions after failure, not read-back requirements after a +successful push. A failed observation leaves the result unresolved. Do not poll +CI or move a tag to settle uncertainty. + +When no usable record exists (for example, storage failed or an older CLI made +the attempt), use the captured endpoint and exact tag-object ID from the original +report for a manual authoritative observation. Checking only the peeled commit +would not establish that the intended annotated tag object arrived. Do not +substitute current profile or local tag state for missing original evidence. + +Only after establishing that a retained local tag was never published or used +by another release channel may it be removed before retrying publication: + +```sh +release_version='vMAJOR.MINOR.PATCH' git tag -d "$release_version" "$releasekit_bin" publish "$release_version" ``` +Replace the placeholder with the original attempt's version. +The next `publish` revalidates the candidate and publication guards. A conflicting +published tag must never be moved or reused. A defect in published source +requires a corrected commit, a new appropriate version, and a matching note. A validation failure after the candidate reached `main` leaves an untagged -candidate there. Correct it with a new commit rather than rewriting or rolling -back `main`. A conflicting published tag must never be moved or reused. A -defect in published source requires a corrected commit, a new semantically -appropriate version, and a matching release note. +candidate there; correct it in a new commit rather than rewriting `main`. ## 7. Optional consumer verification diff --git a/go.mod b/go.mod index d603d19..b85c355 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require go.yaml.in/yaml/v4 v4.0.0-rc.6 require ( github.com/yuin/goldmark/v2 v2.1.6 // indirect - go.maximumdirect.net/releasekit v0.6.0 // indirect + go.maximumdirect.net/releasekit v0.8.0 // indirect golang.org/x/mod v0.40.0 // indirect ) diff --git a/go.sum b/go.sum index f1e7c51..b09fd09 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/yuin/goldmark/v2 v2.1.6 h1:igy0q9JcqovoFXFx/XjtU8sHVhcUHavPj2sA3EoKJUE= github.com/yuin/goldmark/v2 v2.1.6/go.mod h1:G6M4/qOFtfn01/o14BU1UR2Lo5N3S9Qo7xCuz/sHjGQ= -go.maximumdirect.net/releasekit v0.6.0 h1:wbzMeQGeLjXigBSN9j9iJLf/CC3htjzt9iEwubezgy0= -go.maximumdirect.net/releasekit v0.6.0/go.mod h1:QE4BLrAhVJTzi+/YTcVl21K/qZp9ojRLg5kuoy3Zb44= +go.maximumdirect.net/releasekit v0.8.0 h1:hSaHPbUI7tAhnolfbu7UMj2fvmm9FGBY+jhkygdbxR0= +go.maximumdirect.net/releasekit v0.8.0/go.mod h1:QE4BLrAhVJTzi+/YTcVl21K/qZp9ojRLg5kuoy3Zb44= go.yaml.in/yaml/v4 v4.0.0-rc.6 h1:1h7H1ohdUh93/FyE4YaDa1Zh64K6VVbjF4K6WUxMtH4= go.yaml.in/yaml/v4 v4.0.0-rc.6/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs=