Preserve retired agentic-os pin convergence implementation #666

Closed
opened 2026-07-28 18:12:52 +00:00 by coilyco-ops · 2 comments
Member

Why this issue exists

The infrastructure repository is retiring the host-side agentic-os pin convergence timer as part of moving kai-server and ser8 to warded-only agent execution. The timer mutates persistent fleet checkouts, commits, and pushes from the ops host, which no longer matches the execution boundary.

This issue preserves the complete last tracked implementation before the repository removes it. Preserved source SHA-256: 4d1ede4f04b13031227306a6f26fb37aa3b8e1cbc1ea98561ec257c08a87b868.

Preserved source

#!/usr/bin/env bash
# agentic-os-pin-converge.sh - converge every fleet repo's managed agentic-os
# pre-commit block to the latest release tag, so the pin can't silently drift.
#
# apply-agentic-os-hooks.py (in agentic-os) regenerates the managed block per
# repo, but nothing fanned it out - a 2026-06-17 sweep found 12 of 13 repos
# behind, some by 49 minor versions, five still shipping retired commit-msg
# hooks that block every commit. This is the fan-out: the deterministic floor
# (regen + verify + commit + push) runs on a timer, and the non-deterministic
# edges (a hand-edited block that no longer round-trips, a regen that breaks a
# repo's own config, a diff too large to auto-push) escalate to Goose or park.
#
# The wall from doctrine holds: Goose full-auto with --dangerously-skip-permissions
# runs ONLY inside a container. On the bare ops plane the escalation parks the
# repo and alerts, it never runs a skip-permissions agent on the host.
#
# Auto-converge that fails silently is the same disease it cures, so anything
# failed or parked pushes a high-priority Telegram alert; a run that landed new
# pins pushes a quiet line; a no-change run is silent.
#
# infrastructure#372.

set -uo pipefail

PROJECTS_ROOT="${AOS_PIN_PROJECTS_ROOT:-$HOME/projects}"
AGENTIC_OS_REPO="${AOS_PIN_AGENTIC_OS_REPO:-$PROJECTS_ROOT/coilyco-flight-deck/agentic-os}"
STATE_DIR="${AOS_PIN_STATE_DIR:-$HOME/.local/state/agentic-os-pin-converge}"
# Escalation mode: "park" (host-safe: alert + clean tree) or "goose" (full-auto,
# only when AOS_PIN_IN_CONTAINER=1 - skip-permissions is container-only).
ESCALATE="${AOS_PIN_ESCALATE:-park}"
IN_CONTAINER="${AOS_PIN_IN_CONTAINER:-0}"
# A config diff larger than this many lines is "too large to auto-push" (a
# block that didn't round-trip) and escalates instead of pushing blindly.
MAX_DIFF_LINES="${AOS_PIN_MAX_DIFF_LINES:-120}"
PUSH="${AOS_PIN_PUSH:-1}"
DRY_RUN="${AOS_PIN_DRY_RUN:-0}"

# Telegram notify: a high-priority alert -> red channel, anything else -> green.
# Token + chat ids cached host-local by the o11y-telegram role.
TELEGRAM_BOT_TOKEN_FILE="${TELEGRAM_BOT_TOKEN_FILE:-}"
TELEGRAM_GREEN_CHAT_ID_FILE="${TELEGRAM_GREEN_CHAT_ID_FILE:-}"
TELEGRAM_RED_CHAT_ID_FILE="${TELEGRAM_RED_CHAT_ID_FILE:-}"

BEGIN_MARKER="# BEGIN managed by agentic-os/scripts/apply-agentic-os-hooks.py"
# Ward-managed lockdown files: never staged, left dirty on purpose (doctrine).
LOCKDOWN_PATHS=(".claude/lockdown-deny.sh" ".claude/settings.json")
# Legacy stamped validators the regen deletes; staged as deletions, and
# restored on a park so the working tree is left clean.
LEGACY_STAMPED_SCRIPTS=(
  "scripts/check-catalog-block.py"
  "scripts/check-catalog-doc-size.py"
  "scripts/check-catalog-trifecta.py"
  "scripts/check-dead-links.py"
  "scripts/check-skills.py"
)

