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

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.
Files changed (41) hide show
  1. package/bin/adl +2 -2
  2. package/bin/lean-ctx-observe-strip +76 -3
  3. package/bin/reconcile +460 -240
  4. package/bin/validate-manifest +36 -16
  5. package/package.json +1 -1
  6. package/packages/agent-hooks/src/deploy/deploy.ts +98 -18
  7. package/packages/agent-hooks/src/deploy/doctor.ts +61 -11
  8. package/packages/agent-hooks/src/deploy/json-merge.ts +430 -18
  9. package/packages/agent-hooks/src/deploy/lock.ts +36 -2
  10. package/packages/agent-hooks/src/lean-ctx/env.ts +17 -0
  11. package/packages/agent-hooks/src/lean-ctx/mcp.ts +4 -2
  12. package/packages/agent-settings/assets/lean-ctx/config.toml +3 -0
  13. package/packages/agent-settings/assets/lean-ctx/gitignore +4 -0
  14. package/packages/agent-settings/assets/lean-ctx/layout.toml +2 -0
  15. package/packages/agent-settings/src/deploy/agents-deploy.ts +10 -5
  16. package/packages/agent-settings/src/deploy/doctor.ts +39 -3
  17. package/packages/agent-settings/src/deploy/lean-ctx-deploy.ts +104 -12
  18. package/packages/agent-settings/src/deploy/lean-ctx-merge.ts +580 -0
  19. package/packages/agent-settings/src/deploy/lock.ts +4 -1
  20. package/packages/agent-settings/src/deploy/mcp-deploy.ts +9 -6
  21. package/packages/agent-settings/src/deploy/settings.ts +6 -3
  22. package/packages/agent-skills/dist/deploy.d.ts +2 -0
  23. package/packages/agent-skills/dist/deploy.d.ts.map +1 -1
  24. package/packages/agent-skills/dist/deploy.js +15 -8
  25. package/packages/agent-skills/dist/deploy.js.map +1 -1
  26. package/packages/agent-skills/dist/manifest.d.ts +1 -0
  27. package/packages/agent-skills/dist/manifest.d.ts.map +1 -1
  28. package/packages/agent-skills/dist/manifest.js +6 -2
  29. package/packages/agent-skills/dist/manifest.js.map +1 -1
  30. package/packages/agent-skills/src/deploy.ts +15 -8
  31. package/packages/agent-skills/src/manifest.ts +5 -2
  32. package/packages/azdo/src/azdo-repo-doctor.ts +401 -91
  33. package/packages/azdo/src/lib/graph-identity.ts +190 -3
  34. package/packages/fleet/jobs/git-maintain/src/main.ts +305 -45
  35. package/packages/fleet/manifest.json +6 -4
  36. package/packages/fleet/manifest.schema.json +15 -5
  37. package/packages/repo-controls/README.md +13 -6
  38. package/packages/repo-controls/assets/adl-config.default.toml +1 -0
  39. package/packages/repo-controls/src/cli.ts +6 -1
  40. package/packages/repo-controls/src/gitignore-block.ts +17 -0
  41. package/packages/repo-controls/src/install.ts +88 -6
package/bin/reconcile CHANGED
@@ -180,6 +180,12 @@ collect_lean_ctx_observe_strip_backups() {
180
180
  original="${BASH_REMATCH[1]}"
181
181
  backup="${BASH_REMATCH[2]}"
182
182
  record_backup "$original" "$backup"
183
+ elif [[ "$line" =~ ^lean-ctx-observe-strip:\ backed\ up\ previous\ hook\ script\ (.+)\ to\ (.+)$ ]]; then
184
+ # ~/.claude/hooks/lean-ctx-* scripts: same two-path shape as a skill
185
+ # directory (sibling "hooks.pre-lean-ctx-observe-strip-<ts>/" backup).
186
+ original="${BASH_REMATCH[1]}"
187
+ backup="${BASH_REMATCH[2]}"
188
+ record_backup "$original" "$backup"
183
189
  fi
184
190
  done <<<"$text"
185
191
  }
@@ -456,28 +462,15 @@ run_npm() {
456
462
  fi
457
463
  }
458
464
 
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
465
  # Read-only Homebrew query, run exactly like run_brew's real mutations (same
473
466
  # owner, cwd, and stdin) but never a mutation itself. A named function so
474
467
  # tests can stub just this, instead of having to fake sudo's own argument
475
468
  # 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.
469
+ # Apply-mode only: `brew list`/`brew outdated` have been observed to take
470
+ # minutes under real load on a contended box (Homebrew's own auto-update
471
+ # against a large git checkout), so a plan preview never calls this -- it
472
+ # would turn a "quick look" into a slow, load-dependent one for every brew
473
+ # formula on the device.
481
474
  brew_query() {
482
475
  (cd /tmp && sudo -u "$brew_owner" -H "$brew_bin" "$@" </dev/null)
483
476
  }
@@ -800,6 +793,27 @@ install_surface_policy_file() {
800
793
  echo "installed required surface policy: $target"
801
794
  }
802
795
 
