Record host launcher algorithm audit findings

This commit is contained in:
2026-11-04 01:20:02 +00:00
parent 80a610a5c2
commit 13e1c21386

View File

@@ -146,3 +146,63 @@ 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
**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
**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.