# Headless user services get a minimal PATH; pull in brew (ward/uv/git/goose).
export PATH="/home/linuxbrew/.linuxbrew/bin:$HOME/.linuxbrew/bin:$HOME/.local/bin:/usr/local/bin:/usr/bin:/bin:${PATH:-}"

mkdir -p "$STATE_DIR"
JOURNAL="$STATE_DIR/journal.log"

journal() { printf '%s %s\n' "$(date -Is)" "$*" >> "$JOURNAL"; }

tg_notify() { # priority title body  ->  red when high, else green
  [ -r "$TELEGRAM_BOT_TOKEN_FILE" ] || return 0
  local tok chatfile chat
  tok="$(tr -d '[:space:]' < "$TELEGRAM_BOT_TOKEN_FILE" 2>/dev/null || true)"
  [ -n "$tok" ] || return 0
  if [ "$1" = "high" ]; then chatfile="$TELEGRAM_RED_CHAT_ID_FILE"; else chatfile="$TELEGRAM_GREEN_CHAT_ID_FILE"; fi
  [ -r "$chatfile" ] || return 0
  chat="$(tr -d '[:space:]' < "$chatfile" 2>/dev/null || true)"
  [ -n "$chat" ] || return 0
  curl -fsS --max-time 20 \
    --data-urlencode "chat_id=${chat}" \
    --data-urlencode "text=$2"$'\n'"$3" \
    "https://api.telegram.org/bot${tok}/sendMessage" >/dev/null 2>&1 || true
}

notify() { # priority title body
  tg_notify "$1" "$2" "$3"
  echo "[$2] $3"
}

# Latest agentic-os release tag from its checkout (v<MAJOR>.<MINOR>.<PATCH>).
latest_tag() {
  git -C "$AGENTIC_OS_REPO" fetch --tags --quiet origin 2>/dev/null || true
  git -C "$AGENTIC_OS_REPO" tag --list 'v[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname \
    | head -n 1
}

# Pinned rev on the agentic-os block in a consumer's config, or empty.
pinned_rev() { # config-path
  awk '
    /- *repo: .*coilyco-flight-deck\/agentic-os/ { want = 1; next }
    want && $1 == "rev:" { print $2; exit }
  ' "$1"
}

# Restore only the paths the regen touches, so a park leaves a clean tree
# without disturbing the lockdown files or anything else.
revert_repo() { # repo-dir
  local d="$1" p
  git -C "$d" checkout -- .pre-commit-config.yaml 2>/dev/null || true
  for p in "${LEGACY_STAMPED_SCRIPTS[@]}"; do
    git -C "$d" checkout -- "$p" 2>/dev/null || true
  done
}

# Paths changed in the working tree, minus the lockdown files.
dirty_paths() { # repo-dir
  local d="$1" line path
  git -C "$d" status --porcelain 2>/dev/null | while IFS= read -r line; do
    path="${line:3}"
    case " ${LOCKDOWN_PATHS[*]} " in
      *" $path "*) continue ;;
    esac
    printf '%s\n' "$path"
  done
}

run_goose() { # repo-dir repo cur latest reason
  local d="$1" repo="$2" cur="$3" latest="$4" reason="$5"
  if ! command -v goose >/dev/null 2>&1; then
    journal "  goose not on PATH; parking $repo"
    return 1
  fi
  local contract
  contract="$(cat <<EOF
Converge this repo's managed agentic-os pre-commit block, full-auto.

GOAL: pin the agentic-os block in .pre-commit-config.yaml to $latest (from $cur)
so pre-commit run --all-files passes, then commit and push to the origin default
branch.
DONE-CONDITION: .pre-commit-config.yaml pins rev: $latest, pre-commit run
--all-files is green, the change is committed and pushed. Nothing else dirty.
NON-GOALS: never stage or edit .claude/lockdown-deny.sh or .claude/settings.json
(leave them dirty); touch only this repo; no unrelated refactors.
CONTEXT: the deterministic regen (apply-agentic-os-hooks.py) could not converge
this repo cleanly - $reason. Regenerate or repair the block so it round-trips,
verify, and land it. Journal each step.
EOF
)"
  journal "  goose full-auto on $repo: $reason"
  ( cd "$d" && goose run --no-session --dangerously-skip-permissions -t "$contract" ) \
    >> "$JOURNAL" 2>&1 || true
  # Trust the tree, not the exit code: converged iff the pin now matches.
  [ "$(pinned_rev "$d/.pre-commit-config.yaml")" = "$latest" ]
}

