Record publication recovery integration findings
This commit is contained in:
@@ -206,3 +206,162 @@ preserving the actual observation in the implementation rather than converting
|
||||
it to unknown remote state. Document that output-failure status in the CLI
|
||||
contract. Add focused failing-writer coverage for both structured commands;
|
||||
ordinary writable-output state/status behavior should stay unchanged.
|
||||
|
||||
## Integration audit evidence
|
||||
|
||||
A subsequent audit examined the interaction with existing consumer launchers,
|
||||
repository discovery, candidate cleanliness, and the maintainer runbook. The
|
||||
production code remains identical to the code covered by the full-suite/API
|
||||
checks above. A fresh focused run of the existing launcher, Woodpecker,
|
||||
publication-handoff, and lost-output recovery integration tests passed with
|
||||
Go 1.26.8. Disposable probes reproduced the additional issues below. R01–R06
|
||||
remain open and are preserved rather than duplicated here.
|
||||
|
||||
## R07: Make the recovery handoff survive a cold Go-tool invocation — P2
|
||||
|
||||
**Relevant code:** `cmd/releasekit/main.go:134` (`publicationRecoveryCommand`);
|
||||
`examples/multi-module/release.sh`; `docs/consumers/adoption.md`'s module-selected
|
||||
Go-tool workflow; `cmd/releasekit/publication_handoff_test.go`.
|
||||
|
||||
The new handoff always uses `os.Executable()` as the command to run later.
|
||||
Existing consumer guidance and the maintained launcher execute ReleaseKit
|
||||
through `go tool releasekit`. On an uncached tool build, Go 1.26.8 executes a
|
||||
temporary build executable and removes it when the Go driver exits. A warm
|
||||
invocation uses a surviving cached executable, so its behavior differs.
|
||||
|
||||
**Evidence:** A disposable tool-registered consumer used the current CLI with a
|
||||
cold tool build and a local remote that rejected the tag push. Publication
|
||||
returned status 1 and saved a complete record, but its printed recovery command
|
||||
named `/tmp/go-build.../exe/releasekit`. That executable was already absent when
|
||||
the driver returned, and copying the command returned status 127. A surviving
|
||||
CLI reconciled the same record successfully to `not_published` with status 1.
|
||||
The existing handoff test builds a persistent executable directly and therefore
|
||||
does not exercise this launch lifecycle.
|
||||
|
||||
**Expected behavior:** The copyable handoff works with supported consumer launch
|
||||
methods after the publication process has exited. Routine first use on a fresh
|
||||
machine/cache should not require reconstructing another invocation manually.
|
||||
The existing fallback guidance for a subsequently unavailable executable does
|
||||
not supply a working handoff for this ordinary cold-build case.
|
||||
|
||||
**Remediation:** Make the recovery invocation independent of an ephemeral
|
||||
Go-tool executable. Use a supported module-selected entry point when available,
|
||||
or otherwise provide a surviving supported entry point. Account for the
|
||||
maintained launcher's temporary `-modfile` cleanup and its support for ordinary
|
||||
package dependencies as well as registered tools; a bare `go tool` command
|
||||
cannot assume every consumer has a permanent tool declaration. Keep explicit
|
||||
record selection and distinct exit statuses. Add an assembled cold-build test
|
||||
that executes the printed command after the original Go driver and launcher
|
||||
have exited, rather than only testing a directly built CLI or a warm cache.
|
||||
|
||||
## R08: Reuse the existing repository-path semantics for record storage — P2
|
||||
|
||||
**Relevant code:** `internal/publish/record.go:112`, especially line 117;
|
||||
`internal/publish/attempts.go:74`, especially line 82; the existing
|
||||
`internal/git/source.go:39` and `source.go:50` repository-root helpers.
|
||||
|
||||
The pre-existing root resolver removes Git's output newline and resolves
|
||||
filesystem aliases, preserving valid whitespace in the path itself. The new
|
||||
recording and discovery resolvers independently use `strings.TrimSpace`, which
|
||||
changes repository identity when a directory name ends in whitespace. Recording
|
||||
then uses that altered root both for relative overrides and for deciding whether
|
||||
an absolute override lies inside the candidate worktree.
|
||||
|
||||
**Evidence:** In a temporary checkout whose final directory component was
|
||||
`consumer `, the existing `git.RepositoryRoot` preserved the exact path and
|
||||
the existing candidate `Check` completed successfully. The new code produced
|
||||
three inconsistent results:
|
||||
|
||||
- A relative `.releasekit/attempts` override tried to use the different sibling
|
||||
path under `consumer` and failed its Git ignore check, despite the real
|
||||
`.releasekit/` location being ignored.
|
||||
- After a record was saved in the correct absolute ignored location, relative
|
||||
discovery selected the wrong sibling directory and returned a successful
|
||||
empty collection. The actual record still existed in the requested store.
|
||||
- An absolute override inside the actual worktree was misclassified as
|
||||
external because containment used the trimmed root. It successfully wrote an
|
||||
unignored record, which Git reported as untracked.
|
||||
|
||||
**Expected behavior:** Publication, discovery, relative overrides, and worktree
|
||||
containment use the same repository identity already supported by the library.
|
||||
Path data remains intact. This failure is independent of R02's filename mismatch:
|
||||
the containment decision itself uses the wrong root.
|
||||
|
||||
**Remediation:** Reuse `internal/git.RepositoryRoot` and share metadata-path
|
||||
resolution through the Git boundary, coordinating with R04 rather than adding
|
||||
another resolver. Remove output terminators without stripping path characters.
|
||||
Apply the same preservation to common-directory output. Add focused regressions
|
||||
for a valid root ending in whitespace, relative-store read/write agreement, and
|
||||
absolute unignored-path rejection. Preserve existing filesystem-alias and
|
||||
linked-worktree behavior instead of introducing new pathname restrictions.
|
||||
|
||||
## R09: Align maintainer recovery instructions with the source bootstrap — P2
|
||||
|
||||
**Relevant code/docs:** `scripts/release:9`; `docs/release.md:104` and
|
||||
`release.md:149`; `docs/roadmap/publication-reconciliation.md:21`.
|
||||
|
||||
The existing maintainer procedure builds and executes
|
||||
`.releasekit/bin/releasekit`. It does not install an executable named
|
||||
`releasekit` on `PATH`. The new lost-output recovery instructions instead run
|
||||
`releasekit publication-attempts` and `releasekit reconcile` without identifying
|
||||
the executable that the procedure prepared.
|
||||
|
||||
**Evidence:** With Go and Git available on `PATH` and only the source-built CLI
|
||||
in `.releasekit/bin`, the newly documented discovery command returned 127
|
||||
(`releasekit: not found`). The equivalent command using
|
||||
`./.releasekit/bin/releasekit` returned 0 and listed the saved attempt. A globally
|
||||
installed CLI could also be a different version from the source-built one.
|
||||
|
||||
The recovery-section rename additionally left the feature roadmap's link to
|
||||
`docs/release.md#resolve-failures` pointing at a heading that no longer exists.
|
||||
|
||||
**Expected behavior:** The repository's executable runbook uses the CLI prepared
|
||||
by its own prerequisites, and its recovery links resolve to the current owner.
|
||||
It requires no additional global installation for lost-output recovery.
|
||||
|
||||
**Remediation:** Use the source-built executable for discovery and reconciliation
|
||||
in the maintainer runbook, consistent with its existing delivery commands.
|
||||
Retain explicit instructions for selecting another supported CLI when the
|
||||
original executable is genuinely unavailable. Repair the renamed-section link.
|
||||
Verify the recovery snippets in an environment without a global `releasekit`
|
||||
executable; keep generic consumer command examples in their consumer-facing
|
||||
documentation.
|
||||
|
||||
## R10: Preserve endpoint bytes across the private-record handoff — P2
|
||||
|
||||
**Relevant code:** `internal/publish/record.go:77` and its evidence decoding;
|
||||
the string fields in `internal/publish/reconcile.go`'s `Evidence`; the existing
|
||||
Git endpoint resolution/query path in `internal/git/remote.go` and `tag.go`.
|
||||
|
||||
On POSIX, the existing Git transport can use local repository paths containing
|
||||
bytes that are not valid UTF-8. Typed evidence retains those bytes in its Go
|
||||
strings. The new private JSON codec uses `json.Marshal` directly on those
|
||||
strings, which replaces invalid UTF-8 with the Unicode replacement character
|
||||
without reporting an error. Loading the record therefore supplies a different
|
||||
endpoint to the existing Git observer.
|
||||
|
||||
**Evidence:** A disposable valid candidate's publication attempt targeted a local
|
||||
repository whose path contained a non-UTF-8 byte. Its hook rejected the push;
|
||||
publication returned `not_published` with a complete saved record and the exact
|
||||
original endpoint in typed evidence. A second local repository, named using the replacement-character
|
||||
form of that path, held the same intended annotated tag object. Direct typed
|
||||
reconciliation correctly returned `not_published` at the original destination.
|
||||
Loading the saved record changed its endpoint to the second repository and
|
||||
record-based reconciliation returned `published` there. The record save/load
|
||||
reported no error.
|
||||
|
||||
**Expected behavior:** Direct and record-loaded evidence identify the same
|
||||
captured destination. The private handoff retains exact transport identity,
|
||||
including pathname bytes supported by the existing Git boundary, or reports
|
||||
that the handoff could not be saved faithfully. Diagnostic JSON is a display
|
||||
format and can use safe displays; the private record supplies actual operation
|
||||
inputs and must preserve their identity.
|
||||
|
||||
**Remediation:** Make private serialization lossless for supported evidence,
|
||||
using an explicit byte representation where needed. At minimum, detect values
|
||||
that the selected record format cannot round-trip and return a separate
|
||||
recording failure with no misleading saved handoff. Keep the original typed
|
||||
evidence and publication behavior intact; avoid adding a general UTF-8
|
||||
restriction to previously valid Git publication inputs. Cover endpoint/path
|
||||
round trips and equivalent direct-versus-loaded observations, including a
|
||||
distinct replacement-character path, in focused regression tests.
|
||||
|
||||
Reference in New Issue
Block a user