@amsterdamdatalabs/adl-agent-control-linux-x64 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/adl CHANGED
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/bin/adl
3
3
  size: 82875872 bytes
4
- sha256: d04a22f22de92cddad66eef18cb3be120caccd35b2330bed27f0bb526a5adbd5
4
+ sha256: b562edfda0f280940a2b5874874f83c22bde7fe8cd5b9d8159c003f4b53e1f5c
package/bin/reconcile CHANGED
@@ -456,28 +456,15 @@ run_npm() {
456
456
  fi
457
457
  }
458
458
 
459
- # Whether a read-only, owner-scoped query (brew_query, npm_installed_version)
460
- # can run without asking for privilege reconcile does not already have. Apply
461
- # mode always runs as real root (checked elsewhere), which can sudo -u any
462
- # administrator with no password. A plan/audit preview run has no such
463
- # guarantee -- $administrator on a Linux VPS device is "root", and previewing
464
- # that device's plan from an unprivileged account must not demand a sudo
465
- # password it may not have; it falls back to the pre-2026-09-28 unconditional
466
- # message instead. Self-administered devices (m3: administrator=tarunrana)
467
- # need no escalation at all, so their preview can query for real.
468
- can_query_as_administrator() {
469
- [[ "$(id -un)" == "$administrator" ]] || [[ "$(id -u)" -eq 0 ]]
470
- }
471
-
472
459
  # Read-only Homebrew query, run exactly like run_brew's real mutations (same
473
460
  # owner, cwd, and stdin) but never a mutation itself. A named function so
474
461
  # tests can stub just this, instead of having to fake sudo's own argument
475
462
  # parsing the way a raw inline `sudo -u ... "$brew_bin" ...` would require.
476
- # Apply-mode only (see can_query_as_administrator): `brew list`/`brew
477
- # outdated` have been observed to take minutes under real load on a
478
- # contended box (Homebrew's own auto-update against a large git checkout),
479
- # so a plan preview never calls this -- it would turn a "quick look" into a
480
- # slow, load-dependent one for every brew formula on the device.
463
+ # Apply-mode only: `brew list`/`brew outdated` have been observed to take
464
+ # minutes under real load on a contended box (Homebrew's own auto-update
465
+ # against a large git checkout), so a plan preview never calls this -- it
466
+ # would turn a "quick look" into a slow, load-dependent one for every brew
467
+ # formula on the device.
481
468
  brew_query() {
482
469
  (cd /tmp && sudo -u "$brew_owner" -H "$brew_bin" "$@" </dev/null)
483
470
  }
@@ -800,6 +787,27 @@ install_surface_policy_file() {
800
787
  echo "installed required surface policy: $target"
801
788
  }
802
789
 
790
+ # A resolved release version against the first line of a release-archive-
791
+ # latest tool's own `--version` output. Different tools format that line
792
+ # differently -- lean-ctx: "lean-ctx 3.10.5 (official, ...)" (version padded
793
+ # by spaces on both sides); agent-vault: "agent-vault version 0.39.3" (flush
794
+ # against the end of the line, no trailing space); a plain "vX.Y.Z" line
795
+ # with no separating space before the version at all -- so the LEFT boundary
796
+ # is a string start or a real space, with an OPTIONAL literal "v" directly
797
+ # after that boundary (never "v" floating anywhere: found in review,
798
+ # 2026-09-28, `(^|[ v])` treated a bare "v" as equivalent to a space no
799
+ # matter what preceded it, so "tool dev0.39.3"/"tool rev0.39.3" -- where the
800
+ # "v" belongs to "dev"/"rev", not a version prefix -- silently matched and
801
+ # would have skipped a real upgrade). The RIGHT boundary is a space or a
802
+ # string end and never accepts "v", or "0.39.3" would incorrectly match
803
+ # inside "0.39.30". Dots in the version are escaped so they match literally,
804
+ # not "any character," in the regex below.
805
+ release_archive_version_matches() {
806
+ local version_line="$1" version="$2" escaped
807
+ escaped="$(printf '%s' "$version" | sed 's/[.[\*^$()+?{}|]/\\&/g')"
808
+ grep -Eq "(^| )v?${escaped}( |\$)" <<<"$version_line"
809
+ }
810
+
803
811
  # Resolves the release tag GitHub's own "latest" redirect currently points
804
812
  # at, without touching the api.github.com REST API (unauthenticated calls
805
813
  # there are rate-limited per source IP; this fleet's shared egress IP already
@@ -813,65 +821,33 @@ resolve_latest_release_tag() {
813
821
  basename "$location"
814
822
  }
815
823
 