# Escalate a repo the deterministic path can't converge. Returns 0 if an agent
# converged it, 1 if it was parked (needs a human).
escalate() { # repo-dir repo cur latest reason
  local d="$1" repo="$2" cur="$3" latest="$4" reason="$5"
  journal "ESCALATE $repo ($cur -> $latest): $reason"
  if [ "$ESCALATE" = "goose" ]; then
    if [ "$IN_CONTAINER" = "1" ]; then
      if run_goose "$d" "$repo" "$cur" "$latest" "$reason"; then
        journal "  goose converged $repo to $latest"
        return 0
      fi
      journal "  goose did not converge $repo; parking"
    else
      journal "  goose mode requested but not in a container; parking (skip-permissions is container-only)"
    fi
  fi
  revert_repo "$d"
  return 1
}

# Converge one consumer repo. Echoes a one-word outcome:
# applied | pushed-skip | parked | dry | error.
converge_repo() { # repo-dir repo cur latest
  local d="$1" repo="$2" cur="$3" latest="$4"
  journal "converge $repo: $cur -> $latest"

  if [ "$DRY_RUN" = "1" ]; then
    journal "  (dry-run) would regen + push $repo"
    echo dry; return 0
  fi

  local out rc
  out="$( cd "$AGENTIC_OS_REPO" && PROJECTS_ROOT="$PROJECTS_ROOT" \
    ward exec apply-agentic-os-hooks -- --repo "$repo" --rev "$latest" 2>&1 )"
  rc=$?
  if [ "$rc" -ne 0 ]; then
    if escalate "$d" "$repo" "$cur" "$latest" "regen rc=$rc: $(printf '%s' "$out" | tail -n 3)"; then
      echo applied; else echo parked; fi
    return 0
  fi

  # Verify the regenerated tree. A hook rename that breaks the repo's own
  # config surfaces here.
  if ! ( cd "$d" && pre-commit run --all-files >>"$JOURNAL" 2>&1 ); then
    if escalate "$d" "$repo" "$cur" "$latest" "pre-commit run --all-files failed after regen"; then
      echo applied; else echo parked; fi
    return 0
  fi

  # Round-trip guard: the only changes may be the config and the legacy
  # scripts. Anything else means the block didn't round-trip.
  local changed unexpected="" p s keep
  changed="$(dirty_paths "$d")"
  while IFS= read -r p; do
    [ -n "$p" ] || continue
    keep=0
    [ "$p" = ".pre-commit-config.yaml" ] && keep=1
    for s in "${LEGACY_STAMPED_SCRIPTS[@]}"; do
      [ "$p" = "$s" ] && keep=1
    done
    [ "$keep" -eq 0 ] && unexpected+="$p "
  done <<< "$changed"
  if [ -n "$unexpected" ]; then
    if escalate "$d" "$repo" "$cur" "$latest" "non-round-trip changes: $unexpected"; then
      echo applied; else echo parked; fi
    return 0
  fi

  # Stage only the expected paths; never the lockdown files.
  git -C "$d" add -- .pre-commit-config.yaml 2>/dev/null || true
  for p in "${LEGACY_STAMPED_SCRIPTS[@]}"; do
    git -C "$d" add -- "$p" 2>/dev/null || true
  done
  git -C "$d" reset -q -- "${LOCKDOWN_PATHS[@]}" 2>/dev/null || true

  if git -C "$d" diff --cached --quiet; then
    journal "  nothing staged for $repo (rev moved but block unchanged); skipping"
    revert_repo "$d"
    echo pushed-skip; return 0
  fi

  # Diff too large to trust to an unattended push -> escalate for review.
  local lines
  lines="$(git -C "$d" diff --cached --numstat | awk '{a+=$1; b+=$2} END {print a+b+0}')"
  if [ "$lines" -gt "$MAX_DIFF_LINES" ]; then
    git -C "$d" reset -q 2>/dev/null || true
    if escalate "$d" "$repo" "$cur" "$latest" "diff too large ($lines lines) to auto-push"; then
      echo applied; else echo parked; fi
    return 0
  fi

  local default cur_branch
  default="$(git -C "$d" symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null | sed 's|^origin/||')"
  default="${default:-main}"
  cur_branch="$(git -C "$d" rev-parse --abbrev-ref HEAD 2>/dev/null || echo HEAD)"
  if [ "$cur_branch" != "$default" ]; then
    git -C "$d" reset -q 2>/dev/null || true
    if escalate "$d" "$repo" "$cur" "$latest" "on branch $cur_branch, not $default"; then
      echo applied; else echo parked; fi
    return 0
  fi

  git -C "$d" commit -q -m "chore(pre-commit): bump agentic-os hooks $cur -> $latest

Fleet pin converge via infrastructure agentic-os-pin-converge.
infrastructure#372." 2>>"$JOURNAL" || { echo error; return 0; }

  if [ "$PUSH" != "1" ]; then
    journal "  committed $repo (push disabled)"
    echo applied; return 0
  fi

  if git -C "$d" push -q origin "HEAD:$default" 2>>"$JOURNAL"; then
    journal "  landed $repo $cur -> $latest"
    echo applied; return 0
  fi
  journal "  push failed for $repo; committed locally"
  echo parked; return 0
}