796
+ # A resolved release version against the first line of a release-archive-
797
+ # latest tool's own `--version` output. Different tools format that line
798
+ # differently -- lean-ctx: "lean-ctx 3.10.5 (official, ...)" (version padded
799
+ # by spaces on both sides); agent-vault: "agent-vault version 0.39.3" (flush
800
+ # against the end of the line, no trailing space); a plain "vX.Y.Z" line
801
+ # with no separating space before the version at all -- so the LEFT boundary
802
+ # is a string start or a real space, with an OPTIONAL literal "v" directly
803
+ # after that boundary (never "v" floating anywhere: found in review,
804
+ # 2026-09-28, `(^|[ v])` treated a bare "v" as equivalent to a space no
805
+ # matter what preceded it, so "tool dev0.39.3"/"tool rev0.39.3" -- where the
806
+ # "v" belongs to "dev"/"rev", not a version prefix -- silently matched and
807
+ # would have skipped a real upgrade). The RIGHT boundary is a space or a
808
+ # string end and never accepts "v", or "0.39.3" would incorrectly match
809
+ # inside "0.39.30". Dots in the version are escaped so they match literally,
810
+ # not "any character," in the regex below.
811
+ release_archive_version_matches() {
812
+ local version_line="$1" version="$2" escaped
813
+ escaped="$(printf '%s' "$version" | sed 's/[.[\*^$()+?{}|]/\\&/g')"
814
+ grep -Eq "(^| )v?${escaped}( |\$)" <<<"$version_line"
815
+ }
816
+
803
817
  # Resolves the release tag GitHub's own "latest" redirect currently points
804
818
  # at, without touching the api.github.com REST API (unauthenticated calls
805
819
  # there are rate-limited per source IP; this fleet's shared egress IP already
@@ -813,65 +827,33 @@ resolve_latest_release_tag() {
813
827
  basename "$location"
814
828
  }