816
- run_release_archive() {
817
- local package="$1"
818
- local version="$2"
819
- local url="$3"
820
- local expected_sha256="$4"
821
- local archive_executable="$5"
822
- local target="$6"
823
- if [[ "$mode" != apply ]]; then
824
- printf 'would install pinned release archive: %s %s -> %s\n' "$package" "$version" "$target"
825
- return 0
826
- fi
827
- [[ "$(id -u)" -eq 0 ]] || { echo "apply mode must run as root" >&2; exit 1; }
828
- [[ "$target" == /usr/local/bin/* ]] || { echo "release archive target must be under /usr/local/bin: $target" >&2; exit 1; }
829
- [[ "$archive_executable" != /* && "$archive_executable" != *..* ]] || {
830
- echo "unsafe archive executable path: $archive_executable" >&2
831
- exit 1
832
- }
833
-
834
- (
835
- local release_dir archive_file extract_dir actual_sha256
836
- release_dir="$(mktemp -d)"
837
- trap 'rm -rf "$release_dir"' EXIT
838
- archive_file="$release_dir/release.tar.gz"
839
- extract_dir="$release_dir/extract"
840
- mkdir -p "$extract_dir"
841
- curl --proto '=https' --proto-redir '=https' -fsSL --retry 3 -o "$archive_file" "$url"
842
- case "$(uname -s)" in
843
- Darwin) actual_sha256="$(shasum -a 256 "$archive_file" | awk '{print $1}')" ;;
844
- Linux) actual_sha256="$(sha256sum "$archive_file" | awk '{print $1}')" ;;
845
- *) echo "unsupported operating system for release archive" >&2; exit 1 ;;
846
- esac
847
- [[ "$actual_sha256" == "$expected_sha256" ]] || {
848
- echo "checksum mismatch for $package $version" >&2
849
- exit 1
850
- }
851
- tar -xzf "$archive_file" -C "$extract_dir" "$archive_executable"
852
- [[ -f "$extract_dir/$archive_executable" ]] || {
853
- echo "archive executable missing: $archive_executable" >&2
854
- exit 1
855
- }
856
- atomic_install_executable "$extract_dir/$archive_executable" "$target"
857
- )
858
- }
859
-
860
- # Like run_release_archive, but never pins a version or checksum in the
861
- # manifest: it always installs whatever GitHub's own "latest" alias currently
862
- # serves, verified against the checksums file published *in that same
863
- # release* (never a value carried over from a previous run or hardcoded
864
- # here) -- fails loud, installs nothing, if that checksums asset is missing
865
- # or doesn't list our asset. `.../releases/latest/download/<name>` is GitHub's
866
- # own stable alias for "the matching asset of whatever release is latest
867
- # right now", so the download itself never needs the resolved tag.
824
+ # A GitHub release archive, installed under /usr/local/bin, verified against
825
+ # a checksum published *in that same release* (never a value carried over
826
+ # from a previous run or hardcoded here) -- fails loud, installs nothing, if
827
+ # the checksums asset is missing or doesn't list our asset. It always
828
+ # installs whatever GitHub's own "latest" release was when install_one_tool
829
+ # resolved $tag (resolve_latest_release_tag), fetched with $tag pinned into
830
+ # BOTH download URLs -- `.../releases/download/<tag>/<name>`, never through
831
+ # GitHub's separate "always whatever is latest right now" alias for the
832
+ # downloads themselves. Fixed 2026-09-29 (review): that alias re-resolves on
833
+ # every request, so a release published between the tag resolve and the
834
+ # archive download would otherwise serve the checksums file from one release
835
+ # and the archive from a newer one -- silently mixing versions past the
836
+ # checksum check (which would still "pass" against whichever checksums file
837
+ # actually got fetched). Pinning both fetches to the one $tag closes that
838
+ # window.
839
+ # Tarun, 2026-09-29 (hard rule): versions are never pinned in the manifest --
840
+ # this replaced the old pinned run_release_archive (fixed version + url +
841
+ # sha256), which validate-manifest no longer accepts on any manager; $tag
842
+ # here is resolved fresh every run, never carried over or hardcoded.
868
843
  run_release_archive_latest() {
869
844
  local package="$1"
870
845
  local repo="$2"
871
- local asset="$3"
872
- local checksums_asset="$4"
873
- local archive_executable="$5"
874
- local target="$6"
846
+ local tag="$3"
847
+ local asset="$4"
848
+ local checksums_asset="$5"
849
+ local archive_executable="$6"
850
+ local target="$7"
875
851
  [[ "$(id -u)" -eq 0 ]] || { echo "apply mode must run as root" >&2; exit 1; }
876
852
  [[ "$target" == /usr/local/bin/* ]] || { echo "release archive target must be under /usr/local/bin: $target" >&2; exit 1; }
877
853
  [[ "$archive_executable" != /* && "$archive_executable" != *..* ]] || {
@@ -888,13 +864,13 @@ run_release_archive_latest() {
888
864
  extract_dir="$release_dir/extract"
889
865
  mkdir -p "$extract_dir"
890
866
  curl --proto '=https' --proto-redir '=https' -fsSL --retry 3 \
891
- -o "$checksums_file" "https://github.com/$repo/releases/latest/download/$checksums_asset"
867
+ -o "$checksums_file" "https://github.com/$repo/releases/download/$tag/$checksums_asset"
892
868
  expected_sha256="$(awk -v want="$asset" '$2 == want { print $1; found = 1 } END { exit !found }' "$checksums_file")" || {
893
869
  echo "no published checksum for $asset in $repo's $checksums_asset -- refusing to install unverified bytes" >&2
894
870
  exit 1
895
871
  }
896
872
  curl --proto '=https' --proto-redir '=https' -fsSL --retry 3 \
897
- -o "$archive_file" "https://github.com/$repo/releases/latest/download/$asset"
873
+ -o "$archive_file" "https://github.com/$repo/releases/download/$tag/$asset"
898
874
  case "$(uname -s)" in
899
875
  Darwin) actual_sha256="$(shasum -a 256 "$archive_file" | awk '{print $1}')" ;;
900
876
  Linux) actual_sha256="$(sha256sum "$archive_file" | awk '{print $1}')" ;;
@@ -944,7 +920,7 @@ resolve_surface_mode() {
944
920
  # root") is a real environment fault, not a flaky install, and still
945
921
  # aborts immediately -- `exit` is unconditional and is never caught here.
946
922
  install_one_tool() {
947
- local tool_id="$1" manager package executable previous_executable migrating_from_bun=0 rc=0 npm_version npm_spec npm_allow_scripts npm_target_version npm_installed_ver npm_plan_installed brew_outdated_output brew_installed_version
923
+ local tool_id="$1" manager package executable previous_executable migrating_from_bun=0 rc=0 npm_spec npm_allow_scripts npm_target_version npm_installed_ver brew_outdated_output brew_installed_version
948
924
  manager="$(jq -r --arg id "$tool_id" '.[$id].manager' "$resolved_file")"
949
925
  package="$(jq -r --arg id "$tool_id" '.[$id].package' "$resolved_file")"
950
926
  executable="$(jq -r --arg id "$tool_id" '.[$id].executable' "$resolved_file")"
@@ -1000,15 +976,14 @@ install_one_tool() {
1000
976
  run_apt install -y "$package" || rc=1
1001
977
  ;;
1002
978
  npm)
1003
- # An entry's optional .version pins the installed release (e.g.
1004
- # adl-agent-id, Handoff 5 2026-09-26 H4) rather than tracking whatever
1005
- # npm resolves as latest for update_policy=latest-stable. Ownership
979
+ # Tarun, 2026-09-29 (hard rule): versions are never pinned -- every
980
+ # fleet tool installs latest stable. npm entries never carry a
981
+ # .version (validate-manifest rejects one outright); $npm_spec stays a
982
+ # separate variable from $package only because the exact run_npm
983
+ # invocation below is asserted at the source level. Ownership
1006
984
  # verification below still uses the bare package name: npm's global
1007
- # tree and package.json are keyed by package, never by the requested
1008
- # version spec.
1009
- npm_version="$(jq -r --arg id "$tool_id" '.[$id].version // empty' "$resolved_file")"
985
+ # tree and package.json are keyed by package, never by a version spec.
1010
986
  npm_spec="$package"
1011
- [[ -z "$npm_version" ]] || npm_spec="$package@$npm_version"
1012
987
  # Only @amsterdamdatalabs/enact-docs-search's manifest entry carries
1013
988
  # this (docs/operations/global-tools.md Topic 2, decided 2026-09-28):
1014
989
  # its own dependencies' install scripts, explicitly reviewed and
@@ -1019,41 +994,20 @@ install_one_tool() {
1019
994
  previous_executable="$(jq -r --arg id "$tool_id" '.[$id].executable' "$previous_resolved_file")"
1020
995
  echo "would remove retired Bun compilation before npm install: $tool_id ($previous_executable and $(bun_source_hash_file "$tool_id"))"
1021
996
  fi
1022
- # A pinned version is a pure local/offline fact: compare it against
1023
- # the real global install (npm_installed_version, no network) and say
1024
- # so honestly. An UNPINNED entry's "latest stable" is not knowable
1025
- # without a per-package registry round trip (npm_latest_version) --
1026
- # doing that here would make a plan preview slow and network-bound
1027
- # for every unpinned npm tool on the device, so it keeps today's
1028
- # conservative message instead of guessing. Either way, only query
1029
- # when it needs no new sudo privilege (can_query_as_administrator) --
1030
- # a preview run must not demand a password a different device's real
1031
- # administrator would need.
1032
- if [[ -n "$npm_version" ]] && can_query_as_administrator; then
1033
- if npm_plan_installed="$(npm_installed_version "$package")"; then
1034
- if [[ -n "$npm_plan_installed" && "$npm_plan_installed" == "$npm_version" ]]; then
1035
- echo "already latest: $tool_id (npm:$package@$npm_plan_installed)"
1036
- else
1037
- echo "would install or upgrade npm package: $npm_spec"
1038
- fi
1039
- else
1040
- echo "would install or upgrade npm package: $npm_spec (installed version not checked: npm ls -g failed)"
1041
- fi
1042
- else
1043
- echo "would install or upgrade npm package: $npm_spec"
1044
- fi
997
+ # "latest stable" is not knowable without a per-package registry
998
+ # round trip (npm_latest_version) -- doing that here would make a
999
+ # plan preview slow and network-bound for every npm tool on the
1000
+ # device, so plan mode always prints this unconditional message
1001
+ # instead of guessing.
1002
+ echo "would install or upgrade npm package: $npm_spec"
1045
1003
  else
1046
1004
  if is_in_place_bun_to_npm_migration "$tool_id"; then
1047
1005
  migrating_from_bun=1
1048
1006
  prepare_in_place_bun_to_npm_migration "$tool_id" || rc=1
1049
1007
  fi
1050
- if [[ "$rc" -eq 0 ]]; then
1051
- if [[ -n "$npm_version" ]]; then
1052
- npm_target_version="$npm_version"
1053
- elif ! npm_target_version="$(npm_latest_version "$package")"; then
1054
- echo "$tool_id: npm view could not resolve the latest version of $package" >&2
1055
- rc=1
1056
- fi
1008
+ if [[ "$rc" -eq 0 ]] && ! npm_target_version="$(npm_latest_version "$package")"; then
1009
+ echo "$tool_id: npm view could not resolve the latest version of $package" >&2
1010
+ rc=1
1057
1011
  fi
1058
1012
  if [[ "$rc" -eq 0 ]]; then
1059
1013
  if ! npm_installed_ver="$(npm_installed_version "$package")"; then
@@ -1081,21 +1035,8 @@ install_one_tool() {
1081
1035
  fi
1082
1036
  [[ "$rc" -eq 0 ]] && { ensure_global_pip_executable "$executable" || rc=1; }
1083
1037
  ;;
1084
- release-archive)
1085
- local version url sha256 archive_executable
1086
- version="$(jq -r --arg id "$tool_id" '.[$id].version' "$resolved_file")"
1087
- url="$(jq -r --arg id "$tool_id" '.[$id].url' "$resolved_file")"
1088
- sha256="$(jq -r --arg id "$tool_id" '.[$id].sha256' "$resolved_file")"
1089
- archive_executable="$(jq -r --arg id "$tool_id" '.[$id].archive_executable' "$resolved_file")"
1090
- if [[ "$mode" == apply && -x "$executable" ]] &&
1091
- "$executable" version 2>/dev/null | head -n 1 | grep -Fq "v$version"; then
1092
- echo "pinned release already installed: $package $version"
1093
- else
1094
- run_release_archive "$package" "$version" "$url" "$sha256" "$archive_executable" "$executable" || rc=1
1095
- fi
1096
- ;;
1097
1038
  release-archive-latest)
1098
- local repo asset checksums_asset archive_executable latest_tag latest_version
1039
+ local repo asset checksums_asset archive_executable latest_tag latest_version resolved_asset resolved_checksums_asset
1099
1040
  repo="$(jq -r --arg id "$tool_id" '.[$id].repo' "$resolved_file")"
1100
1041
  asset="$(jq -r --arg id "$tool_id" '.[$id].asset' "$resolved_file")"
1101
1042
  checksums_asset="$(jq -r --arg id "$tool_id" '.[$id].checksums_asset // "SHA256SUMS"' "$resolved_file")"
@@ -1106,10 +1047,17 @@ install_one_tool() {
1106
1047
  latest_tag="$(resolve_latest_release_tag "$repo")" || { rc=1; latest_tag=""; }
1107
1048
  if [[ "$rc" -eq 0 ]]; then
1108
1049
  latest_version="${latest_tag#v}"
1109
- if [[ -x "$executable" ]] && "$executable" --version 2>/dev/null | head -n 1 | grep -Fq " $latest_version "; then
1050
+ # A release whose asset filename bakes in the version (agent-vault:
1051
+ # agent-vault_<ver>_darwin_arm64.tar.gz) declares "{version}" in
1052
+ # .asset/.checksums_asset; lean-ctx's own asset names carry no
1053
+ # version at all, so this is a no-op substring replace for it.
1054
+ resolved_asset="${asset//\{version\}/$latest_version}"
1055
+ resolved_checksums_asset="${checksums_asset//\{version\}/$latest_version}"
1056
+ if [[ -x "$executable" ]] &&
1057
+ release_archive_version_matches "$("$executable" --version 2>/dev/null | head -n 1)" "$latest_version"; then
1110
1058
  echo "latest release already installed: $package $latest_version"
1111
1059
  else
1112
- run_release_archive_latest "$package" "$repo" "$asset" "$checksums_asset" "$archive_executable" "$executable" || rc=1
1060
+ run_release_archive_latest "$package" "$repo" "$latest_tag" "$resolved_asset" "$resolved_checksums_asset" "$archive_executable" "$executable" || rc=1
1113
1061
  fi
1114
1062
  fi
1115
1063
  fi
@@ -1654,6 +1602,212 @@ remove_one_repo_dir() {
1654
1602
  rm -rf -- "$target"
1655
1603
  }
1656
1604
 
1605
+ # Lexical-only path normalization: collapses repeated slashes ("//" ->
1606
+ # "/") and current-dir segments ("/./" -> "/"), and drops one trailing
1607
+ # slash. Never resolves ".." or symlinks, and never touches the filesystem
1608
+ # -- the target frequently doesn't exist yet when this runs. Second review
1609
+ # pass, 2026-09-29: without this, "/usr/local/bin//agent-vault" and
1610
+ # "/usr/local/bin/agent-vault" compare unequal even though they name the
1611
+ # same real file, silently defeating executable_claimed_by_current_manifest
1612
+ # below (the guard would say "not claimed" and let the old manager's `rm -f`
1613
+ # through) -- previous_resolved_file is never validated, so a malformed
1614
+ # path there is exactly the case this guard exists to protect against.
1615
+ normalize_manifest_path() {
1616
+ local path="$1"
1617
+ while [[ "$path" == *//* ]]; do path="${path//\/\//\/}"; done
1618
+ while [[ "$path" == */./* ]]; do path="${path//\/.\//\/}"; done
1619
+ path="${path%/}"
1620
+ printf '%s\n' "$path"
1621
+ }
1622
+
1623
+ # A previous entry's executable path can be EXACTLY the path a CURRENT
1624
+ # entry now owns -- e.g. agent-vault-cli migrating release-archive ->
1625
+ # release-archive-latest, both /usr/local/bin/agent-vault. Bug found in
1626
+ # review, 2026-09-29: the install loop runs BEFORE the removal loop below
1627
+ # and already wrote/verified that binary; "retain shared package after
1628
+ # entry deletion" only skips removal when manager+package are BOTH
1629
+ # unchanged, so a manager-only change (exactly this migration) still
1630
+ # selects the id for removal, and the OLD manager's case arm then does a
1631
+ # plain `rm -f` on the path the install pass upstream just installed.
1632
+ # Checked by executable path, not by tool_id/manager/package, because that
1633
+ # is the one thing the filesystem actually cares about -- both sides
1634
+ # normalized first (see normalize_manifest_path) so a malformed legacy path
1635
+ # in previous_resolved_file still compares equal to a clean one.
1636
+ #
1637
+ # Second review pass, 2026-09-29 (bare-name executables): most managers
1638
+ # store an ABSOLUTE path in .executable (release-archive-latest, shim,
1639
+ # bun-compile, repo-script all require the /usr/local/bin/ prefix), but pip,
1640
+ # npm, and brew store a bare command name instead -- pip's own
1641
+ # ensure_global_pip_executable/remove_global_pip_executable ALWAYS place (or
1642
+ # remove) that name as a fixed /usr/local/bin/<name> symlink regardless of
1643
+ # the pip package name, so a bare pip .executable maps there and IS
1644
+ # compared. npm and brew do not get the same treatment: their real install
1645
+ # location depends on the device's own npm global prefix / Homebrew prefix,
1646
+ # resolved only through a live `command -v` PATH search
1647
+ # (npm_global_executable_path) that varies by platform -- never a single
1648
+ # fixed path this function can compute without shelling out. A migration
1649
+ # INTO an npm or brew entry is not detected here and the old manager's real
1650
+ # removal proceeds exactly as before this fix. That gap is narrower than it
1651
+ # looks: npm and brew each remove through their OWN package manager (`npm
1652
+ # uninstall -g`, `brew uninstall`), never the raw `rm -f` this guard
1653
+ # protects, so nothing here relies on rm to leave an npm/brew-owned file
1654
+ # alone in the first place.
1655
+ executable_claimed_by_current_manifest() {
1656
+ local target="$1" normalized_target normalized_current current_manager current_executable
1657
+ normalized_target="$(normalize_manifest_path "$target")"
1658
+ while IFS=$'\t' read -r current_manager current_executable; do
1659
+ [[ -n "$current_executable" ]] || continue
1660
+ if [[ "$current_executable" == /* ]]; then
1661
+ normalized_current="$(normalize_manifest_path "$current_executable")"
1662
+ elif [[ "$current_manager" == pip ]]; then
1663
+ normalized_current="$(normalize_manifest_path "/usr/local/bin/$current_executable")"
1664
+ else
1665
+ continue
1666
+ fi
1667
+ [[ "$normalized_current" == "$normalized_target" ]] && return 0
1668
+ done < <(jq -r '.[] | [.manager, .executable] | @tsv' "$resolved_file")
1669
+ return 1
1670
+ }
1671
+
1672
+ # Uninstalls a single previously-applied tool the CURRENT manifest no longer
1673
+ # declares (or has migrated to a different manager/package) -- extracted
1674
+ # into its own function, like install_one_tool, so
1675
+ # packages/fleet/tests/test-manifest.sh can exercise it directly through the
1676
+ # RECONCILE_SOURCE_ONLY seam instead of only through a real apply run.
1677
+ remove_one_manifest_entry() {
1678
+ local tool_id="$1" manager package executable removed_repo_dir_target
1679
+ if jq -e --arg id "$tool_id" '.config.protected_tools | index($id)' "$manifest" >/dev/null; then
1680
+ echo "refusing to remove protected bootstrap tool: $tool_id" >&2
1681
+ exit 1
1682
+ fi
1683
+ manager="$(jq -r --arg id "$tool_id" '.[$id].manager' "$previous_resolved_file")"
1684
+ package="$(jq -r --arg id "$tool_id" '.[$id].package' "$previous_resolved_file")"
1685
+ executable="$(jq -r --arg id "$tool_id" '.[$id].executable' "$previous_resolved_file")"
1686
+ if is_in_place_bun_to_npm_migration "$tool_id"; then
1687
+ if [[ "$mode" == apply ]]; then
1688
+ echo "retired Bun compilation already removed for npm migration: $tool_id"
1689
+ fi
1690
+ return 0
1691
+ fi
1692
+ if jq -e --arg manager "$manager" --arg package "$package" 'to_entries | any(.value.manager == $manager and .value.package == $package)' "$resolved_file" >/dev/null; then
1693
+ echo "retain shared package after entry deletion: $manager:$package"
1694
+ return 0
1695
+ fi
1696
+ # Truthfulness fix (second review pass, 2026-09-29): for the managers that
1697
+ # remove via a raw filesystem `rm -f` on a fixed path (never their own
1698
+ # package manager's uninstall command), check executable_claimed_by_
1699
+ # current_manifest BEFORE claiming an "uninstall" happened at all. The
1700
+ # log line printed below this case is the only outcome a human or another
1701
+ # tool sees; printing "explicit manifest deletion: uninstall ..." and only
1702
+ # THEN discovering the file is retained (as this used to) makes the log
1703
+ # lie about what just happened, exactly backwards from "log the decision
1704
+ # as the outcome."
1705
+ case "$manager" in
1706
+ release-archive | release-archive-latest | shim | bun-compile)
1707
+ if executable_claimed_by_current_manifest "$executable"; then
1708
+ echo "retain $tool_id ($manager:$package): $executable is still declared by the current manifest"
1709
+ return 0
1710
+ fi
1711
+ ;;
1712
+ esac
1713
+ echo "explicit manifest deletion: uninstall $tool_id ($manager:$package)"
1714
+ case "$manager" in
1715
+ brew) run_brew uninstall "$package" ;;
1716
+ apt) run_apt remove -y "$package" ;;
1717
+ npm) run_npm uninstall -g "$package" ;;
1718
+ pip)
1719
+ run_pip uninstall --break-system-packages -y "$package"
1720
+ remove_global_pip_executable "$executable"
1721
+ ;;
1722
+ # "release-archive" (pinned version+url+sha256) is retired from
1723
+ # validate-manifest's accepted managers (Tarun, 2026-09-29: versions
1724
+ # are never pinned) -- no CURRENT manifest.json can declare one, so
1725
+ # install_one_tool no longer has a case for it. This removal arm stays
1726
+ # so a device whose applied-manifest.json still remembers an old
1727
+ # release-archive entry (agent-vault-cli, before this change) can
1728
+ # still cleanly uninstall it the next time it reconciles; previous
1729
+ # state is read straight from disk, never re-validated.
1730
+ release-archive)
1731
+ [[ "$executable" == /usr/local/bin/* ]] || {
1732
+ echo "refusing unsafe release archive removal target: $executable" >&2
1733
+ exit 1
1734
+ }
1735
+ if [[ "$mode" == apply ]]; then
1736
+ rm -f -- "$executable"
1737
+ else
1738
+ echo "would remove pinned release executable: $executable"
1739
+ fi
1740
+ ;;
1741
+ release-archive-latest)
1742
+ [[ "$executable" == /usr/local/bin/* ]] || {
1743
+ echo "refusing unsafe release archive removal target: $executable" >&2
1744
+ exit 1
1745
+ }
1746
+ if [[ "$mode" == apply ]]; then
1747
+ rm -f -- "$executable"
1748
+ else
1749
+ echo "would remove latest release executable: $executable"
1750
+ fi
1751
+ ;;
1752
+ shim)
1753
+ [[ "$executable" == /usr/local/bin/* ]] || {
1754
+ echo "refusing unsafe shim removal target: $executable" >&2
1755
+ exit 1
1756
+ }
1757
+ if [[ "$mode" == apply ]]; then
1758
+ rm -f -- "$executable"
1759
+ else
1760
+ echo "would remove shim: $executable"
1761
+ fi
1762
+ ;;
1763
+ repo-script)
1764
+ if [[ "$mode" == apply ]]; then
1765
+ remove_one_repo_script "$executable"
1766
+ else
1767
+ [[ "$executable" =~ ^/usr/local/bin/[A-Za-z0-9][A-Za-z0-9._-]*$ ]] || {
1768
+ echo "refusing unsafe managed script removal target: $executable" >&2
1769
+ exit 1
1770
+ }
1771
+ echo "would remove managed repo script: $executable"
1772
+ fi
1773
+ ;;
1774
+ repo-dir)
1775
+ removed_repo_dir_target="$(jq -r --arg id "$tool_id" '.[$id].target' "$previous_resolved_file")"
1776
+ if [[ "$mode" == apply ]]; then
1777
+ remove_one_repo_dir "$removed_repo_dir_target"
1778
+ else
1779
+ [[ "$removed_repo_dir_target" == "$repo_dir_libexec_root"/* ]] || {
1780
+ echo "refusing unsafe repo-dir removal target: $removed_repo_dir_target" >&2
1781
+ exit 1
1782
+ }
1783
+ echo "would remove repo dir: $removed_repo_dir_target"
1784
+ fi
1785
+ ;;
1786
+ bun-compile)
1787
+ [[ "$executable" == /usr/local/bin/* ]] || {
1788
+ echo "refusing unsafe bun binary removal target: $executable" >&2
1789
+ exit 1
1790
+ }
1791
+ if [[ "$mode" == apply ]]; then
1792
+ rm -f -- "$executable"
1793
+ else
1794
+ echo "would remove bun binary: $executable"
1795
+ fi
1796
+ ;;
1797
+ embedding-model)
1798
+ if [[ "$mode" == apply ]]; then
1799
+ [[ "$(id -u)" -eq 0 ]] || { echo "apply mode must run as root" >&2; exit 1; }
1800
+ command -v bun >/dev/null 2>&1 || { echo "embedding-model requires bun; ensure bun installs before this tool" >&2; exit 1; }
1801
+ (cd /tmp && sudo -u "$administrator" -H env PATH="${brew_bin%/*}:/usr/bin:/bin" \
1802
+ bun "$repo_root/packages/fleet/src/install-embedding-model.ts" uninstall </dev/null)
1803
+ else
1804
+ echo "would remove embedding model cache: ~/.cache/adl-embedding/model (as $administrator)"
1805
+ fi
1806
+ ;;
1807
+ *) echo "unsupported manager in previous manifest: $manager" >&2; exit 1 ;;
1808
+ esac
1809
+ }
1810
+
1657
1811
  # config.git_global_config: global git config for the administrator (the
1658
1812
  # account whose HOME the per-user deployers below also write to). Apply runs git as
1659
1813
  # that user (sudo -u -H) so ~/.gitconfig stays user-owned; plan/audit only
@@ -2196,111 +2350,7 @@ reconcile_surface_clis
2196
2350
  if [[ "$have_previous" -eq 1 ]]; then
2197
2351
  while IFS= read -r tool_id; do
2198
2352
  [[ -n "$tool_id" ]] || continue
2199
- if jq -e --arg id "$tool_id" '.config.protected_tools | index($id)' "$manifest" >/dev/null; then
2200
- echo "refusing to remove protected bootstrap tool: $tool_id" >&2
2201
- exit 1
2202
- fi
2203
- manager="$(jq -r --arg id "$tool_id" '.[$id].manager' "$previous_resolved_file")"
2204
- package="$(jq -r --arg id "$tool_id" '.[$id].package' "$previous_resolved_file")"
2205
- executable="$(jq -r --arg id "$tool_id" '.[$id].executable' "$previous_resolved_file")"
2206
- if is_in_place_bun_to_npm_migration "$tool_id"; then
2207
- if [[ "$mode" == apply ]]; then
2208
- echo "retired Bun compilation already removed for npm migration: $tool_id"
2209
- fi
2210
- continue
2211
- fi
2212
- if jq -e --arg manager "$manager" --arg package "$package" 'to_entries | any(.value.manager == $manager and .value.package == $package)' "$resolved_file" >/dev/null; then
2213
- echo "retain shared package after entry deletion: $manager:$package"
2214
- continue
2215
- fi
2216
- echo "explicit manifest deletion: uninstall $tool_id ($manager:$package)"
2217
- case "$manager" in
2218
- brew) run_brew uninstall "$package" ;;
2219
- apt) run_apt remove -y "$package" ;;
2220
- npm) run_npm uninstall -g "$package" ;;
2221
- pip)
2222
- run_pip uninstall --break-system-packages -y "$package"
2223
- remove_global_pip_executable "$executable"
2224
- ;;
2225
- release-archive)
2226
- [[ "$executable" == /usr/local/bin/* ]] || {
2227
- echo "refusing unsafe release archive removal target: $executable" >&2
2228
- exit 1
2229
- }
2230
- if [[ "$mode" == apply ]]; then
2231
- rm -f -- "$executable"
2232
- else
2233
- echo "would remove pinned release executable: $executable"
2234
- fi
2235
- ;;
2236
- release-archive-latest)
2237
- [[ "$executable" == /usr/local/bin/* ]] || {
2238
- echo "refusing unsafe release archive removal target: $executable" >&2
2239
- exit 1
2240
- }
2241
- if [[ "$mode" == apply ]]; then
2242
- rm -f -- "$executable"
2243
- else
2244
- echo "would remove latest release executable: $executable"
2245
- fi
2246
- ;;
2247
- shim)
2248
- [[ "$executable" == /usr/local/bin/* ]] || {
2249
- echo "refusing unsafe shim removal target: $executable" >&2
2250
- exit 1
2251
- }
2252
- if [[ "$mode" == apply ]]; then
2253
- rm -f -- "$executable"
2254
- else
2255
- echo "would remove shim: $executable"
2256
- fi
2257
- ;;
2258
- repo-script)
2259
- if [[ "$mode" == apply ]]; then
2260
- remove_one_repo_script "$executable"
2261
- else
2262
- [[ "$executable" =~ ^/usr/local/bin/[A-Za-z0-9][A-Za-z0-9._-]*$ ]] || {
2263
- echo "refusing unsafe managed script removal target: $executable" >&2
2264
- exit 1
2265
- }
2266
- echo "would remove managed repo script: $executable"
2267
- fi
2268
- ;;
2269
- repo-dir)
2270
- removed_repo_dir_target="$(jq -r --arg id "$tool_id" '.[$id].target' "$previous_resolved_file")"
2271
- if [[ "$mode" == apply ]]; then
2272
- remove_one_repo_dir "$removed_repo_dir_target"
2273
- else
2274
- [[ "$removed_repo_dir_target" == "$repo_dir_libexec_root"/* ]] || {
2275
- echo "refusing unsafe repo-dir removal target: $removed_repo_dir_target" >&2
2276
- exit 1
2277
- }
2278
- echo "would remove repo dir: $removed_repo_dir_target"
2279
- fi
2280
- ;;
2281
- bun-compile)
2282
- [[ "$executable" == /usr/local/bin/* ]] || {
2283
- echo "refusing unsafe bun binary removal target: $executable" >&2
2284
- exit 1
2285
- }
2286
- if [[ "$mode" == apply ]]; then
2287
- rm -f -- "$executable"
2288
- else
2289
- echo "would remove bun binary: $executable"
2290
- fi
2291
- ;;
2292
- embedding-model)
2293
- if [[ "$mode" == apply ]]; then
2294
- [[ "$(id -u)" -eq 0 ]] || { echo "apply mode must run as root" >&2; exit 1; }
2295
- command -v bun >/dev/null 2>&1 || { echo "embedding-model requires bun; ensure bun installs before this tool" >&2; exit 1; }
2296
- (cd /tmp && sudo -u "$administrator" -H env PATH="${brew_bin%/*}:/usr/bin:/bin" \
2297
- bun "$repo_root/packages/fleet/src/install-embedding-model.ts" uninstall </dev/null)
2298
- else
2299
- echo "would remove embedding model cache: ~/.cache/adl-embedding/model (as $administrator)"
2300
- fi
2301
- ;;
2302
- *) echo "unsupported manager in previous manifest: $manager" >&2; exit 1 ;;
2303
- esac
2353
+ remove_one_manifest_entry "$tool_id"
2304
2354
  # Selects an id when it was deleted outright (absent from the current
2305
2355
  # manifest) OR retained but migrated to a different manager/package (e.g.
2306
2356
  # lean-ctx moving from npm:lean-ctx-bin to release-archive-latest:lean-ctx)
@@ -70,18 +70,19 @@ if ! jq -e '
70
70
  (.mac.devices.m3.surface_clis // [] | any(.name == "codex" and .manager == "brew" and .command == "brew" and .update_args == ["upgrade", "--cask", "codex"] and (.report_only // false) == false)) and
71
71
  (.mac.devices.m5.surface_clis // [] | any(.name == "codex" and .manager == "brew" and .command == "brew" and .update_args == ["upgrade", "--cask", "codex"] and (.report_only // false) == false)) and
72
72
  ([.mac.common.tools[], .linux.common.tools[], .mac.devices[].tools[], .linux.devices[].tools[]] |
73
- all(.manager == "brew" or .manager == "apt" or .manager == "npm" or .manager == "pip" or .manager == "release-archive" or .manager == "release-archive-latest" or .manager == "shim" or .manager == "repo-script" or .manager == "repo-dir" or .manager == "bun-compile" or .manager == "embedding-model") and
73
+ all(.manager == "brew" or .manager == "apt" or .manager == "npm" or .manager == "pip" or .manager == "release-archive-latest" or .manager == "shim" or .manager == "repo-script" or .manager == "repo-dir" or .manager == "bun-compile" or .manager == "embedding-model") and
74
74
  all((.package | type) == "string" and (.package | length) > 0) and
75
75
  all((.executable | type) == "string" and (.executable | length) > 0) and
76
+ # Second review pass, 2026-09-29: the removal-path guard in bin/
77
+ # reconcile (executable_claimed_by_current_manifest) compares
78
+ # .executable paths lexically after normalizing a legacy, unvalidated
79
+ # previous-manifest path -- a CURRENT manifest must never need that
80
+ # normalization in the first place, so a "//" or "/./" segment is
81
+ # rejected here outright rather than silently accepted and normalized
82
+ # away later.
83
+ all((.executable | contains("//") | not) and (.executable | contains("/./") | not)) and
76
84
  all((.reason // null) == null or (.reason | type == "string" and length > 0)) and
77
- all(if .manager == "release-archive" then
78
- ((.version | type) == "string" and (.version | length) > 0) and
79
- ((.url | type) == "string" and (.url | startswith("https://"))) and
80
- ((.sha256 | type) == "string" and (.sha256 | test("^[0-9a-f]{64}$"))) and
81
- ((.archive_executable | type) == "string" and
82
- (.archive_executable | test("^[^/]") and (contains("..") | not))) and
83
- (.executable | startswith("/usr/local/bin/"))
84
- elif .manager == "release-archive-latest" then
85
+ all(if .manager == "release-archive-latest" then
85
86
  ((.repo | type) == "string" and (.repo | test("^[^/[:space:]]+/[^/[:space:]]+$"))) and
86
87
  ((.asset | type) == "string" and
87
88
  (.asset | test("^[^/]") and (contains("..") | not))) and
@@ -124,12 +125,13 @@ if ! jq -e '
124
125
  ((.dim | type) == "number") and
125
126
  (has("revision") | not) and (has("source_files") | not) and (has("artifact_files") | not)
126
127
  elif .manager == "npm" then
127
- # .version is optional for npm: absent means update_policy=latest-stable
128
- # tracks whatever npm resolves; present pins the install_one_tool
129
- # $package@$version spec (Handoff 5, 2026-09-26, H4). No checksum field
130
- # exists for npm -- there is no release-archive-style sha256 pin here,
131
- # and none is required or accepted.
132
- ((has("version") | not) or ((.version | type) == "string" and (.version | length) > 0)) and
128
+ # Tarun, 2026-09-29 (hard rule): versions are never pinned -- every
129
+ # fleet tool installs latest stable. .version on npm used to be an
130
+ # optional pin (Handoff 5, 2026-09-26, H4); it is refused outright now,
131
+ # the same way release-archive-latest already refuses one, and the
132
+ # same day the release-archive manager (pinned version+url+sha256) was
133
+ # removed from the accepted managers above entirely.
134
+ (has("version") | not) and
133
135
  # .allow_scripts (docs/operations/global-tools.md Topic 2, decided
134
136
  # 2026-09-28): an explicit, per-entry allow-list of dependency package
135
137
  # names whose install/postinstall scripts reconcile passes through as
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amsterdamdatalabs/adl-agent-control-linux-x64",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "adl CLI binary for linux/x64 (agent identity + Azure DevOps front door — see docs/architecture/cli-design.md)",
5
5
  "license": "UNLICENSED",
6
6
  "os": [
@@ -44,7 +44,7 @@
44
44
  "mac": {
45
45
  "common": {
46
46
  "tools": {
47
- "agent-vault-cli": {"manager": "release-archive", "package": "agent-vault", "executable": "/usr/local/bin/agent-vault", "version": "0.39.3", "url": "https://github.com/Infisical/agent-vault/releases/download/v0.39.3/agent-vault_0.39.3_darwin_arm64.tar.gz", "sha256": "30e312f3016a489936ae27bc380f92bf715f3be88c35eba3030d55841d97dc14", "archive_executable": "agent-vault"},
47
+ "agent-vault-cli": {"manager": "release-archive-latest", "package": "agent-vault", "executable": "/usr/local/bin/agent-vault", "repo": "Infisical/agent-vault", "asset": "agent-vault_{version}_darwin_arm64.tar.gz", "checksums_asset": "checksums.txt", "archive_executable": "agent-vault"},
48
48
  "ast-grep": {"manager": "brew", "package": "ast-grep", "executable": "ast-grep"},
49
49
  "azure-cli": {"manager": "brew", "package": "azure-cli", "executable": "az"},
50
50
  "bun": {"manager": "brew", "package": "bun", "executable": "bun"},
@@ -57,7 +57,7 @@
57
57
  "docker-compose": {"manager": "brew", "package": "docker-compose", "executable": "docker-compose"},
58
58
  "docs-search": {"manager": "npm", "package": "@amsterdamdatalabs/enact-docs-search", "executable": "enact-docs-search", "allow_scripts": ["hnswlib-node", "onnxruntime-node", "sharp", "protobufjs"], "reason": "Separate search product that provides the search tool."},
59
59
  "adl": {"manager": "npm", "package": "@amsterdamdatalabs/adl-agent-control", "executable": "adl"},
60
- "adl-agent-id": {"manager": "npm", "package": "@amsterdamdatalabs/adl-agent-id", "executable": "adl-agent-id", "version": "0.4.0", "reason": "Separate Go binary with its own trust boundary."},
60
+ "adl-agent-id": {"manager": "npm", "package": "@amsterdamdatalabs/adl-agent-id", "executable": "adl-agent-id", "reason": "Separate Go binary with its own trust boundary."},
61
61
  "fd": {"manager": "brew", "package": "fd", "executable": "fd"},
62
62
  "git": {"manager": "brew", "package": "git", "executable": "git"},
63
63
  "jq": {"manager": "brew", "package": "jq", "executable": "jq"},
@@ -134,7 +134,7 @@
134
134
  "common": {
135
135
  "os_upgrade": true,
136
136
  "tools": {
137
- "agent-vault-cli": {"manager": "release-archive", "package": "agent-vault", "executable": "/usr/local/bin/agent-vault", "version": "0.39.3", "url": "https://github.com/Infisical/agent-vault/releases/download/v0.39.3/agent-vault_0.39.3_linux_amd64.tar.gz", "sha256": "edacbddb03c6f4221515d9875c3a7b0943c71434708e7fd13be0f7635a7baecc", "archive_executable": "agent-vault"},
137
+ "agent-vault-cli": {"manager": "release-archive-latest", "package": "agent-vault", "executable": "/usr/local/bin/agent-vault", "repo": "Infisical/agent-vault", "asset": "agent-vault_{version}_linux_amd64.tar.gz", "checksums_asset": "checksums.txt", "archive_executable": "agent-vault"},
138
138
  "ast-grep": {"manager": "brew", "package": "ast-grep", "executable": "ast-grep"},
139
139
  "azure-cli": {"manager": "brew", "package": "azure-cli", "executable": "az"},
140
140
  "bun": {"manager": "brew", "package": "bun", "executable": "bun"},
@@ -147,7 +147,7 @@
147
147
  "docker-compose": {"manager": "brew", "package": "docker-compose", "executable": "docker-compose"},
148
148
  "docs-search": {"manager": "npm", "package": "@amsterdamdatalabs/enact-docs-search", "executable": "enact-docs-search", "allow_scripts": ["hnswlib-node", "onnxruntime-node", "sharp", "protobufjs"], "reason": "Separate search product that provides the search tool."},
149
149
  "adl": {"manager": "npm", "package": "@amsterdamdatalabs/adl-agent-control", "executable": "adl"},
150
- "adl-agent-id": {"manager": "npm", "package": "@amsterdamdatalabs/adl-agent-id", "executable": "adl-agent-id", "version": "0.4.0", "reason": "Separate Go binary with its own trust boundary."},
150
+ "adl-agent-id": {"manager": "npm", "package": "@amsterdamdatalabs/adl-agent-id", "executable": "adl-agent-id", "reason": "Separate Go binary with its own trust boundary."},
151
151
  "fd": {"manager": "brew", "package": "fd", "executable": "fd"},
152
152
  "git": {"manager": "brew", "package": "git", "executable": "git"},
153
153
  "jq": {"manager": "brew", "package": "jq", "executable": "jq"},
@@ -93,7 +93,7 @@
93
93
  "additionalProperties": false,
94
94
  "required": ["manager", "package", "executable"],
95
95
  "properties": {
96
- "manager": {"enum": ["apt", "brew", "npm", "pip", "release-archive", "release-archive-latest", "shim", "repo-script", "bun-compile", "embedding-model"]},
96
+ "manager": {"enum": ["apt", "brew", "npm", "pip", "release-archive-latest", "shim", "repo-script", "bun-compile", "embedding-model"]},
97
97
  "package": {"type": "string", "minLength": 1},
98
98
  "executable": {"type": "string", "minLength": 1},
99
99
  "version": {"type": "string", "minLength": 1},
@@ -110,17 +110,18 @@
110
110
  "reason": {"type": "string", "minLength": 1}
111
111
  },
112
112
  "allOf": [
113
- {
114
- "if": {"properties": {"manager": {"const": "release-archive"}}},
115
- "then": {"required": ["version", "url", "sha256", "archive_executable"]}
116
- },
117
113
  {
118
114
  "if": {"properties": {"manager": {"const": "release-archive-latest"}}},
119
115
  "then": {
120
116
  "required": ["repo", "asset", "archive_executable"],
117
+ "not": {"anyOf": [{"required": ["version"]}, {"required": ["url"]}, {"required": ["sha256"]}]},
121
118
  "properties": {"executable": {"pattern": "^/usr/local/bin/"}}
122
119
  }
123
120
  },
121
+ {
122
+ "if": {"properties": {"manager": {"const": "npm"}}},
123
+ "then": {"not": {"required": ["version"]}}
124
+ },
124
125
  {
125
126
  "if": {"properties": {"manager": {"const": "shim"}}},
126
127
  "then": {"properties": {"executable": {"pattern": "^/usr/local/bin/"}}}
@@ -120,6 +120,23 @@ export const GITIGNORE_BLOCK_LINES: readonly string[] = [
120
120
  '# adl process convention: scratch and throwaway work, never committed',
121
121
  '/.scratchpad/',
122
122
  'packages/*/.scratchpad/',
123
+ '',
124
+ '# fleet tools that write into every checkout: docs-search index, code-review-graph, compliance pytest',
125
+ '.cache/',
126
+ '.code-review-graph/',
127
+ '.codegraph/',
128
+ '.pytest_cache/',
129
+ '',
130
+ '# dependencies, OS and log noise',
131
+ 'node_modules/',
132
+ '.DS_Store',
133
+ '*.log',
134
+ '',
135
+ '# local secrets and per-user agent settings, never committed',
136
+ '.env',
137
+ '.env.*',
138
+ '!.env.example',
139
+ '.claude/settings.local.json',
123
140
  ] as const
124
141
 
125
142
  interface SplitText {