main() {
  if [ ! -f "$AGENTIC_OS_REPO/scripts/apply-agentic-os-hooks.py" ]; then
    notify high "agentic-os-pin-converge: apply script missing" \
      "no apply-agentic-os-hooks.py under $AGENTIC_OS_REPO"
    exit 1
  fi

  local latest
  latest="$(latest_tag)"
  if [ -z "$latest" ]; then
    notify high "agentic-os-pin-converge: no release tag" \
      "could not resolve latest tag in $AGENTIC_OS_REPO"
    exit 1
  fi
  journal "sweep start; latest=$latest escalate=$ESCALATE dry_run=$DRY_RUN"

  local applied=0 parked=0 skipped=0 total=0
  local parked_repos=""
  shopt -s nullglob
  local config repo_dir repo cur outcome
  for config in "$PROJECTS_ROOT"/*/*/.pre-commit-config.yaml; do
    grep -qF "$BEGIN_MARKER" "$config" || continue
    repo_dir="$(dirname "$config")"
    repo="$(basename "$repo_dir")"
    [ -d "$repo_dir/.git" ] || continue
    total=$((total + 1))

    cur="$(pinned_rev "$config")"
    if [ -z "$cur" ]; then
      journal "WARN $repo: managed block present but no rev parsed; skipping"
      continue
    fi
    if [ "$cur" = "$latest" ]; then
      skipped=$((skipped + 1))
      continue
    fi

    outcome="$(converge_repo "$repo_dir" "$repo" "$cur" "$latest")"
    case "$outcome" in
      applied) applied=$((applied + 1)) ;;
      parked|error) parked=$((parked + 1)); parked_repos+="$repo " ;;
      *) skipped=$((skipped + 1)) ;;
    esac
  done

  journal "sweep done; scanned=$total applied=$applied parked=$parked at=$latest"

  if [ "$parked" -gt 0 ]; then
    notify high "agentic-os-pin-converge: $parked repo(s) need attention" \
      "target=$latest applied=$applied parked: ${parked_repos}"$'\n'"see $JOURNAL"
    exit 1
  fi
  if [ "$applied" -gt 0 ]; then
    notify min "agentic-os-pin-converge: pinned $applied repo(s) to $latest" \
      "scanned=$total"
  else
    echo "no drift; $total repo(s) already at $latest"
  fi
}

