diff --git a/docs/releases/v0.9.0.md b/docs/releases/v0.9.0.md new file mode 100644 index 0000000..5c1d278 --- /dev/null +++ b/docs/releases/v0.9.0.md @@ -0,0 +1,41 @@ +# ReleaseKit v0.9.0 + +ReleaseKit's maintained launchers and generated Go recovery commands now +prepare and run the release tool for the Go host when inherited `GOOS` and +`GOARCH` select another target. Release profiles still select the targets for +application builds. + +## Launcher, delivery, and recovery fixes + +- Local launchers and Woodpecker templates select the host before preparing + tool dependencies or compiling the CLI. Module-based launchers invoke + ReleaseKit by its full Go package path, so another registered tool named + `releasekit` does not make the invocation ambiguous. +- Generated Go recovery commands select the host when run in a POSIX shell + and find the Go executable through `PATH`. Recovery for API-only consumers + preserves both wildcard and version-specific module replacements when + relocating relative paths, fixing a panic after a failed publication. +- The manual hosted-page retry stops if fetching or checking out the tag fails, + or if local edits remain. It checks the tagged checkout before downloading + dependencies or building the CLI. + +## Compatibility and upgrade + +The module path remains `go.maximumdirect.net/releasekit` and the Go consumer +minimum remains 1.26.0. Public Go APIs, release profiles, and publication +record formats are unchanged. + +Update copied launchers and CI templates to get the host and tool selection +fixes. Updating the module dependency alone does not update those files. +Configured product checks inherit the launcher's host target; if a check +needs an application target, set it explicitly in the check's `env` map +or command. + +See the [adoption guide](https://forge.maximumdirect.net/base-layer/releasekit/src/tag/v0.9.0/docs/consumers/adoption.md), +[delivery guide](https://forge.maximumdirect.net/base-layer/releasekit/src/tag/v0.9.0/docs/consumers/delivery.md), +and [maintainer runbook](https://forge.maximumdirect.net/base-layer/releasekit/src/tag/v0.9.0/docs/release.md) +for updated recipes. + +[COPYRIGHT](https://forge.maximumdirect.net/base-layer/releasekit/src/tag/v0.9.0/COPYRIGHT) | +[LICENSE](https://forge.maximumdirect.net/base-layer/releasekit/src/tag/v0.9.0/LICENSE) | +[ATTRIBUTION](https://forge.maximumdirect.net/base-layer/releasekit/src/tag/v0.9.0/docs/oss/attribution.md) diff --git a/docs/roadmap/commandkit-feature-request.md b/docs/roadmap/commandkit-feature-request.md deleted file mode 100644 index c621f4b..0000000 --- a/docs/roadmap/commandkit-feature-request.md +++ /dev/null @@ -1,93 +0,0 @@ -# CommandKit feature request: host-safe consumer launchers - -Status: proposed; not an accepted contract or implementation assignment. - -## Consumer need - -CommandKit selects ReleaseKit v0.8.0 as a Go tool and uses a small launcher to -publish its core and two optional integration modules. Maintainers can inherit -`GOOS` and `GOARCH` from application cross-build work. The release tool must run -on the maintainer's host even when those variables name another target. - -CommandKit currently handles this by querying `go env GOHOSTOS` and -`go env GOHOSTARCH`, then using those values when building its native ReleaseKit -executable. Its `TestReleaseLauncherDelegatesPublication` test supplies foreign -target variables and checks native bootstrap behavior together with argument -and exit-status forwarding. Consumer evidence is in CommandKit's -`scripts/release` and `scripts/release_test.go` at local commit -`a74fcb20041cbccde93f9b9a1e3b24cb8dd131e7`. - -Please incorporate this behavior and regression coverage into ReleaseKit's -maintained consumer launcher example so downstream projects can reuse the -recipe and rely on upstream tests for its generic bootstrap mechanics. - -## Observed failure - -Reviewed ReleaseKit v0.8.0, commit -`23203f235cc414c4ffe0fd3941030eb0c9f9943f`. The -[multi-module launcher](../../examples/multi-module/release.sh) invokes -`go tool` without normalizing the inherited target environment. - -On Linux/amd64 with Go 1.26.8, from a prepared consumer module registering -`go.maximumdirect.net/releasekit/cmd/releasekit` at v0.8.0: - -```sh -GOWORK=off GOTOOLCHAIN=local GOFLAGS=-mod=readonly \ - GOPROXY=off GONOPROXY=none GOSUMDB=off \ - GOOS=windows GOARCH=arm64 go tool releasekit version -``` - -The command built a Windows executable and failed to launch it with an -`exec format error`. Repeating the command with `GOOS=linux GOARCH=amd64` -succeeded and printed `releasekit v0.8.0`. These observations establish the -tool-execution failure; the complete upstream launcher was inspected rather -than executed in this reproduction. No publication was attempted. - -## Requested change - -Update the maintained launcher to select the native host target when preparing -and executing ReleaseKit, regardless of inherited application target variables. -Resolve the host through the selected Go toolchain rather than hard-coding a -platform. Scope this change to running the maintainer tool; preserve the -application targets selected through existing release profiles. - -Retain the launcher's existing module/version selection, profile routing, -source-file preservation, workspace/toolchain settings, and child exit-status -behavior. Document the host-target handling in the -[consumer adoption guidance](../consumers/adoption.md) where appropriate. - -Prefer a small change to the supported example and its tests. This request does -not require a new validation option, public bootstrap API, or test-helper -package. CommandKit's choice to select one tool version from its root module -remains consumer-owned; this request does not change ReleaseKit's example policy -of selecting the tool from the chosen module. - -## Acceptance evidence - -Extend the existing -[multi-module launcher tests](../../cmd/releasekit/multi_module_example_test.go) -with a controlled foreign-target environment. Demonstrate that: - -1. The selected tool executes on the host despite foreign `GOOS` and `GOARCH`. -2. The selected module's tool version and publication arguments still reach the - child, and child statuses 0, 1, 2, and 3 are preserved. -3. Source module files remain unchanged and existing bootstrap cleanup behavior - is preserved. - -Reuse existing coverage for unaffected behavior rather than adding a parallel -suite. A host-run test can use a synthetic local tool and needs no real release -or external service. Synthetic module downloads must use test-owned caches as -described in [the developer guide](../development.md#testing-synthetic-modules). - -## Ownership and downstream follow-up - -CommandKit's review found no locally maintained candidate validator that needs -to become a new ReleaseKit feature. Existing formatting, Markdown, and module -distribution options already cover their respective reusable checks; selecting -them as CommandKit release gates is a separate consumer decision. - -ReleaseKit already owns publication-engine tests for annotated tags, exact -remote objects, nested tag prefixes, and endpoint preflight. CommandKit can -narrow its local publication exercise to its launcher and profile wiring -without upstreaming a duplicate engine test suite. Its selectors, product names, -publication endpoint, and release order remain local responsibilities. diff --git a/docs/roadmap/host-native-launchers.md b/docs/roadmap/host-native-launchers.md deleted file mode 100644 index 32265ed..0000000 --- a/docs/roadmap/host-native-launchers.md +++ /dev/null @@ -1,78 +0,0 @@ -# Roadmap: host-native consumer launchers - -Status: implemented. - -## Purpose - -Make ReleaseKit's maintained launchers and generated recovery commands prepare -and execute the release tool for the maintainer's host when inherited `GOOS` -and `GOARCH` select a different application target. Centralize the recipe and -regression coverage so consumers can retain small launchers and product-specific -wiring tests. - -This is a launcher correctness improvement. It requires no public bootstrap API, -validation option, or test-helper package. This roadmap selects no release version. - -## Baseline before implementation - -The [CommandKit feature request](commandkit-feature-request.md) describes a -prepared v0.8.0 tool invocation on Linux/amd64 with `GOOS=windows GOARCH=arm64` -that built a Windows executable and failed with an exec format error. -CommandKit's local launcher already selects the Go host target before -building the release executable. The report's reproduction exercised tool -execution without attempting publication. - -The maintained [multi-module launcher](../../examples/multi-module/release.sh) -already selected a module and profile, copied module metadata into temporary -storage, prepared the selected ReleaseKit dependency, and invoked -`go tool releasekit`. It set `GOWORK=off` and `GOTOOLCHAIN=local` while passing -inherited `GOOS` and `GOARCH` to dependency inspection and tool execution. - -The [adoption guide](../consumers/adoption.md) included a minimal `go tool` -launcher. The [single-module CI template](../../examples/woodpecker/tag-delivery.yml) -and [multi-module CI template](../../examples/multi-module/woodpecker.yml) also -invoked the selected tool without explicit host-target normalization. - -The [maintainer launcher](../../scripts/release), -[source delivery template](../../examples/woodpecker/releasekit-source-delivery.yml), -and [manual delivery retry](../release.md) built and executed the tool with the -same inherited-target assumption. The CLI's [generated recovery command](../../cmd/releasekit/main.go) -inherited the target from the shell where it ran, and [recovery dependency -preparation](../../internal/gotool/recovery.go) also inherited the target. - -Existing [launcher tests](../../cmd/releasekit/multi_module_example_test.go) -exercised synthetic module selection, publication arguments, child statuses -0 through 3, and preservation of source module metadata. Those fixtures provided -the starting point for foreign-target regression coverage. - -## Targeted end state - -Maintained launchers and module-based recovery use `GOHOSTOS` and `GOHOSTARCH` -from the selected Go toolchain as `GOOS` and `GOARCH` while preparing and running -ReleaseKit. The tool executes on that host even when the caller was working with -a foreign application target. Normalize before target-dependent dependency -inspection as well as tool execution. Generated recovery commands select the -host when run, using the Go toolchain that will execute the command. - -Application targets continue to come from existing release profiles. The -launcher's host selection preserves module-selected tool versions, profile and -argument routing, source-file preservation, temporary-file cleanup, existing -workspace/toolchain settings, and child exit statuses. Consumers retain their -own selectors, products, endpoints, and release order. - -The scope is inherited `GOOS` and `GOARCH` handling for the maintainer tool. It -does not establish a hermetic environment, broaden hardware or platform support, -or add release gates. Existing Go/toolchain failures remain ordinary bootstrap -failures. No new fallback toolchain or download policy is introduced. - -The [implementation plan](implementation.md) defines the work to reach this -state. - -## Completion evidence - -Completion requires host selection before dependency inspection and successful -host execution under a foreign target, including for generated recovery commands. -Preserve selected tool identity and arguments, child-status forwarding, unchanged -source metadata, and existing cleanup behavior. The maintained local and CI -recipes and adoption guidance must use the same host-selection approach. -Application build targets must retain their existing profile-driven behavior. diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md deleted file mode 100644 index 2027539..0000000 --- a/docs/roadmap/implementation.md +++ /dev/null @@ -1,193 +0,0 @@ -# Implementation plan: host-native ReleaseKit launchers - -Follow the stages in order as one sprint. The -[feature roadmap](host-native-launchers.md) defines the intended behavior and -scope. The focused checks below describe regression coverage; intermediate -stages need no separate review, release, or passing build. Run the assembled -checks in Stage 6. - -## Stage 1: Normalize the local multi-module launcher - -**Completed.** - -1. In [the multi-module launcher](../../examples/multi-module/release.sh), after - entering the selected module and exporting its existing `GOWORK=off` and - `GOTOOLCHAIN=local` settings, insert the following before the first Go - bootstrap command. Reuse this shell recipe in the subsequent stages: - - ```sh - releasekit_host_os=$(go env GOHOSTOS) || exit $? - releasekit_host_arch=$(go env GOHOSTARCH) || exit $? - export GOOS="$releasekit_host_os" GOARCH="$releasekit_host_arch" - ``` - - Keep the checked assignments separate from `export` so a failed Go query - stops the launcher. Keep host selection inside the existing launcher scope; - preserve selected module metadata, dependency preparation, publication - arguments, traps, and the child status. Application targets still come from - release profiles. - -2. Extend [the existing launcher tests](../../cmd/releasekit/multi_module_example_test.go). - In `TestMultiModuleLauncherUsesSelectedModuleAndProfile`, let the Go shim - answer the two host queries and assert the exported target at - `go list -deps` in the selected module. In - `TestMultiModuleLauncherRunsToolFromSelectedDependency`, supply a foreign - target to the launcher and retain real Go tool preparation and execution. - Keep the checks for the nested module's version, forwarded profile and - arguments, statuses 0 through 3, unchanged source, and bootstrap cleanup. - For these fixtures, choose `windows` unless the host OS is `windows` - (then `linux`), and `arm64` unless the host architecture is `arm64` (then - `amd64`). This makes both variables differ from the host pair. Keep - synthetic versions in a test-owned `GOMODCACHE`. - -## Stage 2: Normalize consumer Woodpecker templates - -**Completed.** - -1. Apply the Stage 1 recipe in the - [single-module](../../examples/woodpecker/tag-delivery.yml) and - [multi-module](../../examples/multi-module/woodpecker.yml) tag delivery - commands, under the existing Go settings and in the selected module. Insert - it after tag verification and checkout, before the first Go bootstrap - command. Write the new shell variable references as - `$${releasekit_host_os}` and `$${releasekit_host_arch}` so Woodpecker - passes them through. Keep the verified commit, selected tool, profile - mapping, credentials, alternate module and sum files, cleanup, and - `ci-deliver --prepared` invocation. - -2. Update the existing - [single-module](../../cmd/releasekit/woodpecker_test.go) and - [multi-module](../../cmd/releasekit/multi_module_woodpecker_test.go) workflow - harnesses. Teach the controlled Go shim to answer host queries before - enforcing its alternate module file checks. In both Go shims, log the - target at `go list -deps`. Feed a foreign target to - `TestWoodpeckerTagDeliveryTemplate`, - `TestWoodpeckerTagDeliveryUsesTaggedModuleVersion`, - and `TestMultiModuleWoodpeckerDelivery` and check that dependency inspection - uses the host pair. Retain real execution where the fixture already uses it, - and keep the existing checks that a rejected tag or commit reaches no Go - command. - -## Stage 3: Normalize source-built launchers - -**Completed.** - -1. Apply the same recipe in the [maintainer launcher](../../scripts/release) - before building and executing ReleaseKit. Prefix the two queries with - `GOTOOLCHAIN=local` to match its existing build. Preserve usage checking, - output location, build settings, and the final `exec`. -2. Apply the recipe in the - [source delivery template](../../examples/woodpecker/releasekit-source-delivery.yml) - after tag verification and checkout, under its existing Go settings and - before the source build. Use the Woodpecker escaping from Stage 2. Keep - its temporary build output, dependency preparation through the existing - build, and prepared delivery arguments. - Exercise `TestWoodpeckerSourceDeliveryTemplateSelfBootstraps` with a foreign - target; log and assert the host pair at the real `go build`, retain actual - execution, and keep the checkout, cleanup, and dependency preparation - assertions. -3. Update the manual delivery retry in the [maintainer runbook](../release.md). - After fetching and checking out the tagged version, run host selection, - dependency download, build, and delivery inside a subshell so the caller's - target environment stays intact. Query with `GOTOOLCHAIN=local` as above; - make the sequence stop on failure inside the subshell. Preserve the - dependency download before the offline build, the build-only proxy and sum - settings, the output path, and the older-tag credential example. - -## Stage 4: Normalize module-based recovery preparation - -**Completed.** The CLI adapter and assembled handoff tests follow in the next stage. - -1. In [the Go recovery implementation](../../internal/gotool/recovery.go), - keep the existing registered-tool, API-only, and no-dependency selections. - Have `RecoveryTool` return `(*RecoveryInvocation, error)` within the - internal package. Give the descriptor `Directory string` and - `Args []string` fields. A nil result still selects the native executable fallback. - Set the directory to the original module for a registered tool or the - durable bootstrap root for an API-only consumer. Put `tool -modfile=` - and the existing full command package path in `Args`; the caller supplies - the working directory. Preserve the selected version, local and versioned - replacements, private module and sum files, and their existing lifetime. -2. Immediately before the API-only branch runs `go list -deps`, query - `go env -json GOHOSTOS GOHOSTARCH` in its private root with its existing - `GOWORK=off` and `GOTOOLCHAIN=local` overrides. Reuse the internal - `discover` runner with the existing context. Decode both fields, treat - missing values as a preparation error, and add only `GOOS` and `GOARCH` - to the environment of the dependency command. Keep the target-independent - module-selection query and the registered-tool early return. Use the - existing preparation error and cleanup paths; leave candidate Go settings, - proxy policy, and fallback selection as they are. -3. Update the direct `RecoveryTool` callers in - [the handoff tests](../../cmd/releasekit/publication_handoff_test.go) to run - the descriptor's arguments in its directory. In - `TestRecoveryToolPreservesVersionedReplacement`, set a foreign target for - the preparation call, log the target of the real `go list -deps` through a - delegating shim, and verify the host pair there. Run the test's subsequent - direct tool invocation with the host pair and retain its version and - metadata assertions. Retain the source-module and vendored API-only cases. - Update the CLI caller in Stage 5. - -## Stage 5: Normalize the printed recovery command when it runs - -**Completed.** - -1. In `selectedPublicationRecoveryCommand` in - [the CLI](../../cmd/releasekit/main.go), render the descriptor as one - copyable POSIX shell command. Use this structure, with ``, - ``, and `` standing for shell-quoted operands: - - ```sh - (export GOWORK=off GOTOOLCHAIN=local && releasekit_host_os=$(go -C env GOHOSTOS) && releasekit_host_arch=$(go -C env GOHOSTARCH) && GOOS="$releasekit_host_os" GOARCH="$releasekit_host_arch" go -C tool '-modfile=' reconcile --record ) - ``` - - Build the common `go -C` prefix from the descriptor directory and render - its tool arguments using the existing diagnostic redaction and shell - quoting. Keep the explicit absolute record operand. Both host queries run - in the invocation directory with the same Go settings as the final command. - A failed query stops the command, and the subshell returns the final command - status when launched. Leave the native executable command, handoff timeout - and cancellation behavior, and existing fallback and diagnostic behavior - intact. -2. In `TestColdGoToolRecoverySurvivesLauncherExit`, execute the printed - command in a separate shell with a foreign target, after the original - launcher and its temporary files are gone. Exercise both registered and - API-only handoffs and retain status checks for 0 through 3. For the - alternate-record cases, replace the quoted original record operand inside - the command instead of appending flags after the closing subshell. - Retain the vendored API-only, native fallback, record-selection, and - redaction checks. Exercise the printed command itself; running the Go - descriptor directly would miss a regression in the shell recipe. - -## Stage 6: Align consumer guidance and verify the sprint - -**Completed.** - -1. In the [adoption guide](../consumers/adoption.md), make the minimal - launcher use the Stage 1 recipe followed by `exec go tool releasekit "$@"`. - Run it from the selected module, retaining that consumer's workspace and - toolchain settings. Move the registration smoke check to follow the launcher - example and invoke `version` through that launcher. In the - [multi-module README](../../examples/multi-module/README.md), apply the same - recipe to the direct `check` smoke commands: enter each selected module in - its own subshell, select the host there, then run the existing check with - its repository-root profile and version. Preserve the source-selected - fixture instructions and avoid turning the smoke check into publication. -2. Align the [consumer delivery guide](../consumers/delivery.md), - [CLI contract](../cli.md), and [internal Go tool guide](../internal/gotool.md) - with the maintained recipes and handoff behavior. Explain that consumers - update copied launchers and templates separately from upgrading the module - dependency. ReleaseKit tests generic bootstrap behavior; consumers can - focus their own wiring tests on selectors and profile mapping. Profile - build targets continue to select application binaries. -3. Run the focused launcher, workflow, and recovery fixtures from the preceding - stages with the existing local Git and module proxy fixtures. Run - `go test ./...` and `go run ./internal/apicheck` as documented in the - [development guide](../development.md#validation). Check shell syntax, - including Woodpecker command blocks after interpolation, local documentation - links, and `git diff --check`. Use the existing offline fixtures; no hosted - delivery, live publication, or external runner is needed. -4. Include the following entry in the next release note during release - preparation, when its version is selected: “Maintained launchers and - generated Go recovery commands now use the host target to prepare and run - ReleaseKit. Update copied launchers and CI templates to get the fix; - release profiles still select application build targets.” diff --git a/docs/roadmap/remediation.md b/docs/roadmap/remediation.md deleted file mode 100644 index 2c2a784..0000000 --- a/docs/roadmap/remediation.md +++ /dev/null @@ -1,289 +0,0 @@ -# Host-native launcher review findings - -Reviewed the implementation from `bf07bed` through `0269648` against the -[feature roadmap](host-native-launchers.md) and -[implementation plan](implementation.md). - -The maintained launchers select the Go host before preparing and running the -tool. API-only recovery prepares its private module on the host; printed Go -handoffs discover the host again when run. The CLI retains the native fallback -and saved-record selection. Application build targets still come from the -profile, and the public Go facades and dependency boundaries are unchanged. - -## Make the adoption smoke command runnable - -**Completed.** The smoke command runs the saved launcher through `sh`; verified with a non-executable file and a foreign target. - -**Evidence:** The [adoption guide](../consumers/adoption.md#cli-tool-dependency) -tells readers to save the launcher as `releasekit` and run -`./releasekit version`. A newly saved shell file ordinarily lacks its execute -bit. In a disposable directory, the documented command failed with status 126 -and “Permission denied.” Invoking the same snippet through `sh` ran a -module-registered fixture tool on Linux/amd64 with inherited -`GOOS=windows GOARCH=arm64`. - -**Fix:** Use `sh ./releasekit version` in the smoke instructions, or give an -explicit `chmod +x ./releasekit` step before direct execution. Verify the -published instructions from a newly created file under a foreign target. - -## Limit the recovery guarantee to GOOS and GOARCH - -**Completed.** The CLI reference names the two target variables and identifies the shell syntax. - -**Evidence:** The [CLI contract](../cli.md) says Go handoffs select the host -“independently of inherited cross-compilation settings.” The -[command formatter](../../cmd/releasekit/main.go) overrides `GOOS` and -`GOARCH` and retains other build settings. For example, building the actual CLI -with the native target, `CGO_ENABLED=1`, and an unavailable C compiler named -by `CC` still fails in `runtime/cgo`. This is consistent with the roadmap's -scope, but the CLI wording implies broader independence. - -**Fix:** Say that generated Go handoffs set `GOOS` and `GOARCH` from the -selected Go toolchain at invocation time. Preserve the other inherited settings -and the existing workspace and toolchain overrides. The documented handoff -uses POSIX shell syntax; identify that syntax when telling operators where to -paste the command. - -## Exercise a profile target that differs from the launcher host - -**Completed.** The binary delivery fixture selects a non-host Linux target and checks the uploaded ELF machine as well as the filename. - -**Evidence:** The binary-delivery case in -[`TestMultiModuleWoodpeckerDelivery`](../../cmd/releasekit/multi_module_woodpecker_test.go) -supplies a foreign target to the workflow and checks the uploaded -`core-v2.1.0-linux-amd64` asset. The profile itself selects `linux/amd64`. -On the Linux/amd64 review host, the test cannot distinguish using the -profile target from accidentally using the normalized launcher target. The -current build code still applies the selected profile target explicitly. - -**Fix:** In that fixture, select a supported profile target distinct from the -host and assert the uploaded asset matches it. Where practical, inspect the -uploaded executable header as well as the name, so the test catches a -host-built payload with a profile-derived filename. Keep the existing local -forge fixture and native version check; the cross-built asset need not run on -the host. - -## Reconcile the feature roadmap with the completed implementation - -**Completed.** The feature roadmap records implementation and labels the pre-change baseline. The release-note entry remains in the implementation plan. - -**Evidence:** The [feature roadmap](host-native-launchers.md) still declares -“implementation pending” and describes the pre-change launchers as the current -baseline. All six stages in the [implementation plan](implementation.md) are -marked complete, and the maintained launchers and recovery code implement the -host selection described there. - -**Fix:** Mark the roadmap implemented and label the old baseline as historical, -or retire the completed planning documents after review. Preserve the planned -release-note entry for the next selected release version. - -## Verification - -`go test ./...`, `go run ./internal/apicheck`, and syntax checks for the -maintained shell scripts passed. The Go suite exercised real tool preparation, -Woodpecker command blocks with local fixtures, and later execution of printed -recovery commands. Disposable smoke probes also verified the maintainer -launcher forwards the child status and the manual retry preserves the calling -shell's target variables. The review made no changes to production code or -tests. - -## Integration audit - -The audit below examines how the new bootstrap paths interact with existing -invocation recipes, product checks, and publication recovery. - -### Use the external Go executable in printed recovery commands - -**Completed.** All three Go invocations use external lookup through `PATH`. The handoff regression reproduces the shell-function failure before the fix and verifies reconciliation afterward. - -**Evidence:** Before host normalization, the -[command formatter](../../cmd/releasekit/main.go) emitted -`env GOWORK=off GOTOOLCHAIN=local go ...`, which resolves Go through `PATH`. -[Recovery preparation](../../internal/gotool/recovery.go) still executes an -external Go command. The new printed command uses bare `go` for both host -queries and the later tool invocation, so a function named `go` in the -operator's shell can intercept it. In a local rejected-push fixture, the -actual printed command reconciled the saved record in an ordinary shell. -With `go() { GOOS=windows GOARCH=arm64 command go "$@"; }` defined, the same -command failed with an exec format error. The function overrode the final -assignments even though the host queries reported the correct host. In this -case both paths exited with status 1, but only one produced a reconciliation -result. - -**Fix:** Restore external command lookup for the host queries and tool -invocation, for example by using `env go -C ...` throughout the subshell. -Keep selection through the operator's current `PATH`, including executable -shims, and keep the exact record and exit status. Exercise a generated -handoff from a POSIX shell that defines a cross-build Go function; assert -the reconciliation result as well as the status. - -### Explain how host normalization affects configured product checks - -**Completed.** The adoption guide explains inherited host variables and points checks that need an application target to the existing environment overlay. - -**Evidence:** The updated launchers export `GOOS` and `GOARCH` to the -ReleaseKit process. Existing [configured checks](../config.md) inherit that -process environment unless their `env` maps override it; -[`RunChecks`](../../cmd/releasekit/internal/cli/config.go) adds the configured -and ReleaseKit-owned values to the child. This differs from built-in -application compilation, which chooses its target from the profile. A -disposable consumer's check using shell defaults for an application target -passed with the previous direct `go tool` invocation and no target -variables, then failed through the updated launcher because it observed -`linux/amd64`. Adding explicit `GOOS=windows` and `GOARCH=arm64` to that -check's `env` restored its behavior. - -**Fix:** In the launcher adoption guidance, explain that host variables -reach configured child checks and that checks which need an application -target should set it explicitly in their existing `env` map or command. -Link to the configuration contract. Keep the current environment overlay -semantics and the separate profile selection for built-in builds. - -### Align direct source and standalone CI recipes with the launcher guidance - -**Completed.** The source examples link to host selection, and the standalone install recipe scopes the same settings to `go install`. Both paths were verified under a foreign target. - -**Evidence:** The [README quick start](../../README.md) and -[CLI reference](../cli.md) still recommend bare `go run ./cmd/releasekit` -for source invocation. The [consumer delivery guide](../consumers/delivery.md#woodpecker-tag-workflows) -also recommends bare `go install ...@PIN` for consumers who install the -delivery executable separately. These older recipes remain target-sensitive -alongside the new host-native launchers. On Linux/amd64 with inherited -`GOOS=windows GOARCH=arm64`, the README's actual `version` command failed -with an exec format error. A versioned install from a disposable local -module proxy failed under the same variables with `GOBIN` set; the native -install succeeded and its binary ran. - -**Fix:** Link these recipes to the host-selection instructions and make the -native target requirement explicit at the source invocation and at the -standalone installation step. Use the existing recipe where a copyable -command is useful. Keep the selected version, install location, and -`go run` exit-status caveat. An already installed native binary does not -need a new bootstrap step. - -## Algorithmic audit - -The host normalization itself adds a fixed number of Go invocations per -launch. The CLI renders a fixed argument list, so quoting and formatting take -time and space linear in the lengths of the directory and record paths. Go -dependency inspection and compilation retain their existing roles. The -findings below concern a pre-existing algorithm on the audited recovery path -and a new module-selection defect in the smoke recipe. - -### Relocate recovery replacements in one pass without merging selectors - -**Completed.** Recovery rewrites relative destinations in one pass. The CLI regression verifies both selectors, the selected tool, the rejected-push status, and later reconciliation. - -**Evidence:** The API-only path in -[`RecoveryTool`](../../internal/gotool/recovery.go) walks the parsed -`go.mod` replacements. It calls `modfile.AddReplace` for each relative -local replacement. In the pinned `golang.org/x/mod` implementation, -each call scans the entire replacement slice. With `R` total directives -and `L` relative local directives, relocation visits `O(R × L)` entries, -and takes quadratic time when all or most entries require relocation. -This loop predates host normalization and still runs before the new host -query. - -The same loop can panic on a valid module that declares both an unversioned -and a version-specific replacement for the same dependency. When the -unversioned directive appears first and uses a relative path, calling -`AddReplace` with an empty old version treats both directives as matches -and clears the version-specific entry. The outer loop then visits that -cleared entry as if it were another relative replacement. In a disposable -API-only consumer with both forms for ReleaseKit, Go accepted the module -and validation passed. After a local hook rejected the tag push, ReleaseKit -saved the publication record, then panicked in `modfile.addReplace`. It -exited with status 2 instead of reporting the rejected push and a recovery -handoff. - -**Fix:** Rewrite each relative right-hand path on its existing parsed -directive in one pass, preserving the exact left-hand path and version -selector. For example, update the typed replacement and its corresponding -syntax token before formatting. This takes `O(R)` directive visits plus -the bytes processed for the paths and avoids selector merging. Keep the -existing relocation policy, the separate synthetic replacement for the -original module, and the private module lifetime. Add a recovery fixture -with distinct local destinations for a wildcard and a version-specific -replacement. Check that the handoff preserves both directives, selects the -version-specific destination, and reports the original publication status. - -### Anchor smoke-check module paths to the intended checkout - -**Completed.** Both nested paths use `./`; verified the full smoke recipe with a conflicting `CDPATH`. - -**Evidence:** The [multi-module smoke recipe](../../examples/multi-module/README.md) -replaced `go -C integrations/...` with subshells that run -`cd integrations/...` before host discovery. A shell with `CDPATH` set can -resolve these relative names in another checkout. In a disposable probe -with matching module paths under two directories, the published snippet -sent both nested checks to the checkout named by `CDPATH` and returned -success. If the other checkout has valid profiles and tools, the smoke -result can appear valid for the wrong candidate. - -**Fix:** Use `cd ./integrations/ckconfig` and `cd ./integrations/core/v2`, -or clear `CDPATH` for those two directory changes. The number of lookups is -already fixed; the change corrects which directory is selected. Verify the -copyable recipe with a conflicting `CDPATH` entry. - -## Follow-up review after remediation - -Reviewed all six implementation stages at `9797f43`. The focused launcher, -delivery, and recovery tests and the public API and dependency checks passed. -The host discovery work remains bounded to a fixed number of calls per -invocation, and recovery replacement relocation now visits each directive -once. No additional scaling issue was identified. - -### Stages 1, 2, and 6: Invoke the exact registered Go tool - -**Completed.** The launchers, templates, and consumer recipes use the full -package path. The real Go launcher regression verifies tool selection and -exit statuses with a second registered tool of the same short name. - -**Evidence:** The [multi-module launcher](../../examples/multi-module/release.sh) -and both consumer Woodpecker templates -([single-module](../../examples/woodpecker/tag-delivery.yml), -[multi-module](../../examples/multi-module/woodpecker.yml)) register the -full ReleaseKit package path, but invoke the short name `releasekit`. The -[adoption launcher](../consumers/adoption.md#select-the-go-host-target) and -[multi-module smoke recipe](../../examples/multi-module/README.md) also use -the short name. Go allows two different package paths with the same final -component in the tool declarations. With a second local tool ending in -`/cmd/releasekit`, the maintained launcher and both extracted CI command -blocks prepared the intended module, then exited with an ambiguous-tool -error. Changing only the invocation to the full package path ran the -intended host-native tool in all three probes. Recovery already invokes -the full path and is unaffected. - -**Fix:** Invoke the same full package path used for registration in the -maintained launchers, templates, and copyable consumer recipes. Preserve -the selected module version, arguments, and exit status. Extend an -existing launcher fixture with a second registered tool of the same short -name and verify that only the intended tool runs. - -### Stage 3: Build the manual retry tool only from a clean tagged checkout - -**Completed.** The retry resolves and checks out the fetched tag and checks -cleanliness before invoking Go. Local probes verified that fetch failure, -checkout failure, and remaining edits stop before Go, and a clean retry -builds and runs the tagged tool for the host. - -**Evidence:** The [manual retry block](../release.md) runs `git fetch` and -`git checkout --detach` before the subshell that enables `set -e`. A -failed checkout can therefore be followed by dependency preparation, a -build, and execution from the wrong source. A successful checkout can -also carry local edits forward. Both cases were reproduced with the -documented block in disposable Git repositories and a local bare remote: -a checkout refused because of conflicting edits still ran a fixture -built from the edited source, and checking out the already selected tag -succeeded while retaining and executing a local edit. The fixture exited -successfully in both cases. ReleaseKit's normal delivery guards check -the tagged checkout after the executable has already been built and -started; they do not protect this bootstrap. - -**Fix:** Include the fetch and checkout in the fail-fast subshell. Resolve -the literal tag to its commit and require a clean checkout before -downloading dependencies or building the CLI, as the -[source delivery template](../../examples/woodpecker/releasekit-source-delivery.yml) -does. Preserve the scoped host target, dependency download before the -offline build, and the existing retry arguments. Verify that checkout -failure and carried-over edits both stop before the first Go command.