Development record
when-abcd-needs-another-tool-such-as-a
Summary
spc-2610241008092356 replaces the tool registry's Homebrew step with a checked download pinned in abcd's own release, so setup installs gitleaks and gh for the person alone, into their own bin folder, with no admin rights, and says how far each download was checked (itd-2610240918594138, dcn-2610240845548776, adr-2610241057124151). It resolves iss-2610240824571059.
Scope
- In: the release table (gitleaks v8.30.1, gh v2.103.2, macOS and Linux on
arm64 and amd64); the bounded https download from GitHub's release hosts;
the sha256 check against the pin; the in-process signed build record check
for gh; the archive read; the atomic write into
$XDG_BIN_HOME, else$XDG_DATA_HOME/../bin, else~/.local/bin; the installed-tools record in~/.abcd.noindex/tools.json; abcd's callers running the recorded copy; uninstall removing recorded copies; the plan that decides whether a copy already installed is used; the minimum wiring into ahoy's dependency step, the gh offer, and ahoy uninstall. - Out: the setup-flow redesign that lays the plan out before asking (a
separate lane calls
Installer.Plan); release-time checks that move a pin to a newer verified version (iss-2610240959030370, left open); Windows; tools whose makers publish no checksums.
Approach
The table lives in internal/core/tools/release.go: per tool a Release
(repository, pinned version, an Asset per <GOOS>/<GOARCH> holding the full
https://github.com/<repo>/releases/download/<tag>/<file> URL, its sha256, the
archive kind and the program's member path), an optional Signer (repository,
workflow file, ref; gh is cli/cli .github/workflows/deployment.yml on
refs/heads/trunk, gitleaks has none), and a Compatible version range. The
digests were copied on 2026-10-24 from each release's published checksums file
(gitleaks_8.30.1_checksums.txt, gh_2.103.2_checksums.txt) and each matched
the downloaded asset. No version is ever looked up (invariant 7).
Installer.Plan(name) decides, from the disk, PATH and the version a copy
already installed reports through the tool's verify argv, with no network:
use-abcd-copy (an intact recorded copy at the pinned version), use-existing
(a copy installed some other way whose version lies in Compatible), install
(abcd's own copy offered, saying which of the two will run in abcd and in the
person's shell; over abcd's own copy too, as an upgrade while that copy is in
Compatible, which keeps running on a no, and in its place when it is not),
or unavailable (no download for the platform, or the bin folder holds a
program of that name abcd did not install). Installer.Install refuses in CI,
uses the plan, asks the front door's Confirm with the plan in the
explanation, then downloads (fetch.go: https only, GitHub release hosts only,
no proxy, CA overrides held out of the root pool, urlguard on dial and
redirect, 256 MiB and five-minute bounds), checks the sha256, checks the
signed build record (attest.go: GitHub's attestations API, inline bundle or
bundle_url into GitHub's attestation store decoded from snappy by
snappy.go, verified with sigstore-go against the TUF-fetched trust root, the
exact SAN, issuer, source repository, ref and GitHub-hosted runner), reads the
member (archive.go), and only then writes with fsutil.WriteFileAtomic,
verifies the pinned version, and records path and digest (local.go). A
failed verify or record removes the copy again.
tools.Resolve(name) is where abcd runs a registered tool from: the recorded
copy while its bytes hash to the record, else PATH. The gitleaks adapter's
NewDefault, internal/core/github and internal/core/landing resolve gh and
gitleaks through it; ahoy's presence check (onPath) counts the recorded copy.
tools.Uninstall(home) removes recorded copies whose digest still matches and
drops the record; ahoy.Uninstall calls it and reports tools in its
receipt.
Core API for the setup-flow lane
All in internal/core/tools, none of it prints:
tools.Default(guard string) *tools.Installer— the production installer.(*Installer).Plan(name string) tools.Plan— what setup would do, withAction(UseOwned,UseExisting,InstallOwn,Unavailable),Pinned,Compatible,Download(version, URL, sha256, member, dest,Checks,ChecksumOnly),ExistingandOwned(Copy: path, version, compatible, why),RunsandWhy.(*Installer).Install(name string, c tools.Capability, confirm tools.Confirm) tools.Result—Ready,Ran,Installed,Verified,Declined,Path,Version,Checked,ChecksumOnly,Runs,Why,OnDecline, andSummary().tools.Explain(name, c) tools.Explanation— now carryingDownloadand, when Install hands it toConfirm,Plan;StepText()andManualText()(the by-hand URL, sha256 and destination).(tools.Release).Checks() string— how far a download is checked.tools.Resolve(name string) (string, error);tools.Uninstall(home string) ([]tools.Removal, error).
How the compatible range prepares for iss-2610240959030370
The range is the one statement of which versions abcd works with, and
TestEveryPinnedReleaseIsComplete holds every pin inside it. A release-time
check that tries a newer release against abcd's gates would move the pin, its
digests and, when the gates show it, the range, in one change to
release.go; holding the last passing pin is leaving the table as it is. That
check is not built here.
Acceptance-criteria satisfaction
- Missing tool installed, pinned, into the person's folder, checked before
writing, no admin rights, no admin step shown:
Installer.Installorder ininstall.go;TestInstallDownloadsChecksWritesAndRecords,TestExplainNamesTheCheckedDownloadandnoAdmin(nosudo,brew,chownin any explanation or result),TestEveryPinnedReleaseIsComplete,TestBinDirFollowsUvsOrder; at ahoyTestDependencyYesRunsTheStepAndReportsIt. - A failed check writes nothing and says why; checksum-only is said:
TestAFailedCheckWritesNothingAndSaysWhy(tampered sha256, failed build record, failed download),TestAFailedVerifyRemovesTheCopy,TestHowEachToolIsCheckedIsSaid,TestGhsRecordedBuildRecordVerifiesAgainstItsPinnedSignerandTestABuildRecordFromAnotherSignerOrForOtherBytesFailsover the recorded real gh v2.103.2 bundles. At setup, a yes whose download then fails is a needs-you step in the default report carrying the reason (TestAFailedToolDownloadIsANeedsYouStep), and a required tool's step says it is required (TestRequiredToolStepIsNotCalledOptional). - A compatible copy is used and nothing installed; otherwise abcd's copy is
offered, saying which runs, and the other copy is never changed:
TestACompatibleCopyIsUsedAndNothingInstalled,TestAnIncompatibleCopyGetsAbcdsOwnAndSaysWhichRuns,TestABinFolderCopyAbcdDidNotInstallIsNeverReplaced; abcd's own copy is used only inCompatible, replaced only while it is still the bytes abcd wrote:TestAnOwnedCopyOutsideTheRangeIsReplaced,TestAnOwnedCopyAtAnotherPinIsAnUpgrade,TestAForeignFileAtAnOwnedPathIsNeverReplaced; at ahoyTestAnIncompatibleGitleaksIsAGapOfItsOwn, and the detection pass the hooks run starts no tool (TestDetectionNeverRunsTheTool). - abcd runs its copy whatever PATH says, and uninstall removes only its
copies:
TestResolvePrefersAbcdsCopyWhateverPathSays,TestUninstallRemovesOnlyAbcdsIntactCopies; at ahoyTestUninstallRemovesTheToolCopiesAbcdRecorded.
Scope conditions: the four platforms are pinned for both tools; nothing needs admin rights, so an account with or without them takes the same route; both tools publish checksums.
Known limits: gh's offer at ahoy remote apply and site setup still counts
any gh on PATH as present (only a missing gh is offered abcd's copy), because
gh's compatible floor (2.102.0, for gh attestation verify --source-ref)
guards abcd's by-hand fallback, not gh api; the full criterion-3 judgement
runs for gitleaks at ahoy install. When the bin folder already holds a
foreign program of the tool's name, abcd installs nothing rather than choose
another folder.
Footprint
- packages: internal/core/tools, internal/core/ahoy, internal/adapter/gitleaks, internal/core/github, internal/core/landing, internal/surface/cli
- docs: commands/ahoy.md, .abcd/development/brief/04-surfaces/01-ahoy.md, .abcd/development/decisions/adrs/2610241057124151-abcd-installs-the-tools-it-needs-from.md
- tests: internal/core/tools/tools_test.go (and testdata/attest), internal/core/ahoy/tools_route_test.go, internal/core/ahoy/remote_gh_offer_test.go, internal/core/ahoy/remote_gh_explain_test.go, internal/core/site/setup_test.go, internal/surface/cli/ahoy_tool_confirm_test.go, internal/surface/cli/ahoy_remote_gh_offer_test.go, internal/surface/cli/site_setup_test.go
Steps
No steps listed: the spec is built as one step. To split it, list the steps in order as 1. <title>, each with - packages: and - tests: indented beneath it; - landed: <pull request or commit> marks a step that has landed.
Frontmatter
| id | spc-2610241008092356 |
|---|---|
| type | spec |
| lifecycle | closed |
.abcd/development/specs/closed/spc-2610241008092356-when-abcd-needs-another-tool-such-as-a.md
open on GitHub ↗ · commit history ↗
References1
- itd-2610240918594138abcd installs the tools it needs for the person alone, checkedimplements
mentions4
- adr-2610241057124151abcd installs the tools it needs from downloads pinned in its own release
- dcn-26102408455487762026-10-24 — Tool installs default to a verified download, on macOS and Linux alike: abcd fetches the tool's official release, checks it against the release's published checksums (and its signed build record where the project publishes one), and puts it in ~/.local/bin, needing no admin rights; this is the route abcd already uses to install itself. Homebrew is not the default, because a supported Homebrew for one non-admin account cannot be created by abcd on a Mac (Homebrew's installer refuses a prefix longer than /opt/homebrew, so no home-folder prefix fits) and a shared Homebrew is often not writable by the person. Decided by the product thinker on 2026-10-24 after the Homebrew-without-admin research (iss-2610240824571059).
- iss-2610240824571059ahoy install offers a tool install through Homebrew on an account that cannot write to Homebrew: on this machine /opt/homebrew is owned by the admin account, so brew install gitleaks fails on every run, and the failure note relays Homebrew's own sudo chown advice, although abcd never escalates privileges. The registry has one install step per platform (Homebrew), so an account without admin rights has no route that works.
- iss-2610240959030370before a new release, abcd must verify that all dependencies and especially those it pins to an abcd-managed repository are moved to the latest, verified version -- verified meaning that they are guaranteed to work with abcd. It should stay with a previous version if the newest one breaks abcd.