main "$@"

Successor constraint

Any future fleet pin rollout should run through isolated warded checkouts and the published reusable CI contract. It should not sweep or mutate long-lived host worktrees.

## Why this issue exists The infrastructure repository is retiring the host-side agentic-os pin convergence timer as part of moving kai-server and ser8 to warded-only agent execution. The timer mutates persistent fleet checkouts, commits, and pushes from the ops host, which no longer matches the execution boundary. This issue preserves the complete last tracked implementation before the repository removes it. Preserved source SHA-256: `4d1ede4f04b13031227306a6f26fb37aa3b8e1cbc1ea98561ec257c08a87b868`. ## Preserved source ```bash #!/usr/bin/env bash # agentic-os-pin-converge.sh - converge every fleet repo's managed agentic-os # pre-commit block to the latest release tag, so the pin can't silently drift. # # apply-agentic-os-hooks.py (in agentic-os) regenerates the managed block per # repo, but nothing fanned it out - a 2026-06-17 sweep found 12 of 13 repos # behind, some by 49 minor versions, five still shipping retired commit-msg # hooks that block every commit. This is the fan-out: the deterministic floor # (regen + verify + commit + push) runs on a timer, and the non-deterministic # edges (a hand-edited block that no longer round-trips, a regen that breaks a # repo's own config, a diff too large to auto-push) escalate to Goose or park. # # The wall from doctrine holds: Goose full-auto with --dangerously-skip-permissions # runs ONLY inside a container. On the bare ops plane the escalation parks the # repo and alerts, it never runs a skip-permissions agent on the host. # # Auto-converge that fails silently is the same disease it cures, so anything # failed or parked pushes a high-priority Telegram alert; a run that landed new # pins pushes a quiet line; a no-change run is silent. # # infrastructure#372. set -uo pipefail PROJECTS_ROOT="${AOS_PIN_PROJECTS_ROOT:-$HOME/projects}" AGENTIC_OS_REPO="${AOS_PIN_AGENTIC_OS_REPO:-$PROJECTS_ROOT/coilyco-flight-deck/agentic-os}" STATE_DIR="${AOS_PIN_STATE_DIR:-$HOME/.local/state/agentic-os-pin-converge}" # Escalation mode: "park" (host-safe: alert + clean tree) or "goose" (full-auto, # only when AOS_PIN_IN_CONTAINER=1 - skip-permissions is container-only). ESCALATE="${AOS_PIN_ESCALATE:-park}" IN_CONTAINER="${AOS_PIN_IN_CONTAINER:-0}" # A config diff larger than this many lines is "too large to auto-push" (a # block that didn't round-trip) and escalates instead of pushing blindly. MAX_DIFF_LINES="${AOS_PIN_MAX_DIFF_LINES:-120}" PUSH="${AOS_PIN_PUSH:-1}" DRY_RUN="${AOS_PIN_DRY_RUN:-0}" # Telegram notify: a high-priority alert -> red channel, anything else -> green. # Token + chat ids cached host-local by the o11y-telegram role. TELEGRAM_BOT_TOKEN_FILE="${TELEGRAM_BOT_TOKEN_FILE:-}" TELEGRAM_GREEN_CHAT_ID_FILE="${TELEGRAM_GREEN_CHAT_ID_FILE:-}" TELEGRAM_RED_CHAT_ID_FILE="${TELEGRAM_RED_CHAT_ID_FILE:-}" BEGIN_MARKER="# BEGIN managed by agentic-os/scripts/apply-agentic-os-hooks.py" # Ward-managed lockdown files: never staged, left dirty on purpose (doctrine). LOCKDOWN_PATHS=(".claude/lockdown-deny.sh" ".claude/settings.json") # Legacy stamped validators the regen deletes; staged as deletions, and # restored on a park so the working tree is left clean. LEGACY_STAMPED_SCRIPTS=( "scripts/check-catalog-block.py" "scripts/check-catalog-doc-size.py" "scripts/check-catalog-trifecta.py" "scripts/check-dead-links.py" "scripts/check-skills.py" ) # Headless user services get a minimal PATH; pull in brew (ward/uv/git/goose). export PATH="/home/linuxbrew/.linuxbrew/bin:$HOME/.linuxbrew/bin:$HOME/.local/bin:/usr/local/bin:/usr/bin:/bin:${PATH:-}" mkdir -p "$STATE_DIR" JOURNAL="$STATE_DIR/journal.log" journal() { printf '%s %s\n' "$(date -Is)" "$*" >> "$JOURNAL"; } tg_notify() { # priority title body -> red when high, else green [ -r "$TELEGRAM_BOT_TOKEN_FILE" ] || return 0 local tok chatfile chat tok="$(tr -d '[:space:]' < "$TELEGRAM_BOT_TOKEN_FILE" 2>/dev/null || true)" [ -n "$tok" ] || return 0 if [ "$1" = "high" ]; then chatfile="$TELEGRAM_RED_CHAT_ID_FILE"; else chatfile="$TELEGRAM_GREEN_CHAT_ID_FILE"; fi [ -r "$chatfile" ] || return 0 chat="$(tr -d '[:space:]' < "$chatfile" 2>/dev/null || true)" [ -n "$chat" ] || return 0 curl -fsS --max-time 20 \ --data-urlencode "chat_id=${chat}" \ --data-urlencode "text=$2"$'\n'"$3" \ "https://api.telegram.org/bot${tok}/sendMessage" >/dev/null 2>&1 || true } notify() { # priority title body tg_notify "$1" "$2" "$3" echo "[$2] $3" } # Latest agentic-os release tag from its checkout (v<MAJOR>.<MINOR>.<PATCH>). latest_tag() { git -C "$AGENTIC_OS_REPO" fetch --tags --quiet origin 2>/dev/null || true git -C "$AGENTIC_OS_REPO" tag --list 'v[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname \ | head -n 1 } # Pinned rev on the agentic-os block in a consumer's config, or empty. pinned_rev() { # config-path awk ' /- *repo: .*coilyco-flight-deck\/agentic-os/ { want = 1; next } want && $1 == "rev:" { print $2; exit } ' "$1" } # Restore only the paths the regen touches, so a park leaves a clean tree # without disturbing the lockdown files or anything else. revert_repo() { # repo-dir local d="$1" p git -C "$d" checkout -- .pre-commit-config.yaml 2>/dev/null || true for p in "${LEGACY_STAMPED_SCRIPTS[@]}"; do git -C "$d" checkout -- "$p" 2>/dev/null || true done } # Paths changed in the working tree, minus the lockdown files. dirty_paths() { # repo-dir local d="$1" line path git -C "$d" status --porcelain 2>/dev/null | while IFS= read -r line; do path="${line:3}" case " ${LOCKDOWN_PATHS[*]} " in *" $path "*) continue ;; esac printf '%s\n' "$path" done } run_goose() { # repo-dir repo cur latest reason local d="$1" repo="$2" cur="$3" latest="$4" reason="$5" if ! command -v goose >/dev/null 2>&1; then journal " goose not on PATH; parking $repo" return 1 fi local contract contract="$(cat <<EOF Converge this repo's managed agentic-os pre-commit block, full-auto. GOAL: pin the agentic-os block in .pre-commit-config.yaml to $latest (from $cur) so pre-commit run --all-files passes, then commit and push to the origin default branch. DONE-CONDITION: .pre-commit-config.yaml pins rev: $latest, pre-commit run --all-files is green, the change is committed and pushed. Nothing else dirty. NON-GOALS: never stage or edit .claude/lockdown-deny.sh or .claude/settings.json (leave them dirty); touch only this repo; no unrelated refactors. CONTEXT: the deterministic regen (apply-agentic-os-hooks.py) could not converge this repo cleanly - $reason. Regenerate or repair the block so it round-trips, verify, and land it. Journal each step. EOF )" journal " goose full-auto on $repo: $reason" ( cd "$d" && goose run --no-session --dangerously-skip-permissions -t "$contract" ) \ >> "$JOURNAL" 2>&1 || true # Trust the tree, not the exit code: converged iff the pin now matches. [ "$(pinned_rev "$d/.pre-commit-config.yaml")" = "$latest" ] } # Escalate a repo the deterministic path can't converge. Returns 0 if an agent # converged it, 1 if it was parked (needs a human). escalate() { # repo-dir repo cur latest reason local d="$1" repo="$2" cur="$3" latest="$4" reason="$5" journal "ESCALATE $repo ($cur -> $latest): $reason" if [ "$ESCALATE" = "goose" ]; then if [ "$IN_CONTAINER" = "1" ]; then if run_goose "$d" "$repo" "$cur" "$latest" "$reason"; then journal " goose converged $repo to $latest" return 0 fi journal " goose did not converge $repo; parking" else journal " goose mode requested but not in a container; parking (skip-permissions is container-only)" fi fi revert_repo "$d" return 1 } # Converge one consumer repo. Echoes a one-word outcome: # applied | pushed-skip | parked | dry | error. converge_repo() { # repo-dir repo cur latest local d="$1" repo="$2" cur="$3" latest="$4" journal "converge $repo: $cur -> $latest" if [ "$DRY_RUN" = "1" ]; then journal " (dry-run) would regen + push $repo" echo dry; return 0 fi local out rc out="$( cd "$AGENTIC_OS_REPO" && PROJECTS_ROOT="$PROJECTS_ROOT" \ ward exec apply-agentic-os-hooks -- --repo "$repo" --rev "$latest" 2>&1 )" rc=$? if [ "$rc" -ne 0 ]; then if escalate "$d" "$repo" "$cur" "$latest" "regen rc=$rc: $(printf '%s' "$out" | tail -n 3)"; then echo applied; else echo parked; fi return 0 fi # Verify the regenerated tree. A hook rename that breaks the repo's own # config surfaces here. if ! ( cd "$d" && pre-commit run --all-files >>"$JOURNAL" 2>&1 ); then if escalate "$d" "$repo" "$cur" "$latest" "pre-commit run --all-files failed after regen"; then echo applied; else echo parked; fi return 0 fi # Round-trip guard: the only changes may be the config and the legacy # scripts. Anything else means the block didn't round-trip. local changed unexpected="" p s keep changed="$(dirty_paths "$d")" while IFS= read -r p; do [ -n "$p" ] || continue keep=0 [ "$p" = ".pre-commit-config.yaml" ] && keep=1 for s in "${LEGACY_STAMPED_SCRIPTS[@]}"; do [ "$p" = "$s" ] && keep=1 done [ "$keep" -eq 0 ] && unexpected+="$p " done <<< "$changed" if [ -n "$unexpected" ]; then if escalate "$d" "$repo" "$cur" "$latest" "non-round-trip changes: $unexpected"; then echo applied; else echo parked; fi return 0 fi # Stage only the expected paths; never the lockdown files. git -C "$d" add -- .pre-commit-config.yaml 2>/dev/null || true for p in "${LEGACY_STAMPED_SCRIPTS[@]}"; do git -C "$d" add -- "$p" 2>/dev/null || true done git -C "$d" reset -q -- "${LOCKDOWN_PATHS[@]}" 2>/dev/null || true if git -C "$d" diff --cached --quiet; then journal " nothing staged for $repo (rev moved but block unchanged); skipping" revert_repo "$d" echo pushed-skip; return 0 fi # Diff too large to trust to an unattended push -> escalate for review. local lines lines="$(git -C "$d" diff --cached --numstat | awk '{a+=$1; b+=$2} END {print a+b+0}')" if [ "$lines" -gt "$MAX_DIFF_LINES" ]; then git -C "$d" reset -q 2>/dev/null || true if escalate "$d" "$repo" "$cur" "$latest" "diff too large ($lines lines) to auto-push"; then echo applied; else echo parked; fi return 0 fi local default cur_branch default="$(git -C "$d" symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null | sed 's|^origin/||')" default="${default:-main}" cur_branch="$(git -C "$d" rev-parse --abbrev-ref HEAD 2>/dev/null || echo HEAD)" if [ "$cur_branch" != "$default" ]; then git -C "$d" reset -q 2>/dev/null || true if escalate "$d" "$repo" "$cur" "$latest" "on branch $cur_branch, not $default"; then echo applied; else echo parked; fi return 0 fi git -C "$d" commit -q -m "chore(pre-commit): bump agentic-os hooks $cur -> $latest Fleet pin converge via infrastructure agentic-os-pin-converge. infrastructure#372." 2>>"$JOURNAL" || { echo error; return 0; } if [ "$PUSH" != "1" ]; then journal " committed $repo (push disabled)" echo applied; return 0 fi if git -C "$d" push -q origin "HEAD:$default" 2>>"$JOURNAL"; then journal " landed $repo $cur -> $latest" echo applied; return 0 fi journal " push failed for $repo; committed locally" echo parked; return 0 } main() { if [ ! -f "$AGENTIC_OS_REPO/scripts/apply-agentic-os-hooks.py" ]; then notify high "agentic-os-pin-converge: apply script missing" \ "no apply-agentic-os-hooks.py under $AGENTIC_OS_REPO" exit 1 fi local latest latest="$(latest_tag)" if [ -z "$latest" ]; then notify high "agentic-os-pin-converge: no release tag" \ "could not resolve latest tag in $AGENTIC_OS_REPO" exit 1 fi journal "sweep start; latest=$latest escalate=$ESCALATE dry_run=$DRY_RUN" local applied=0 parked=0 skipped=0 total=0 local parked_repos="" shopt -s nullglob local config repo_dir repo cur outcome for config in "$PROJECTS_ROOT"/*/*/.pre-commit-config.yaml; do grep -qF "$BEGIN_MARKER" "$config" || continue repo_dir="$(dirname "$config")" repo="$(basename "$repo_dir")" [ -d "$repo_dir/.git" ] || continue total=$((total + 1)) cur="$(pinned_rev "$config")" if [ -z "$cur" ]; then journal "WARN $repo: managed block present but no rev parsed; skipping" continue fi if [ "$cur" = "$latest" ]; then skipped=$((skipped + 1)) continue fi outcome="$(converge_repo "$repo_dir" "$repo" "$cur" "$latest")" case "$outcome" in applied) applied=$((applied + 1)) ;; parked|error) parked=$((parked + 1)); parked_repos+="$repo " ;; *) skipped=$((skipped + 1)) ;; esac done journal "sweep done; scanned=$total applied=$applied parked=$parked at=$latest" if [ "$parked" -gt 0 ]; then notify high "agentic-os-pin-converge: $parked repo(s) need attention" \ "target=$latest applied=$applied parked: ${parked_repos}"$'\n'"see $JOURNAL" exit 1 fi if [ "$applied" -gt 0 ]; then notify min "agentic-os-pin-converge: pinned $applied repo(s) to $latest" \ "scanned=$total" else echo "no drift; $total repo(s) already at $latest" fi } main "$@" ``` ## Successor constraint Any future fleet pin rollout should run through isolated warded checkouts and the published reusable CI contract. It should not sweep or mutate long-lived host worktrees.
Author
Member

The tracked retirement landed on canonical main in c311353, with the complete operator handoff in 10753b4. Live host cleanup is isolated in interactive issue #667. The preserved source above remains the recovery artifact.

The tracked retirement landed on canonical main in `c311353`, with the complete operator handoff in `10753b4`. Live host cleanup is isolated in interactive issue #667. The preserved source above remains the recovery artifact.
Author
Member

The isolated read-only successor is now tracked in #668. It consumes the preserved lessons here without restoring host-side checkout mutation.

The isolated read-only successor is now tracked in #668. It consumes the preserved lessons here without restoring host-side checkout mutation.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-flight-deck/infrastructure#666
No description provided.