815
829
 
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.
830
+ # A GitHub release archive, installed under /usr/local/bin, verified against
831
+ # a checksum published *in that same release* (never a value carried over
832
+ # from a previous run or hardcoded here) -- fails loud, installs nothing, if
833
+ # the checksums asset is missing or doesn't list our asset. It always
834
+ # installs whatever GitHub's own "latest" release was when install_one_tool
835
+ # resolved $tag (resolve_latest_release_tag), fetched with $tag pinned into
836
+ # BOTH download URLs -- `.../releases/download/<tag>/<name>`, never through
837
+ # GitHub's separate "always whatever is latest right now" alias for the
838
+ # downloads themselves. Fixed 2026-09-29 (review): that alias re-resolves on
839
+ # every request, so a release published between the tag resolve and the
840
+ # archive download would otherwise serve the checksums file from one release
841
+ # and the archive from a newer one -- silently mixing versions past the
842
+ # checksum check (which would still "pass" against whichever checksums file
843
+ # actually got fetched). Pinning both fetches to the one $tag closes that
844
+ # window.
845
+ # Tarun, 2026-09-29 (hard rule): versions are never pinned in the manifest --
846
+ # this replaced the old pinned run_release_archive (fixed version + url +
847
+ # sha256), which validate-manifest no longer accepts on any manager; $tag
848
+ # here is resolved fresh every run, never carried over or hardcoded.
868
849
  run_release_archive_latest() {
869
850
  local package="$1"
870
851
  local repo="$2"
871
- local asset="$3"
872
- local checksums_asset="$4"
873
- local archive_executable="$5"
874
- local target="$6"
852
+ local tag="$3"
853
+ local asset="$4"
854
+ local checksums_asset="$5"
855
+ local archive_executable="$6"
856
+ local target="$7"
875
857
  [[ "$(id -u)" -eq 0 ]] || { echo "apply mode must run as root" >&2; exit 1; }
876
858
  [[ "$target" == /usr/local/bin/* ]] || { echo "release archive target must be under /usr/local/bin: $target" >&2; exit 1; }
877
859
  [[ "$archive_executable" != /* && "$archive_executable" != *..* ]] || {
@@ -888,13 +870,13 @@ run_release_archive_latest() {
888
870
  extract_dir="$release_dir/extract"
889
871
  mkdir -p "$extract_dir"
890
872
  curl --proto '=https' --proto-redir '=https' -fsSL --retry 3 \
891
- -o "$checksums_file" "https://github.com/$repo/releases/latest/download/$checksums_asset"
873
+ -o "$checksums_file" "https://github.com/$repo/releases/download/$tag/$checksums_asset"
892
874
  expected_sha256="$(awk -v want="$asset" '$2 == want { print $1; found = 1 } END { exit !found }' "$checksums_file")" || {
893
875
  echo "no published checksum for $asset in $repo's $checksums_asset -- refusing to install unverified bytes" >&2
894
876
  exit 1
895
877
  }
896
878
  curl --proto '=https' --proto-redir '=https' -fsSL --retry 3 \
897
- -o "$archive_file" "https://github.com/$repo/releases/latest/download/$asset"
879
+ -o "$archive_file" "https://github.com/$repo/releases/download/$tag/$asset"
898
880
  case "$(uname -s)" in
899
881
  Darwin) actual_sha256="$(shasum -a 256 "$archive_file" | awk '{print $1}')" ;;
900
882
  Linux) actual_sha256="$(sha256sum "$archive_file" | awk '{print $1}')" ;;
@@ -944,7 +926,7 @@ resolve_surface_mode() {
944
926
  # root") is a real environment fault, not a flaky install, and still
945
927
  # aborts immediately -- `exit` is unconditional and is never caught here.
946
928
  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
929
+ 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
930
  manager="$(jq -r --arg id "$tool_id" '.[$id].manager' "$resolved_file")"
949
931
  package="$(jq -r --arg id "$tool_id" '.[$id].package' "$resolved_file")"
950
932
  executable="$(jq -r --arg id "$tool_id" '.[$id].executable' "$resolved_file")"
@@ -1000,15 +982,14 @@ install_one_tool() {
1000
982
  run_apt install -y "$package" || rc=1
1001
983
  ;;
1002
984
  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
985
+ # Tarun, 2026-09-29 (hard rule): versions are never pinned -- every
986
+ # fleet tool installs latest stable. npm entries never carry a
987
+ # .version (validate-manifest rejects one outright); $npm_spec stays a
988
+ # separate variable from $package only because the exact run_npm
989
+ # invocation below is asserted at the source level. Ownership
1006
990
  # 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")"
991
+ # tree and package.json are keyed by package, never by a version spec.
1010
992
  npm_spec="$package"
1011
- [[ -z "$npm_version" ]] || npm_spec="$package@$npm_version"
1012
993
  # Only @amsterdamdatalabs/enact-docs-search's manifest entry carries
1013
994
  # this (docs/operations/global-tools.md Topic 2, decided 2026-09-28):
1014
995
  # its own dependencies' install scripts, explicitly reviewed and
@@ -1019,41 +1000,20 @@ install_one_tool() {
1019
1000
  previous_executable="$(jq -r --arg id "$tool_id" '.[$id].executable' "$previous_resolved_file")"
1020
1001
  echo "would remove retired Bun compilation before npm install: $tool_id ($previous_executable and $(bun_source_hash_file "$tool_id"))"
1021
1002
  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
1003
+ # "latest stable" is not knowable without a per-package registry
1004
+ # round trip (npm_latest_version) -- doing that here would make a
1005
+ # plan preview slow and network-bound for every npm tool on the
1006
+ # device, so plan mode always prints this unconditional message
1007
+ # instead of guessing.
1008
+ echo "would install or upgrade npm package: $npm_spec"
1045
1009
  else
1046
1010
  if is_in_place_bun_to_npm_migration "$tool_id"; then
1047
1011
  migrating_from_bun=1
1048
1012
  prepare_in_place_bun_to_npm_migration "$tool_id" || rc=1
1049
1013
  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
1014
+ if [[ "$rc" -eq 0 ]] && ! npm_target_version="$(npm_latest_version "$package")"; then
1015
+ echo "$tool_id: npm view could not resolve the latest version of $package" >&2
1016
+ rc=1
1057
1017
  fi
1058
1018
  if [[ "$rc" -eq 0 ]]; then
1059
1019
  if ! npm_installed_ver="$(npm_installed_version "$package")"; then
@@ -1081,21 +1041,8 @@ install_one_tool() {
1081
1041
  fi
1082
1042
  [[ "$rc" -eq 0 ]] && { ensure_global_pip_executable "$executable" || rc=1; }
1083
1043
  ;;
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
1044
  release-archive-latest)
1098
- local repo asset checksums_asset archive_executable latest_tag latest_version
1045
+ local repo asset checksums_asset archive_executable latest_tag latest_version resolved_asset resolved_checksums_asset
1099
1046
  repo="$(jq -r --arg id "$tool_id" '.[$id].repo' "$resolved_file")"
1100
1047
  asset="$(jq -r --arg id "$tool_id" '.[$id].asset' "$resolved_file")"
1101
1048
  checksums_asset="$(jq -r --arg id "$tool_id" '.[$id].checksums_asset // "SHA256SUMS"' "$resolved_file")"
@@ -1106,10 +1053,17 @@ install_one_tool() {
1106
1053
  latest_tag="$(resolve_latest_release_tag "$repo")" || { rc=1; latest_tag=""; }
1107
1054
  if [[ "$rc" -eq 0 ]]; then
1108
1055
  latest_version="${latest_tag#v}"
1109
- if [[ -x "$executable" ]] && "$executable" --version 2>/dev/null | head -n 1 | grep -Fq " $latest_version "; then
1056
+ # A release whose asset filename bakes in the version (agent-vault:
1057
+ # agent-vault_<ver>_darwin_arm64.tar.gz) declares "{version}" in
1058
+ # .asset/.checksums_asset; lean-ctx's own asset names carry no
1059
+ # version at all, so this is a no-op substring replace for it.
1060
+ resolved_asset="${asset//\{version\}/$latest_version}"
1061
+ resolved_checksums_asset="${checksums_asset//\{version\}/$latest_version}"
1062
+ if [[ -x "$executable" ]] &&
1063
+ release_archive_version_matches "$("$executable" --version 2>/dev/null | head -n 1)" "$latest_version"; then
1110
1064
  echo "latest release already installed: $package $latest_version"
1111
1065
  else
1112
- run_release_archive_latest "$package" "$repo" "$asset" "$checksums_asset" "$archive_executable" "$executable" || rc=1
1066
+ run_release_archive_latest "$package" "$repo" "$latest_tag" "$resolved_asset" "$resolved_checksums_asset" "$archive_executable" "$executable" || rc=1
1113
1067
  fi
1114
1068
  fi
1115
1069
  fi
@@ -1654,6 +1608,212 @@ remove_one_repo_dir() {
1654
1608
  rm -rf -- "$target"
1655
1609
  }
1656
1610
 
1611
+ # Lexical-only path normalization: collapses repeated slashes ("//" ->
1612
+ # "/") and current-dir segments ("/./" -> "/"), and drops one trailing
1613
+ # slash. Never resolves ".." or symlinks, and never touches the filesystem
1614
+ # -- the target frequently doesn't exist yet when this runs. Second review
1615
+ # pass, 2026-09-29: without this, "/usr/local/bin//agent-vault" and
1616
+ # "/usr/local/bin/agent-vault" compare unequal even though they name the
1617
+ # same real file, silently defeating executable_claimed_by_current_manifest
1618
+ # below (the guard would say "not claimed" and let the old manager's `rm -f`
1619
+ # through) -- previous_resolved_file is never validated, so a malformed
1620
+ # path there is exactly the case this guard exists to protect against.
1621
+ normalize_manifest_path() {
1622
+ local path="$1"
1623
+ while [[ "$path" == *//* ]]; do path="${path//\/\//\/}"; done
1624
+ while [[ "$path" == */./* ]]; do path="${path//\/.\//\/}"; done
1625
+ path="${path%/}"
1626
+ printf '%s\n' "$path"
1627
+ }
1628
+
1629
+ # A previous entry's executable path can be EXACTLY the path a CURRENT
1630
+ # entry now owns -- e.g. agent-vault-cli migrating release-archive ->
1631
+ # release-archive-latest, both /usr/local/bin/agent-vault. Bug found in
1632
+ # review, 2026-09-29: the install loop runs BEFORE the removal loop below
1633
+ # and already wrote/verified that binary; "retain shared package after
1634
+ # entry deletion" only skips removal when manager+package are BOTH
1635
+ # unchanged, so a manager-only change (exactly this migration) still
1636
+ # selects the id for removal, and the OLD manager's case arm then does a
1637
+ # plain `rm -f` on the path the install pass upstream just installed.
1638
+ # Checked by executable path, not by tool_id/manager/package, because that
1639
+ # is the one thing the filesystem actually cares about -- both sides
1640
+ # normalized first (see normalize_manifest_path) so a malformed legacy path
1641
+ # in previous_resolved_file still compares equal to a clean one.
1642
+ #
1643
+ # Second review pass, 2026-09-29 (bare-name executables): most managers
1644
+ # store an ABSOLUTE path in .executable (release-archive-latest, shim,
1645
+ # bun-compile, repo-script all require the /usr/local/bin/ prefix), but pip,
1646
+ # npm, and brew store a bare command name instead -- pip's own
1647
+ # ensure_global_pip_executable/remove_global_pip_executable ALWAYS place (or
1648
+ # remove) that name as a fixed /usr/local/bin/<name> symlink regardless of
1649
+ # the pip package name, so a bare pip .executable maps there and IS
1650
+ # compared. npm and brew do not get the same treatment: their real install
1651
+ # location depends on the device's own npm global prefix / Homebrew prefix,
1652
+ # resolved only through a live `command -v` PATH search
1653
+ # (npm_global_executable_path) that varies by platform -- never a single
1654
+ # fixed path this function can compute without shelling out. A migration
1655
+ # INTO an npm or brew entry is not detected here and the old manager's real
1656
+ # removal proceeds exactly as before this fix. That gap is narrower than it
1657
+ # looks: npm and brew each remove through their OWN package manager (`npm
1658
+ # uninstall -g`, `brew uninstall`), never the raw `rm -f` this guard
1659
+ # protects, so nothing here relies on rm to leave an npm/brew-owned file
1660
+ # alone in the first place.
1661
+ executable_claimed_by_current_manifest() {
1662
+ local target="$1" normalized_target normalized_current current_manager current_executable
1663
+ normalized_target="$(normalize_manifest_path "$target")"
1664
+ while IFS=$'\t' read -r current_manager current_executable; do
1665
+ [[ -n "$current_executable" ]] || continue
1666
+ if [[ "$current_executable" == /* ]]; then
1667
+ normalized_current="$(normalize_manifest_path "$current_executable")"
1668
+ elif [[ "$current_manager" == pip ]]; then
1669
+ normalized_current="$(normalize_manifest_path "/usr/local/bin/$current_executable")"
1670
+ else
1671
+ continue
1672
+ fi
1673
+ [[ "$normalized_current" == "$normalized_target" ]] && return 0
1674
+ done < <(jq -r '.[] | [.manager, .executable] | @tsv' "$resolved_file")
1675
+ return 1
1676
+ }
1677
+
1678
+ # Uninstalls a single previously-applied tool the CURRENT manifest no longer
1679
+ # declares (or has migrated to a different manager/package) -- extracted
1680
+ # into its own function, like install_one_tool, so
1681
+ # packages/fleet/tests/test-manifest.sh can exercise it directly through the
1682
+ # RECONCILE_SOURCE_ONLY seam instead of only through a real apply run.
1683
+ remove_one_manifest_entry() {
1684
+ local tool_id="$1" manager package executable removed_repo_dir_target
1685
+ if jq -e --arg id "$tool_id" '.config.protected_tools | index($id)' "$manifest" >/dev/null; then
1686
+ echo "refusing to remove protected bootstrap tool: $tool_id" >&2
1687
+ exit 1
1688
+ fi
1689
+ manager="$(jq -r --arg id "$tool_id" '.[$id].manager' "$previous_resolved_file")"
1690
+ package="$(jq -r --arg id "$tool_id" '.[$id].package' "$previous_resolved_file")"
1691
+ executable="$(jq -r --arg id "$tool_id" '.[$id].executable' "$previous_resolved_file")"
1692
+ if is_in_place_bun_to_npm_migration "$tool_id"; then
1693
+ if [[ "$mode" == apply ]]; then
1694
+ echo "retired Bun compilation already removed for npm migration: $tool_id"
1695
+ fi
1696
+ return 0
1697
+ fi
1698
+ if jq -e --arg manager "$manager" --arg package "$package" 'to_entries | any(.value.manager == $manager and .value.package == $package)' "$resolved_file" >/dev/null; then
1699
+ echo "retain shared package after entry deletion: $manager:$package"
1700
+ return 0
1701
+ fi
1702
+ # Truthfulness fix (second review pass, 2026-09-29): for the managers that
1703
+ # remove via a raw filesystem `rm -f` on a fixed path (never their own
1704
+ # package manager's uninstall command), check executable_claimed_by_
1705
+ # current_manifest BEFORE claiming an "uninstall" happened at all. The
1706
+ # log line printed below this case is the only outcome a human or another
1707
+ # tool sees; printing "explicit manifest deletion: uninstall ..." and only
1708
+ # THEN discovering the file is retained (as this used to) makes the log
1709
+ # lie about what just happened, exactly backwards from "log the decision
1710
+ # as the outcome."
1711
+ case "$manager" in
1712
+ release-archive | release-archive-latest | shim | bun-compile)
1713
+ if executable_claimed_by_current_manifest "$executable"; then
1714
+ echo "retain $tool_id ($manager:$package): $executable is still declared by the current manifest"
1715
+ return 0
1716
+ fi
1717
+ ;;
1718
+ esac
1719
+ echo "explicit manifest deletion: uninstall $tool_id ($manager:$package)"
1720
+ case "$manager" in
1721
+ brew) run_brew uninstall "$package" ;;
1722
+ apt) run_apt remove -y "$package" ;;
1723
+ npm) run_npm uninstall -g "$package" ;;
1724
+ pip)
1725
+ run_pip uninstall --break-system-packages -y "$package"
1726
+ remove_global_pip_executable "$executable"
1727
+ ;;
1728
+ # "release-archive" (pinned version+url+sha256) is retired from
1729
+ # validate-manifest's accepted managers (Tarun, 2026-09-29: versions
1730
+ # are never pinned) -- no CURRENT manifest.json can declare one, so
1731
+ # install_one_tool no longer has a case for it. This removal arm stays
1732
+ # so a device whose applied-manifest.json still remembers an old
1733
+ # release-archive entry (agent-vault-cli, before this change) can
1734
+ # still cleanly uninstall it the next time it reconciles; previous
1735
+ # state is read straight from disk, never re-validated.
1736
+ release-archive)
1737
+ [[ "$executable" == /usr/local/bin/* ]] || {
1738
+ echo "refusing unsafe release archive removal target: $executable" >&2
1739
+ exit 1
1740
+ }
1741
+ if [[ "$mode" == apply ]]; then
1742
+ rm -f -- "$executable"
1743
+ else
1744
+ echo "would remove pinned release executable: $executable"
1745
+ fi
1746
+ ;;
1747
+ release-archive-latest)
1748
+ [[ "$executable" == /usr/local/bin/* ]] || {
1749
+ echo "refusing unsafe release archive removal target: $executable" >&2
1750
+ exit 1
1751
+ }
1752
+ if [[ "$mode" == apply ]]; then
1753
+ rm -f -- "$executable"
1754
+ else
1755
+ echo "would remove latest release executable: $executable"
1756
+ fi
1757
+ ;;
1758
+ shim)
1759
+ [[ "$executable" == /usr/local/bin/* ]] || {
1760
+ echo "refusing unsafe shim removal target: $executable" >&2
1761
+ exit 1
1762
+ }
1763
+ if [[ "$mode" == apply ]]; then
1764
+ rm -f -- "$executable"
1765
+ else
1766
+ echo "would remove shim: $executable"
1767
+ fi
1768
+ ;;
1769
+ repo-script)
1770
+ if [[ "$mode" == apply ]]; then
1771
+ remove_one_repo_script "$executable"
1772
+ else
1773
+ [[ "$executable" =~ ^/usr/local/bin/[A-Za-z0-9][A-Za-z0-9._-]*$ ]] || {
1774
+ echo "refusing unsafe managed script removal target: $executable" >&2
1775
+ exit 1
1776
+ }
1777
+ echo "would remove managed repo script: $executable"
1778
+ fi
1779
+ ;;
1780
+ repo-dir)
1781
+ removed_repo_dir_target="$(jq -r --arg id "$tool_id" '.[$id].target' "$previous_resolved_file")"
1782
+ if [[ "$mode" == apply ]]; then
1783
+ remove_one_repo_dir "$removed_repo_dir_target"
1784
+ else
1785
+ [[ "$removed_repo_dir_target" == "$repo_dir_libexec_root"/* ]] || {
1786
+ echo "refusing unsafe repo-dir removal target: $removed_repo_dir_target" >&2
1787
+ exit 1
1788
+ }
1789
+ echo "would remove repo dir: $removed_repo_dir_target"
1790
+ fi
1791
+ ;;
1792
+ bun-compile)
1793
+ [[ "$executable" == /usr/local/bin/* ]] || {
1794
+ echo "refusing unsafe bun binary removal target: $executable" >&2
1795
+ exit 1
1796
+ }
1797
+ if [[ "$mode" == apply ]]; then
1798
+ rm -f -- "$executable"
1799
+ else
1800
+ echo "would remove bun binary: $executable"
1801
+ fi
1802
+ ;;
1803
+ embedding-model)
1804
+ if [[ "$mode" == apply ]]; then
1805
+ [[ "$(id -u)" -eq 0 ]] || { echo "apply mode must run as root" >&2; exit 1; }
1806
+ command -v bun >/dev/null 2>&1 || { echo "embedding-model requires bun; ensure bun installs before this tool" >&2; exit 1; }
1807
+ (cd /tmp && sudo -u "$administrator" -H env PATH="${brew_bin%/*}:/usr/bin:/bin" \
1808
+ bun "$repo_root/packages/fleet/src/install-embedding-model.ts" uninstall </dev/null)
1809
+ else
1810
+ echo "would remove embedding model cache: ~/.cache/adl-embedding/model (as $administrator)"
1811
+ fi
1812
+ ;;
1813
+ *) echo "unsupported manager in previous manifest: $manager" >&2; exit 1 ;;
1814
+ esac
1815
+ }
1816
+
1657
1817
  # config.git_global_config: global git config for the administrator (the
1658
1818
  # account whose HOME the per-user deployers below also write to). Apply runs git as
1659
1819
  # that user (sudo -u -H) so ~/.gitconfig stays user-owned; plan/audit only
@@ -1825,6 +1985,37 @@ run_surface_update() {
1825
1985
  sudo -u "$administrator" -H env PATH="$surface_cli_search_path" "$resolved" "$@" </dev/null
1826
1986
  }
1827
1987
 
1988
+ # Extracts an update command's own reported failure reason from its
1989
+ # captured output, or prints nothing (empty) if none is found. Confirmed
1990
+ # live for kimi on strato and hostinger-vps-1 (2026-09-30): `kimi upgrade`
1991
+ # printed "error: failed to check for updates: CDN /latest returned HTTP
1992
+ # 521" and still exited 0. Broadened by independent review the same day:
1993
+ # optional leading whitespace before the marker, ANSI color codes stripped
1994
+ # first (real CLIs colorize their own output), and any of
1995
+ # "error:"/"Error:"/"ERROR:"/"✖ error:" -- case-insensitive by
1996
+ # construction (POSIX bracket character classes, not a sed `-I`/`I` flag,
1997
+ # which BSD sed does not support). Applied identically to every surface's
1998
+ # update path, native/npm and Homebrew-cask alike, rather than one
1999
+ # per-surface parser.
2000
+ #
2001
+ # Never `cmd1 | cmd2` piped straight into the caller's assignment: see the
2002
+ # inner `if var=$(grep ...); then` below (not `grep | sed`) for why -- under
2003
+ # this file's `set -euo pipefail`, grep's ordinary "no match" exit would
2004
+ # otherwise abort the run on every surface that DIDN'T report an error. An
2005
+ # `if`-tested command is exempt from that abort by design; the function's
2006
+ # OWN exit status is therefore never meaningful (an `if` with no `else`
2007
+ # always returns 0), so callers check the printed string's emptiness, never
2008
+ # this function's exit status.
2009
+ surface_update_error_reason() {
2010
+ local output="$1" plain line
2011
+ # LC_ALL=C: the strip is bytewise. Under en_US.UTF-8 glibc resolves [@-~]
2012
+ # by collation, so the escape's final letter would never match.
2013
+ plain="$(LC_ALL=C sed -E $'s/\x1b\\[[0-9;]*[@-~]//g' <<<"$output")"
2014
+ if line="$(grep -m1 -iE '^[[:space:]]*(✖[[:space:]]*)?error:' <<<"$plain")"; then
2015
+ sed -E 's/^[[:space:]]*(✖[[:space:]]*)?[eE][rR][rR][oO][rR]:[[:space:]]*//' <<<"$line"
2016
+ fi
2017
+ }
2018
+
1828
2019
  # Reconciles one declared surface_clis entry. report_only (hostinger's loose
1829
2020
  # /root/codex, unknown install method) is version-reported only -- run_surface_update
1830
2021
  # is never called for it, by construction (this branch returns before
@@ -1863,6 +2054,26 @@ reconcile_one_surface_cli() {
1863
2054
  echo "surface update failed: $name (exit $update_status)" >&2
1864
2055
  return 1
1865
2056
  fi
2057
+ # Same broadened check as the native/npm path below (independent
2058
+ # review, 2026-09-30): brew itself can exit 0 while its own output
2059
+ # reports a real problem. No install/upgrade claim without checking
2060
+ # for one first -- Homebrew has no version comparison in this branch
2061
+ # (it acts on cask PRESENCE, not a read version), so there is no
2062
+ # "version changed" success case to weigh it against here; any error
2063
+ # line found is a hard failure.
2064
+ local update_error_line
2065
+ update_error_line="$(surface_update_error_reason "$update_output")"
2066
+ if [[ -n "$update_error_line" ]]; then
2067
+ # Install vs upgrade distinguished in the failure wording too (second
2068
+ # coordinator pass, 2026-09-30): the same `cask_action[0]` that already
2069
+ # picks the real brew argv above also names which one failed.
2070
+ if [[ "${cask_action[0]}" == install ]]; then
2071
+ echo "surface $name: cask install failed ($update_error_line)" >&2
2072
+ else
2073
+ echo "surface $name: cask upgrade failed ($update_error_line)" >&2
2074
+ fi
2075
+ return 1
2076
+ fi
1866
2077
  if [[ "${cask_action[0]}" == install ]]; then
1867
2078
  echo "surface $name: Homebrew cask installed"
1868
2079
  else
@@ -1891,7 +2102,7 @@ reconcile_one_surface_cli() {
1891
2102
  echo "surface $name: report-only, currently ${ver:-unknown}"
1892
2103
  return 0
1893
2104
  fi
1894
- local old_version new_version update_output update_status=0
2105
+ local old_version new_version update_output update_status=0 update_error_line
1895
2106
  old_version="$(surface_cli_version "$resolved")"
1896
2107
  update_output="$(run_with_timeout "$SURFACE_UPDATE_TIMEOUT_SECONDS" run_surface_update "$resolved" ${update_args[@]+"${update_args[@]}"} 2>&1)" || update_status=$?
1897
2108
  printf '%s\n' "$update_output"
@@ -1902,12 +2113,48 @@ reconcile_one_surface_cli() {
1902
2113
  echo "surface update failed: $name (exit $update_status)" >&2
1903
2114
  return 1
1904
2115
  fi
2116
+ # A surface's update command can exit 0 while its own output says the
2117
+ # check itself failed -- confirmed live for kimi (strato + hostinger,
2118
+ # 2026-09-30): `kimi upgrade` printed
2119
+ # "error: failed to check for updates: CDN /latest returned HTTP 521" and
2120
+ # still exited 0 (AGENTS.md: no silent fallback). Read new_version BEFORE
2121
+ # judging anything (independent review, 2026-09-30): a version CHANGE is
2122
+ # proof positive the update happened, which outranks an error line the
2123
+ # command also printed (e.g. a flaky post-install self-check after a
2124
+ # real, successful install) -- reported as a note on the success line,
2125
+ # never a failure. Only an UNCHANGED version with an error line is the
2126
+ # case this whole check exists for: the command claimed nothing changed,
2127
+ # and said why, and that is reported as a failure -- wording "update
2128
+ # check failed", not "could not check for updates", since the reported
2129
+ # reason may not even be about the check itself (kept generic rather than
2130
+ # asserting which).
2131
+ #
2132
+ # An EMPTY new_version (second coordinator pass, 2026-09-30: the binary
2133
+ # broke, or --version now produces no output) must never count as
2134
+ # "updated" just because empty differs from a non-empty old_version --
2135
+ # that used to read as "surface X: 1.2.3 -> unknown", an arrow-style
2136
+ # SUCCESS line for a version we cannot actually confirm. Checked before
2137
+ # the "changed" branch below, so only a REAL, non-empty, different
2138
+ # version ever reaches it.
1905
2139
  new_version="$(surface_cli_version "$resolved")"
1906
- if [[ "$old_version" == "$new_version" ]]; then
1907
- echo "surface $name: already latest (${new_version:-unknown})"
1908
- else
1909
- echo "surface $name: ${old_version:-unknown} -> ${new_version:-unknown}"
2140
+ update_error_line="$(surface_update_error_reason "$update_output")"
2141
+ if [[ -z "$new_version" ]]; then
2142
+ echo "surface $name: update ran but version unreadable (${update_error_line:-"--version produced no output"}); was ${old_version:-unknown}" >&2
2143
+ return 1
1910
2144
  fi
2145
+ if [[ "$old_version" != "$new_version" ]]; then
2146
+ if [[ -n "$update_error_line" ]]; then
2147
+ echo "surface $name: updated ${old_version:-unknown} -> $new_version (reported: $update_error_line)"
2148
+ else
2149
+ echo "surface $name: ${old_version:-unknown} -> $new_version"
2150
+ fi
2151
+ return 0
2152
+ fi
2153
+ if [[ -n "$update_error_line" ]]; then
2154
+ echo "surface $name: update check failed ($update_error_line); kept ${old_version:-unknown}" >&2
2155
+ return 1
2156
+ fi
2157
+ echo "surface $name: already latest ($new_version)"
1911
2158
  return 0
1912
2159
  }
1913
2160
 
@@ -2196,111 +2443,7 @@ reconcile_surface_clis
2196
2443
  if [[ "$have_previous" -eq 1 ]]; then
2197
2444
  while IFS= read -r tool_id; do
2198
2445
  [[ -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
2446
+ remove_one_manifest_entry "$tool_id"
2304
2447
  # Selects an id when it was deleted outright (absent from the current
2305
2448
  # manifest) OR retained but migrated to a different manager/package (e.g.
2306
2449
  # lean-ctx moving from npm:lean-ctx-bin to release-archive-latest:lean-ctx)
@@ -2642,6 +2785,83 @@ else
2642
2785
  echo "global-tools: $mode complete; no state changed"
2643
2786
  fi
2644
2787
 
2788
+ # git_maintain_repos (2026-09-30, arm-only decision the same day): an
2789
+ # explicit, closed list of repositories this device's own checkout lives
2790
+ # at OUTSIDE any recursively-scanned home directory -- the Linux VPSes'
2791
+ # root-owned /opt/adl-azdo is the confirmed case for both strato-vps-1 and
2792
+ # hostinger-vps-1 (STATUS.md's 2026-09-29 first-deployment record;
2793
+ # hostinger's own fleet-cutover --apply landed 2026-09-29T23:43Z, confirmed
2794
+ # the same day). git-maintain's own scheduling (packages/fleet/src/
2795
+ # launch-agents.ts: a dedicated compiled binary, ad-hoc signed, run by a
2796
+ # launchd LaunchAgent under TCC) is macOS-only by construction, so it never
2797
+ # reaches these checkouts on its own. This runs the job directly, on
2798
+ # reconcile's own schedule (every platform this script runs on already has
2799
+ # one), passing each declared path as `--repo` argv -- never a recursive
2800
+ # scan of a shared parent like `/opt` (which also holds unrelated,
2801
+ # explicitly off-limits directories), and never a new environment
2802
+ # variable: the list itself comes only from the manifest, and
2803
+ # GIT_MAINTAIN_DRY below is this job's own pre-existing dry/real contract,
2804
+ # driven by reconcile's own --mode exactly like every other step in this
2805
+ # file, not a knob an operator sets by hand. Absent or empty (every mac
2806
+ # device today) is a silent no-op, the same convention
2807
+ # launch_agents/scratch_repos/surface_clis already use.
2808
+ #
2809
+ # ARM-ONLY, always, on this path (independent review, 2026-09-30): the full
2810
+ # job on this SAME checkout `bin/reconcile` runs from turned out to be new,
2811
+ # unapproved, root-run behaviour once actually reasoned through, not merely
2812
+ # a self-checkout read/write ordering hazard -- see the `--arm-only`
2813
+ # decision recorded in jobs/git-maintain/src/main.ts's own header comment
2814
+ # for the full list (a silent fast-forward-driven auto-deploy of
2815
+ # `origin/integration`/main with no approval gate, colliding with
2816
+ # fleet-cutover.sh re-reading fleet-retire-list.json from this same
2817
+ # checkout after reconcile runs, `git branch -D` as root, and a fetch that
2818
+ # still runs even in dry/plan/audit mode and exits 0 on its own failure).
2819
+ # `--arm-only` narrows this call to ONLY `armHooks`: no fetch, no
2820
+ # fast-forward, no branch prune, no bundle-refresh installer, no network,
2821
+ # and no Azure DevOps identity resolution. The only remaining write is
2822
+ # `.git/config`'s `core.hooksPath`, real only in apply mode
2823
+ # (GIT_MAINTAIN_DRY=1 below makes it a `[dry] would arm ...` log line and
2824
+ # nothing else, in both plan AND audit mode). That is never a tracked file
2825
+ # this script (or anything downstream of it) reads, so arm-only has no
2826
+ # self-checkout read/write ordering hazard left to avoid -- this step stays
2827
+ # in this late position anyway, unchanged from the full-job version: it is
2828
+ # still the simplest place for it, and there is no reason to special-case
2829
+ # arm-only's position just because its own hazard is gone.
2830
+ git_maintain_repos=()
2831
+ while IFS= read -r repo; do
2832
+ [[ -n "$repo" ]] && git_maintain_repos+=("$repo")
2833
+ done < <(jq -r --arg platform "$platform" --arg device "$device" \
2834
+ '.[$platform].devices[$device].git_maintain_repos // [] | .[]' "$manifest")
2835
+ if [[ "${#git_maintain_repos[@]}" -gt 0 ]]; then
2836
+ git_maintain_repo_args=(--arm-only)
2837
+ for repo in "${git_maintain_repos[@]}"; do
2838
+ git_maintain_repo_args+=(--repo "$repo")
2839
+ done
2840
+ # Unlike default-agent-file.ts/managed-settings.ts's plan branches earlier
2841
+ # in this file (which never fail, by construction, so they run
2842
+ # unguarded), this job's dry pass still validates each declared path is a
2843
+ # real repo -- a genuine failure mode even in a non-apply mode, e.g. a
2844
+ # declared checkout that has not been cloned yet. So every non-apply mode
2845
+ # is guarded exactly like apply mode here, deliberately deviating from
2846
+ # those sibling steps: report it, collect it, never let it abort the rest
2847
+ # of this script under this file's own `set -e`. `$mode` (plan OR audit)
2848
+ # is reported verbatim -- audit is a real, distinct reconcile mode, never
2849
+ # relabeled "plan".
2850
+ if [[ "$mode" != apply ]]; then
2851
+ echo "global-tools: git-maintain (declared repos, arm-only) mode=$mode device=$device -> ${git_maintain_repos[*]}"
2852
+ if ! GIT_MAINTAIN_DRY=1 bun "$repo_root/packages/fleet/jobs/git-maintain/src/main.ts" "${git_maintain_repo_args[@]}"; then
2853
+ echo "global-tools: TOOL INSTALL FAILED: git-maintain-repos" >&2
2854
+ failed_tools+=("git-maintain-repos")
2855
+ fi
2856
+ else
2857
+ echo "global-tools: git-maintain (declared repos, arm-only) mode=apply device=$device -> ${git_maintain_repos[*]}"
2858
+ if ! bun "$repo_root/packages/fleet/jobs/git-maintain/src/main.ts" "${git_maintain_repo_args[@]}"; then
2859
+ echo "global-tools: TOOL INSTALL FAILED: git-maintain-repos" >&2
2860
+ failed_tools+=("git-maintain-repos")
2861
+ fi
2862
+ fi
2863
+ fi
2864
+
2645
2865
  # A recorded, continued-past tool failure (install_one_tool /
2646
2866
  # build_one_bun_tool / install_one_embedding_model_tool / write_one_shim)
2647
2867
  # still lets every post-pass above run -- sweep, kimi-hooks, global-agents,