fix(bootstrap): fail closed before manifest installs

This commit is contained in:
Atirna
2026-08-13 03:22:32 +05:30
parent 1d929bf5db
commit 87e00b80db
6 changed files with 348 additions and 177 deletions
+4
View File
@@ -32,6 +32,10 @@ jobs:
shell: pwsh
run: ./skills/scripts/verify-routing-coherence.ps1
- name: Bootstrap supply-chain regression
shell: pwsh
run: ./skills/scripts/test-bootstrap-supply-chain.ps1
- name: Smoke (verify + parse + quick route)
shell: pwsh
run: ./skills/scripts/smoke.ps1
+3 -146
View File
@@ -23,6 +23,7 @@ $ErrorActionPreference = 'Stop'
$OutputEncoding = [System.Text.UTF8Encoding]::new($false)
. (Join-Path $PSScriptRoot 'lib\ToolDiscovery.ps1')
. (Join-Path $PSScriptRoot 'lib\BootstrapSupplyChain.ps1')
function Get-BootstrapDependency {
param([Parameter(Mandatory = $true)][string]$Name)
@@ -164,29 +165,6 @@ function Ensure-JavaRuntime {
}
}
function Ensure-Pnpm {
Ensure-NodeRuntime
$dependency = Get-BootstrapDependency -Name 'pnpm'
$pnpm = Get-NodeCommandPath -Name 'pnpm'
$currentVersion = ''
if ($pnpm) {
$versionLine = & $pnpm --version 2>$null | Select-Object -First 1
if ($LASTEXITCODE -eq 0 -and $null -ne $versionLine) {
$currentVersion = ([string]$versionLine).Trim()
}
}
if ($currentVersion -ne [string]$dependency.version) {
$npm = Get-NodeCommandPath -Name 'npm'
if ([string]::IsNullOrWhiteSpace($npm)) {
throw 'npm is not available after Node.js installation.'
}
& $npm install -g ([string]$dependency.package)
if ($LASTEXITCODE -ne 0) {
throw "Failed to install pinned pnpm dependency $($dependency.package)."
}
}
}
function Get-AnythingAnalyzerUserDataPaths {
$candidates = @(
(Join-Path $env:APPDATA 'anything-analyzer'),
@@ -801,54 +779,9 @@ if (Test-ReverseIsWindows) {
if ([string]::IsNullOrWhiteSpace($pnpm)) {
throw 'pnpm is not available after installation.'
}
$workspacePath = Join-Path $repoDir 'pnpm-workspace.yaml'
$workspaceExisted = Test-Path -LiteralPath $workspacePath -PathType Leaf
$workspaceBytes = if ($workspaceExisted) { [IO.File]::ReadAllBytes($workspacePath) } else { $null }
Push-Location $repoDir
try {
Approve-AnythingAnalyzerBuildScripts -RepoDir $repoDir
if (-not (Test-AnythingAnalyzerElectronHealthy -RepoDir $repoDir -PnpmPath $pnpm)) {
$nodeModules = Join-Path $repoDir 'node_modules'
if (Test-Path -LiteralPath $nodeModules) {
Remove-Item -LiteralPath $nodeModules -Recurse -Force
}
}
& $pnpm install --frozen-lockfile
if ($LASTEXITCODE -ne 0) {
if (-not [string]::IsNullOrWhiteSpace($vsBuildToolsError)) {
throw "pnpm install failed for anything-analyzer. Visual Studio Build Tools auto-install also failed earlier: $vsBuildToolsError"
}
throw 'pnpm install failed for anything-analyzer.'
}
& $pnpm rebuild electron esbuild better-sqlite3
if ($LASTEXITCODE -ne 0) {
if (-not [string]::IsNullOrWhiteSpace($vsBuildToolsError)) {
throw "pnpm rebuild failed for anything-analyzer. Visual Studio Build Tools auto-install also failed earlier: $vsBuildToolsError"
}
throw 'pnpm rebuild failed for anything-analyzer.'
}
if (-not (Test-AnythingAnalyzerElectronHealthy -RepoDir $repoDir -PnpmPath $pnpm)) {
throw 'Electron is still not healthy after reinstall/rebuild.'
}
}
finally {
Pop-Location
if ($workspaceExisted) {
[IO.File]::WriteAllBytes($workspacePath, $workspaceBytes)
}
elseif (Test-Path -LiteralPath $workspacePath) {
Remove-Item -LiteralPath $workspacePath -Force
}
}
$git = Get-FirstCommandPath -Names @('git')
Assert-GitCheckoutState -GitPath $git -CheckoutPath $repoDir -PinnedCommit ([string]$Definition.pinnedCommit)
Invoke-AnythingAnalyzerPinnedInstall -RepoDir $repoDir -PnpmPath $pnpm -GitPath $git `
-PinnedCommit ([string]$Definition.pinnedCommit) -VsBuildToolsError $vsBuildToolsError
$stdoutLog = Join-Path $repoDir 'anything-analyzer-dev.log'
$stderrLog = Join-Path $repoDir 'anything-analyzer-dev.err.log'
@@ -889,82 +822,6 @@ function Ensure-AndroidPlatformTools {
return (Resolve-ReverseToolSpec -Name 'adb')
}
function Assert-GitCheckoutState {
param(
[Parameter(Mandatory = $true)][string]$GitPath,
[Parameter(Mandatory = $true)][string]$CheckoutPath,
[Parameter(Mandatory = $true)][string]$PinnedCommit
)
$resolvedLine = & $GitPath -C $CheckoutPath rev-parse HEAD 2>$null | Select-Object -First 1
$resolvedCommit = if ($null -eq $resolvedLine) { '' } else { ([string]$resolvedLine).Trim() }
if ($LASTEXITCODE -ne 0 -or $resolvedCommit -ne $PinnedCommit) {
throw "Checkout verification failed: expected $PinnedCommit, got $resolvedCommit ($CheckoutPath)"
}
$status = @(& $GitPath -C $CheckoutPath status --porcelain --untracked-files=all 2>&1)
if ($LASTEXITCODE -ne 0) {
throw "Cannot inspect checkout state: $CheckoutPath"
}
if ($status.Count -gt 0) {
throw "Checkout has local changes; refusing to execute it: $CheckoutPath"
}
}
function Ensure-GitCloneInstall {
param(
[Parameter(Mandatory = $true)]$Definition,
[Parameter(Mandatory = $true)][string]$TargetPath
)
$pinnedCommit = if ($Definition.PSObject.Properties['pinnedCommit']) { [string]$Definition.pinnedCommit } else { '' }
$git = Get-FirstCommandPath -Names @('git')
if ([string]::IsNullOrWhiteSpace($git)) {
throw "Cannot clone $($Definition.repo) because git is not available."
}
if ((Test-Path -LiteralPath $TargetPath -PathType Container) -and (Test-Path -LiteralPath (Join-Path $TargetPath '.git'))) {
if ([string]::IsNullOrWhiteSpace($pinnedCommit)) {
throw "Git capability $($Definition.repo) must define pinnedCommit before an existing checkout can be used."
}
Assert-GitCheckoutState -GitPath $git -CheckoutPath $TargetPath -PinnedCommit $pinnedCommit
return $true
}
if (Test-Path -LiteralPath $TargetPath) {
throw "Install path exists but is not a git checkout: $TargetPath"
}
if ([string]::IsNullOrWhiteSpace($pinnedCommit)) {
throw "Git capability $($Definition.repo) must define pinnedCommit."
}
$parent = Split-Path -Path $TargetPath -Parent
Ensure-DownloadDirectory -Path $parent
$stagePath = Join-Path $parent ('.reverse-bootstrap-{0}' -f [Guid]::NewGuid().ToString('N'))
New-Item -ItemType Directory -Path $stagePath | Out-Null
try {
& $git init --quiet $stagePath
if ($LASTEXITCODE -ne 0) { throw 'git init failed' }
& $git -C $stagePath remote add origin $Definition.repo
if ($LASTEXITCODE -ne 0) { throw 'git remote add failed' }
& $git -C $stagePath fetch --depth 1 origin $pinnedCommit
if ($LASTEXITCODE -ne 0) { throw 'git fetch failed' }
& $git -C $stagePath checkout --quiet --detach FETCH_HEAD
if ($LASTEXITCODE -ne 0) { throw 'git checkout failed' }
Assert-GitCheckoutState -GitPath $git -CheckoutPath $stagePath -PinnedCommit $pinnedCommit
Move-Item -LiteralPath $stagePath -Destination $TargetPath
if ((Test-Path -LiteralPath $stagePath) -or -not (Test-Path -LiteralPath (Join-Path $TargetPath '.git') -PathType Container)) {
throw "Failed to promote staged checkout to $TargetPath"
}
}
finally {
if (Test-Path -LiteralPath $stagePath) {
Remove-Item -LiteralPath $stagePath -Recurse -Force
}
}
return $true
}
function Ensure-Capability {
param([Parameter(Mandatory = $true)][string]$Name)
+41 -31
View File
@@ -224,13 +224,7 @@ install_brew_cask() {
}
ensure_python_runtime() {
if ! has_cmd python3; then
case "$PLATFORM" in
macos) install_brew python ;;
linux) install_apt python3 ;;
*) log_err "Install Python 3 manually. See $(platform_doc)"; return 1 ;;
esac
fi
ensure_python_interpreter || return 1
local pipx_package pipx_version current_version
pipx_package=$(manifest_dependency pipx package) || return 1
pipx_version=$(manifest_dependency pipx version) || return 1
@@ -245,6 +239,17 @@ ensure_python_runtime() {
export PATH="$HOME/.local/bin:$PATH"
}
ensure_python_interpreter() {
if ! has_cmd python3; then
case "$PLATFORM" in
macos) install_brew python ;;
linux) install_apt python3 ;;
*) log_err "Install Python 3 manually. See $(platform_doc)"; return 1 ;;
esac
fi
has_cmd python3 || { log_err "Python 3 installation completed without a usable python3 command."; return 1; }
}
ensure_node_runtime() {
if has_cmd node && has_cmd npm && has_cmd npx; then return 0; fi
case "$PLATFORM" in
@@ -567,10 +572,10 @@ ensure_jadx() {
if has_cmd jadx; then log_ok "jadx ready: $(cmd_path jadx)"; return 0; fi
ensure_java_runtime
local repo re tag sha
repo=$(manifest_field jadx repo)
re=$(manifest_field jadx assetRegex)
tag=$(manifest_field jadx releaseTag)
sha=$(manifest_field jadx assetSha256)
repo=$(manifest_field jadx repo) || return 1
re=$(manifest_field jadx assetRegex) || return 1
tag=$(manifest_field jadx releaseTag) || return 1
sha=$(manifest_field jadx assetSha256) || return 1
case "$PLATFORM" in
macos) install_brew jadx || install_github_release "$repo" "$re" "$TOOLS_ROOT/jadx" "$tag" "$sha" ;;
linux) install_github_release "$repo" "$re" "$TOOLS_ROOT/jadx" "$tag" "$sha" ;;
@@ -587,10 +592,10 @@ ensure_apktool() {
ensure_dir "$TOOLS_ROOT/apktool"
local meta url digest jar wrapper
local repo tag sha re
repo=$(manifest_field apktool repo)
tag=$(manifest_field apktool releaseTag)
sha=$(manifest_field apktool assetSha256)
re=$(manifest_field apktool assetRegex)
repo=$(manifest_field apktool repo) || return 1
tag=$(manifest_field apktool releaseTag) || return 1
sha=$(manifest_field apktool assetSha256) || return 1
re=$(manifest_field apktool assetRegex) || return 1
meta=$(latest_github_asset_meta "$repo" "$re" "$tag")
url=$(printf '%s' "$meta" | cut -f1)
digest=$(printf '%s' "$meta" | cut -f2)
@@ -609,7 +614,7 @@ ensure_frida_tools() {
ensure_python_runtime || return 1
if has_cmd frida && has_cmd frida-ps; then log_ok "frida-tools ready"; return 0; fi
local package
package=$(manifest_field frida pipPackage)
package=$(manifest_field frida pipPackage) || return 1
pipx install --force "$package" || return 1
export PATH="$HOME/.local/bin:$PATH"
}
@@ -618,7 +623,7 @@ ensure_idalib_mcp() {
ensure_python_runtime || return 1
if has_cmd ida-pro-mcp; then log_ok "ida-pro-mcp ready: $(cmd_path ida-pro-mcp)"; return 0; fi
local source
source=$(manifest_field idalib-mcp pipSource)
source=$(manifest_field idalib-mcp pipSource) || return 1
pipx install --force "$source" || return 1
export PATH="$HOME/.local/bin:$PATH"
log_warn "Post-install: run 'ida-pro-mcp --install', choose Streamable HTTP + Global, then restart IDA Pro."
@@ -627,7 +632,7 @@ ensure_idalib_mcp() {
ensure_jshookmcp() {
ensure_node_runtime || return 1
local package
package=$(manifest_field jshookmcp npmPackage)
package=$(manifest_field jshookmcp npmPackage) || return 1
write_mcp_server "jshook" "$(python3 - "$package" <<'PY'
import json, sys
print(json.dumps({'command':'npx','args':['-y',sys.argv[1]],'env':{'JSHOOK_BASE_PROFILE':'search'}}))
@@ -638,7 +643,7 @@ PY
ensure_reqable_mcp() {
ensure_node_runtime || return 1
local package
package=$(manifest_field reqable-mcp npmPackage)
package=$(manifest_field reqable-mcp npmPackage) || return 1
write_mcp_server "reqable-mcp" "$(python3 - "$package" <<'PY'
import json, sys
print(json.dumps({'command':'npx','args':['-y',sys.argv[1]]}))
@@ -650,8 +655,8 @@ PY
ensure_anything_analyzer() {
local dir="$TOOLS_ROOT/anything-analyzer"
local repo commit
repo=$(manifest_field anything-analyzer repoUrl)
commit=$(manifest_field anything-analyzer pinnedCommit)
repo=$(manifest_field anything-analyzer repoUrl) || return 1
commit=$(manifest_field anything-analyzer pinnedCommit) || return 1
if ! has_cmd git; then
case "$PLATFORM" in macos) install_brew git ;; linux) install_apt git ;; esac
fi
@@ -719,7 +724,7 @@ ensure_agent_browser() {
ensure_node_runtime || return 1
if has_cmd agent-browser; then log_ok "agent-browser ready"; return 0; fi
local package
package=$(manifest_field agent-browser npmPackage)
package=$(manifest_field agent-browser npmPackage) || return 1
npm install -g "$package" || return 1
if has_cmd npx; then npx playwright install chromium || true; fi
local setup="$SKILL_ROOT/browser-automation/scripts/setup.sh"
@@ -729,8 +734,8 @@ ensure_agent_browser() {
ensure_ghidra_mcp() {
ensure_java_runtime || return 1
local repo regex
repo=$(manifest_field ghidra-mcp repo)
regex=$(manifest_field ghidra-mcp assetRegex)
repo=$(manifest_field ghidra-mcp repo) || return 1
regex=$(manifest_field ghidra-mcp assetRegex) || return 1
case "$PLATFORM" in
macos)
if ! has_cmd ghidraRun && [[ ! -d /Applications/Ghidra.app ]]; then
@@ -752,8 +757,8 @@ ensure_seclists() {
if [[ -d /usr/share/seclists ]]; then log_ok "SecLists ready"; return 0; fi
if ! has_cmd git; then case "$PLATFORM" in macos) install_brew git ;; linux) install_apt git ;; esac; fi
local repo commit
repo=$(manifest_field seclists repo)
commit=$(manifest_field seclists pinnedCommit)
repo=$(manifest_field seclists repo) || return 1
commit=$(manifest_field seclists pinnedCommit) || return 1
install_git_commit "$repo" "$commit" "$dir" || return 1
}
@@ -761,8 +766,8 @@ ensure_proxycat() {
ensure_python_runtime || return 1
if has_cmd proxycat; then log_ok "proxycat ready"; return 0; fi
local repo commit
repo=$(manifest_field proxycat repo)
commit=$(manifest_field proxycat pinnedCommit)
repo=$(manifest_field proxycat repo) || return 1
commit=$(manifest_field proxycat pinnedCommit) || return 1
pipx install "git+${repo}@${commit}" || {
manual_required proxycat "Clone/install ProxyCat manually; verify command 'proxycat'."
LAST_CAPABILITY_MANUAL=true
@@ -810,8 +815,8 @@ ensure_pentestswarm() {
case "$PLATFORM" in macos) install_brew go ;; linux) install_apt golang-go ;; esac
fi
local go_package docker_image
go_package=$(manifest_field pentestswarm goPackage)
docker_image=$(manifest_field pentestswarm dockerImage)
go_package=$(manifest_field pentestswarm goPackage) || return 1
docker_image=$(manifest_field pentestswarm dockerImage) || return 1
if go install "$go_package"; then
local go_bin
go_bin="$(go env GOBIN 2>/dev/null || true)"
@@ -857,7 +862,7 @@ ensure_pwntools() {
ensure_python_runtime || return 1
if python3 -c "import pwn" 2>/dev/null; then log_ok "pwntools ready"; return 0; fi
local package
package=$(manifest_field pwntools pipPackage)
package=$(manifest_field pwntools pipPackage) || return 1
pipx install "$package" || python3 -m pip install --user "$package" || return 1
}
@@ -937,6 +942,11 @@ done < <(expand_capabilities "${CAPABILITIES[@]}")
log_info "platform=$PLATFORM doc=$(platform_doc) tools_root=$TOOLS_ROOT"
if ! ensure_python_interpreter; then
log_err "Python 3 is required to read bootstrap-manifest.json; no capability was executed."
exit 1
fi
for cap in "${EXPANDED[@]}"; do
log_info "ensure $cap"
LAST_CAPABILITY_MANUAL=false
+151
View File
@@ -0,0 +1,151 @@
function Ensure-Pnpm {
Ensure-NodeRuntime
$dependency = Get-BootstrapDependency -Name 'pnpm'
$pnpm = Get-NodeCommandPath -Name 'pnpm'
$currentVersion = ''
if ($pnpm) {
$versionLine = & $pnpm --version 2>$null | Select-Object -First 1
if ($LASTEXITCODE -eq 0 -and $null -ne $versionLine) {
$currentVersion = ([string]$versionLine).Trim()
}
}
if ($currentVersion -ne [string]$dependency.version) {
$npm = Get-NodeCommandPath -Name 'npm'
if ([string]::IsNullOrWhiteSpace($npm)) {
throw 'npm is not available after Node.js installation.'
}
& $npm install -g ([string]$dependency.package)
if ($LASTEXITCODE -ne 0) {
throw "Failed to install pinned pnpm dependency $($dependency.package)."
}
}
}
function Assert-GitCheckoutState {
param(
[Parameter(Mandatory = $true)][string]$GitPath,
[Parameter(Mandatory = $true)][string]$CheckoutPath,
[Parameter(Mandatory = $true)][string]$PinnedCommit
)
$resolvedLine = & $GitPath -C $CheckoutPath rev-parse HEAD 2>$null | Select-Object -First 1
$resolvedCommit = if ($null -eq $resolvedLine) { '' } else { ([string]$resolvedLine).Trim() }
if ($LASTEXITCODE -ne 0 -or $resolvedCommit -ne $PinnedCommit) {
throw "Checkout verification failed: expected $PinnedCommit, got $resolvedCommit ($CheckoutPath)"
}
$status = @(& $GitPath -C $CheckoutPath status --porcelain --untracked-files=all 2>&1)
if ($LASTEXITCODE -ne 0) {
throw "Cannot inspect checkout state: $CheckoutPath"
}
if ($status.Count -gt 0) {
throw "Checkout has local changes; refusing to execute it: $CheckoutPath"
}
}
function Move-BootstrapDirectory {
param(
[Parameter(Mandatory = $true)][string]$Source,
[Parameter(Mandatory = $true)][string]$Destination
)
[IO.Directory]::Move($Source, $Destination)
}
function Ensure-GitCloneInstall {
param(
[Parameter(Mandatory = $true)]$Definition,
[Parameter(Mandatory = $true)][string]$TargetPath
)
$git = Get-FirstCommandPath -Names @('git')
if ([string]::IsNullOrWhiteSpace($git)) {
throw 'git is required for git-clone bootstrap definitions.'
}
$pinnedCommit = if ($Definition.PSObject.Properties['pinnedCommit']) { [string]$Definition.pinnedCommit } else { '' }
if ([string]::IsNullOrWhiteSpace($pinnedCommit)) {
throw "Git capability $($Definition.repo) must define pinnedCommit."
}
if ((Test-Path -LiteralPath $TargetPath -PathType Container) -and (Test-Path -LiteralPath (Join-Path $TargetPath '.git'))) {
Assert-GitCheckoutState -GitPath $git -CheckoutPath $TargetPath -PinnedCommit $pinnedCommit
return $true
}
if (Test-Path -LiteralPath $TargetPath) {
throw "Install path exists but is not a git checkout: $TargetPath"
}
$parent = Split-Path -Path $TargetPath -Parent
Ensure-DownloadDirectory -Path $parent
$stagePath = Join-Path $parent ('.reverse-bootstrap-{0}' -f [Guid]::NewGuid().ToString('N'))
New-Item -ItemType Directory -Path $stagePath | Out-Null
try {
& $git init --quiet $stagePath
if ($LASTEXITCODE -ne 0) { throw 'git init failed' }
& $git -C $stagePath remote add origin $Definition.repo
if ($LASTEXITCODE -ne 0) { throw 'git remote add failed' }
& $git -C $stagePath fetch --depth 1 origin $pinnedCommit
if ($LASTEXITCODE -ne 0) { throw 'git fetch failed' }
& $git -C $stagePath checkout --quiet --detach FETCH_HEAD
if ($LASTEXITCODE -ne 0) { throw 'git checkout failed' }
Assert-GitCheckoutState -GitPath $git -CheckoutPath $stagePath -PinnedCommit $pinnedCommit
Move-BootstrapDirectory -Source $stagePath -Destination $TargetPath
if (-not (Test-Path -LiteralPath (Join-Path $TargetPath '.git') -PathType Container)) {
throw "Failed to promote staged checkout to $TargetPath"
}
}
finally {
if (Test-Path -LiteralPath $stagePath) {
Remove-Item -LiteralPath $stagePath -Recurse -Force
}
}
return $true
}
function Invoke-AnythingAnalyzerPinnedInstall {
param(
[Parameter(Mandatory = $true)][string]$RepoDir,
[Parameter(Mandatory = $true)][string]$PnpmPath,
[Parameter(Mandatory = $true)][string]$GitPath,
[Parameter(Mandatory = $true)][string]$PinnedCommit,
[string]$VsBuildToolsError = ''
)
$workspacePath = Join-Path $RepoDir 'pnpm-workspace.yaml'
$workspaceExisted = Test-Path -LiteralPath $workspacePath -PathType Leaf
$workspaceBytes = if ($workspaceExisted) { [IO.File]::ReadAllBytes($workspacePath) } else { $null }
Push-Location $RepoDir
try {
Approve-AnythingAnalyzerBuildScripts -RepoDir $RepoDir
if (-not (Test-AnythingAnalyzerElectronHealthy -RepoDir $RepoDir -PnpmPath $PnpmPath)) {
$nodeModules = Join-Path $RepoDir 'node_modules'
Remove-Item -LiteralPath $nodeModules -Recurse -Force -ErrorAction SilentlyContinue
}
& $PnpmPath install --frozen-lockfile
if ($LASTEXITCODE -ne 0) {
if ($VsBuildToolsError) { throw "pnpm install failed for anything-analyzer. Visual Studio Build Tools auto-install also failed earlier: $VsBuildToolsError" }
throw 'pnpm install failed for anything-analyzer.'
}
& $PnpmPath rebuild electron esbuild better-sqlite3
if ($LASTEXITCODE -ne 0) {
if ($VsBuildToolsError) { throw "pnpm rebuild failed for anything-analyzer. Visual Studio Build Tools auto-install also failed earlier: $VsBuildToolsError" }
throw 'pnpm rebuild failed for anything-analyzer.'
}
if (-not (Test-AnythingAnalyzerElectronHealthy -RepoDir $RepoDir -PnpmPath $PnpmPath)) {
throw 'anything-analyzer Electron dependency is still unhealthy after pnpm rebuild.'
}
}
finally {
Pop-Location
if ($workspaceExisted) {
[IO.File]::WriteAllBytes($workspacePath, $workspaceBytes)
}
elseif (Test-Path -LiteralPath $workspacePath) {
Remove-Item -LiteralPath $workspacePath -Force
}
}
Assert-GitCheckoutState -GitPath $GitPath -CheckoutPath $RepoDir -PinnedCommit $PinnedCommit
}
+46
View File
@@ -19,6 +19,11 @@ name="$(basename "$0")"
case "$name:${1:-}" in
pipx:--version) printf '%s\n' "${STUB_PIPX_VERSION:-0}" ;;
pnpm:--version) printf '%s\n' "${STUB_PNPM_VERSION:-0}" ;;
brew:install)
if [[ "${2:-}" == python ]]; then
ln -sf "$STUB_PYTHON_SOURCE" "$STUB_ACTIVE_BIN/python3"
fi
;;
git:init)
target="${!#}"; mkdir -p "$target/.git"; printf '%s\n' unpinned-head > "$target/.stub-head"
;;
@@ -43,6 +48,7 @@ esac
STUB
chmod +x "$STUB_BIN/command-stub"
for name in git node npm npx pipx pnpm sleep nc; do ln -s command-stub "$STUB_BIN/$name"; done
ln -s command-stub "$STUB_BIN/brew"
cat > "$STUB_BIN/python3" <<STUB
#!/usr/bin/env bash
@@ -67,6 +73,7 @@ PY
run_generic() {
env PATH="$STUB_BIN:/usr/bin:/bin" HOME="$SCRATCH/home" CALL_LOG="$CALL_LOG" \
STUB_ACTIVE_BIN="$STUB_BIN" STUB_PYTHON_SOURCE="$STUB_BIN/python3" \
STUB_PIPX_VERSION="${STUB_PIPX_VERSION:-}" STUB_PNPM_VERSION="${STUB_PNPM_VERSION:-}" \
STUB_FAIL_PIP_INSTALL="${STUB_FAIL_PIP_INSTALL:-0}" STUB_FAIL_FETCH="${STUB_FAIL_FETCH:-0}" \
REVERSE_SKILL_TOOLS_DIR="${TEST_TOOLS_ROOT:-$SCRATCH/tools}" \
@@ -91,6 +98,45 @@ pnpm_package=$(json_value dependency pnpm)
anything_repo=$(json_value anything-analyzer repoUrl)
anything_pin=$(json_value anything-analyzer pinnedCommit)
# The manifest parser is bootstrapped before a Node-only sink, without installing pipx.
NO_PYTHON_BIN="$SCRATCH/no-python-bin"
PARSER_FIXTURE="$SCRATCH/parser-bootstrap"
mkdir -p "$NO_PYTHON_BIN"
for name in git node npm npx pipx pnpm sleep nc brew; do ln -s "$STUB_BIN/command-stub" "$NO_PYTHON_BIN/$name"; done
for tool in bash uname dirname mktemp rm head tr basename mkdir cat ln; do ln -s "$(command -v "$tool")" "$NO_PYTHON_BIN/$tool"; done
mkdir -p "$PARSER_FIXTURE"
cp "$BOOTSTRAP" "$PARSER_FIXTURE/bootstrap-reverse.sh"
cp "$MANIFEST" "$PARSER_FIXTURE/bootstrap-manifest.json"
: > "$CALL_LOG"
env PATH="$NO_PYTHON_BIN" HOME="$SCRATCH/home" CALL_LOG="$CALL_LOG" \
STUB_ACTIVE_BIN="$NO_PYTHON_BIN" STUB_PYTHON_SOURCE="$STUB_BIN/python3" \
REVERSE_SKILL_TOOLS_DIR="$SCRATCH/tools" CLAUDE_MCP_CONFIG="$SCRATCH/home/mcp.json" \
bash "$PARSER_FIXTURE/bootstrap-reverse.sh" agent-browser --skip-refresh >/dev/null
expect_line 'brew|install|python'
expect_line "npm|install|-g|$(json_value agent-browser npmPackage)"
! grep -Fq '|pip|install|' "$CALL_LOG"
# A required empty manifest field fails before any package-manager sink.
BROKEN_DIR="$SCRATCH/broken-bootstrap"
mkdir -p "$BROKEN_DIR"
cp "$BOOTSTRAP" "$BROKEN_DIR/bootstrap-reverse.sh"
"$REAL_PYTHON" - "$MANIFEST" "$BROKEN_DIR/bootstrap-manifest.json" <<'PY'
import json, pathlib, sys
data = json.loads(pathlib.Path(sys.argv[1]).read_text())
next(x for x in data['capabilities'] if x['name'] == 'agent-browser')['npmPackage'] = ''
pathlib.Path(sys.argv[2]).write_text(json.dumps(data))
PY
: > "$CALL_LOG"
set +e
env PATH="$STUB_BIN:/usr/bin:/bin" HOME="$SCRATCH/home" CALL_LOG="$CALL_LOG" \
STUB_ACTIVE_BIN="$STUB_BIN" STUB_PYTHON_SOURCE="$STUB_BIN/python3" \
REVERSE_SKILL_TOOLS_DIR="$SCRATCH/tools" CLAUDE_MCP_CONFIG="$SCRATCH/home/mcp.json" \
bash "$BROKEN_DIR/bootstrap-reverse.sh" agent-browser --skip-refresh >/dev/null 2>&1
broken_rc=$?
set -e
[[ $broken_rc -ne 0 ]]
! grep -Eq '^npm\|install\|-g(\||$)' "$CALL_LOG"
# Table: each generic package-manager sink receives its canonical manifest value.
while IFS='|' read -r capability field expected; do
: > "$CALL_LOG"
@@ -0,0 +1,103 @@
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
$scratch = Join-Path ([IO.Path]::GetTempPath()) ('reverse-bootstrap-ps-' + [Guid]::NewGuid().ToString('N'))
New-Item -ItemType Directory -Path $scratch | Out-Null
function Ensure-DownloadDirectory { param([string]$Path) New-Item -ItemType Directory -Path $Path -Force | Out-Null }
function Get-FirstCommandPath { param([string[]]$Names) return (Get-Command $Names[0]).Source }
function Ensure-NodeRuntime {}
function Get-NodeCommandPath { param([string]$Name) $command = Get-Command $Name -ErrorAction SilentlyContinue; if ($command) { return $command.Source } }
function Get-BootstrapDependency { return [pscustomobject]@{ package = 'pnpm@10.24.0'; version = '10.24.0' } }
function Approve-AnythingAnalyzerBuildScripts { param([string]$RepoDir) Set-Content (Join-Path $RepoDir 'pnpm-workspace.yaml') 'generated' }
function Test-AnythingAnalyzerElectronHealthy { return $true }
. (Join-Path $PSScriptRoot 'lib/BootstrapSupplyChain.ps1')
function Assert-True { param([bool]$Condition, [string]$Message) if (-not $Condition) { throw $Message } }
function Invoke-Git { param([string[]]$Arguments) & git @Arguments; if ($LASTEXITCODE -ne 0) { throw "git failed: $Arguments" } }
try {
$source = Join-Path $scratch 'source'
New-Item -ItemType Directory -Path $source | Out-Null
Invoke-Git -Arguments @('-C', $source, 'init', '--quiet')
Invoke-Git -Arguments @('-C', $source, 'config', 'user.email', 'test@example.invalid')
Invoke-Git -Arguments @('-C', $source, 'config', 'user.name', 'test')
Set-Content (Join-Path $source 'package.json') '{}'
Invoke-Git -Arguments @('-C', $source, 'add', 'package.json')
Invoke-Git -Arguments @('-C', $source, 'commit', '--quiet', '-m', 'fixture')
$pin = (& git -C $source rev-parse HEAD).Trim()
$definition = [pscustomobject]@{ repo = $source; pinnedCommit = $pin }
$target = Join-Path $scratch 'installed'
Ensure-GitCloneInstall -Definition $definition -TargetPath $target | Out-Null
Assert-True ((& git -C $target rev-parse HEAD).Trim() -eq $pin) 'pinned checkout was not promoted'
Set-Content (Join-Path $target 'package.json') '{"dirty":true}'
try { Ensure-GitCloneInstall -Definition $definition -TargetPath $target | Out-Null; throw 'dirty checkout accepted' } catch { Assert-True ($_.Exception.Message -match 'local changes') 'dirty rejection reason changed' }
$failedTarget = Join-Path $scratch 'failed'
$badDefinition = [pscustomobject]@{ repo = (Join-Path $scratch 'missing'); pinnedCommit = $pin }
try { Ensure-GitCloneInstall -Definition $badDefinition -TargetPath $failedTarget | Out-Null; throw 'failed fetch accepted' } catch {}
Assert-True (-not (Test-Path $failedTarget)) 'failed fetch poisoned final path'
Assert-True (@(Get-ChildItem $scratch -Filter '.reverse-bootstrap-*').Count -eq 0) 'failed fetch left staging path'
$raceTarget = Join-Path $scratch 'race'
$raceStage = Join-Path $scratch '.reverse-bootstrap-race'
New-Item -ItemType Directory -Path $raceTarget, $raceStage | Out-Null
Set-Content (Join-Path $raceTarget 'owner.txt') owner
$raceRejected = $false
try { Move-BootstrapDirectory -Source $raceStage -Destination $raceTarget } catch { $raceRejected = $true }
Assert-True $raceRejected 'promotion race accepted'
Assert-True ((Get-Content (Join-Path $raceTarget 'owner.txt')) -eq 'owner') 'promotion race modified concurrent target'
Remove-Item -LiteralPath $raceStage -Recurse -Force
$bin = Join-Path $scratch 'bin'
New-Item -ItemType Directory -Path $bin | Out-Null
$env:PATH = "$bin$([IO.Path]::PathSeparator)$env:PATH"
$env:BOOTSTRAP_PS_LOG = Join-Path $scratch 'commands.log'
$isWindowsHost = $env:OS -eq 'Windows_NT'
$stub = Join-Path $bin ($(if ($isWindowsHost) { 'npm.cmd' } else { 'npm' }))
if ($isWindowsHost) {
Set-Content $stub @'
@echo off
echo npm^|%*>>"%BOOTSTRAP_PS_LOG%"
'@
}
else {
Set-Content $stub @'
#!/bin/sh
printf "npm|%s\n" "$*" >> "$BOOTSTRAP_PS_LOG"
'@
& chmod +x $stub
}
$pnpm = Join-Path $bin ($(if ($isWindowsHost) { 'pnpm.cmd' } else { 'pnpm' }))
if ($isWindowsHost) { Set-Content $pnpm "@echo off`r`necho 0" }
else { Set-Content $pnpm "#!/bin/sh`necho 0"; & chmod +x $pnpm }
Ensure-Pnpm
Assert-True ((Get-Content $env:BOOTSTRAP_PS_LOG) -match 'npm\|install -g pnpm@10.24.0') 'pnpm install was not pinned'
Invoke-Git -Arguments @('-C', $target, 'checkout', '--quiet', '--', 'package.json')
if ($isWindowsHost) {
Set-Content $pnpm @'
@echo off
if "%1"=="--version" (echo 10.24.0) else (echo pnpm^|%*>>"%BOOTSTRAP_PS_LOG%")
'@
}
else {
Set-Content $pnpm @'
#!/bin/sh
[ "$1" = --version ] && { echo 10.24.0; exit; }
printf "pnpm|%s\n" "$*" >> "$BOOTSTRAP_PS_LOG"
'@
& chmod +x $pnpm
}
function Approve-AnythingAnalyzerBuildScripts { param([string]$RepoDir) Set-Content (Join-Path $RepoDir 'pnpm-workspace.yaml') 'generated'; Set-Content (Join-Path $RepoDir 'package.json') '{"mutated":true}' }
$dirtyRejected = $false
try { Invoke-AnythingAnalyzerPinnedInstall -RepoDir $target -PnpmPath $pnpm -GitPath (Get-Command git).Source -PinnedCommit $pin } catch { $dirtyRejected = $_.Exception.Message -match 'local changes' }
Assert-True $dirtyRejected 'post-install dirty checkout accepted or rejection reason changed'
Assert-True (-not (Test-Path (Join-Path $target 'pnpm-workspace.yaml'))) 'generated workspace file was not removed'
Write-Host 'PowerShell bootstrap supply-chain regression passed'
}
finally {
Remove-Item -LiteralPath $scratch -Recurse -Force -ErrorAction SilentlyContinue
}