#!/usr/bin/env bash
set -Eeuo pipefail
umask 077

STEP="STEP_050M07R13B_HARDEN_MACHINE_GIT_COUNT_CONTRACT"
PASS_RESULT="PASS_${STEP}"
STOP_RESULT="STOP_${STEP}"
RESULT="$STOP_RESULT"
PHASE="bootstrap"
FAILURE_REASON=""

TOKEN="e94a0859747d7b96f29c7fdafc2d0351ba603bb0a7e9e5a4"
TUNNEL_BASE="https://receiver-rejected-average-combat.trycloudflare.com"
PUBLIC_ROOT="/opt/router-ops/public/r/$TOKEN"
ROUTER_OPS_ROOT="/opt/router-ops"
TS="$(date -u +%Y%m%d-%H%M%S)"
SLUG="step050m07r13b_harden_machine_git_count_contract"
WORK_DIR="$ROUTER_OPS_ROOT/work/${TS}_${SLUG}"
REPORT_DIR="$PUBLIC_ROOT/${TS}_${SLUG}"
REPORT_URL="$TUNNEL_BASE/r/$TOKEN/${TS}_${SLUG}/"

LOCAL_PLAN_DIR="$PUBLIC_ROOT/${TS}_local_architecture_plan_vm101_autonomous_hmn_recovery"
LOCAL_PLAN_URL="$TUNNEL_BASE/r/$TOKEN/${TS}_local_architecture_plan_vm101_autonomous_hmn_recovery/"
METHODS_DIR="$PUBLIC_ROOT/${TS}_vm101_methods_machine_git_count_contract_r13b"
METHODS_URL="$TUNNEL_BASE/r/$TOKEN/${TS}_vm101_methods_machine_git_count_contract_r13b/"

GLOBAL_PROJECT_PLAN_URL="https://receiver-rejected-average-combat.trycloudflare.com/r/$TOKEN/20260711-123348_global_project_plan_wg_paid/"
ACCESS_MAP_URL="https://receiver-rejected-average-combat.trycloudflare.com/r/$TOKEN/20260711-120734_xs_map_audit_repair_publish/"
XS_MAP_URL="$ACCESS_MAP_URL"
VM100_PUBLIC_URL="https://receiver-rejected-average-combat.trycloudflare.com/r/$TOKEN/20260713-182653_vm100_git_source_097755408853/"
VM101_PUBLIC_URL="https://receiver-rejected-average-combat.trycloudflare.com/r/$TOKEN/20260713-192042_vm101_git_source_027589515a26/"
VM121_PUBLIC_URL="https://receiver-rejected-average-combat.trycloudflare.com/r/$TOKEN/20260713-182700_vm121_git_source_93e0e5a31f09/"
VM101_MODEL_URL="https://receiver-rejected-average-combat.trycloudflare.com/r/$TOKEN/20260713-192030_vm101_model_hmn_download_selector_r13a/vm101-model.txt"

R13A_REPORT_URL="https://receiver-rejected-average-combat.trycloudflare.com/r/$TOKEN/20260713-192030_step050m07r13a_rebuild_hmn_download_all_awg_selector/"
R13A_FACTS_URL="${R13A_REPORT_URL}facts.json"
R13A_STEP_CODE_URL="${R13A_REPORT_URL}step-code.sh"
VM101_FACTS_URL="${VM101_PUBLIC_URL}facts.json"
VM101_PUBLISH_LOG_URL="${VM101_PUBLIC_URL}vm101-publish.txt"
VM101_ARCHIVE_URL="${VM101_PUBLIC_URL}wg-paid-vm101-027589515a26.tar.gz"

LIB_REL="lib/machine-git-count-contract.sh"
FACTS_REL="bin/router-machine-git-facts"
GUARD_REL="bin/router-step-machine-git-guard"
TEST_REL="tests/test-machine-git-count-contract.sh"
POLICY_REL="policy/machine-git-count-contract-v1.md"
TARGET_RELS=("$LIB_REL" "$FACTS_REL" "$GUARD_REL" "$TEST_REL" "$POLICY_REL")

LIB_TARGET="$ROUTER_OPS_ROOT/$LIB_REL"
FACTS_TARGET="$ROUTER_OPS_ROOT/$FACTS_REL"
GUARD_TARGET="$ROUTER_OPS_ROOT/$GUARD_REL"
TEST_TARGET="$ROUTER_OPS_ROOT/$TEST_REL"
POLICY_TARGET="$ROUTER_OPS_ROOT/$POLICY_REL"
BACKUP_DIR="$WORK_DIR/rollback-backup"
PROGRESS_LOG="$REPORT_DIR/progress.log"
VERIFY_FILE="$REPORT_DIR/verify.txt"
ROLLBACK_FILE="$REPORT_DIR/rollback.sh"

LEGACY_FIXTURE_COUNT=""
CANONICAL_ROOT_COUNT=""
CANONICAL_SUBDIR_COUNT=""
CANONICAL_TMP_COUNT=""
ARCHIVE_FIXTURE_COUNT=""
R13A_REPORTED_COUNT=""
R13A_CORRECT_TREE_COUNT=""
R13A_CHANGED_FILE_COUNT=""
R13A_CHANGED_FILE=""
R13A_ARCHIVE_FILE_COUNT=""
R13A_ARCHIVE_SHA_EXPECTED=""
R13A_ARCHIVE_SHA_ACTUAL=""
ACTIVE_AUDIT_PASS="false"
REGRESSION_PASS="false"
BAD_CODE_REJECTED="false"
GOOD_CODE_ACCEPTED="false"
OLD_FACTS_REJECTED="false"
CORRECTED_FACTS_ACCEPTED="false"
CWD_INDEPENDENCE="false"
ROUTER_OPS_GIT_USED="false"
ROUTER_OPS_GIT_BEFORE=""
ROUTER_OPS_GIT_COMMIT=""
ROUTER_OPS_GIT_REMOTE_HEAD=""
ROUTER_OPS_GIT_BRANCH=""
RUNTIME_IMPACT="false"
VM100_CHANGED="false"
VM101_CHANGED="false"
VM121_CHANGED="false"
EXTERNAL_VERIFY="false"

mkdir -p "$WORK_DIR" "$REPORT_DIR" "$LOCAL_PLAN_DIR" "$METHODS_DIR" "$BACKUP_DIR"
cp "$0" "$REPORT_DIR/step-code.sh"
chmod 600 "$REPORT_DIR/step-code.sh"
exec > >(tee -a "$PROGRESS_LOG") 2>&1

log() { printf '>>> [%s] %s\n' "$PHASE" "$*"; }

make_index() {
  local dir="$1" title="$2"
  shift 2
  {
    printf '<!doctype html><html><head><meta charset="utf-8"><title>%s</title></head><body>\n' "$title"
    printf '<h1>%s</h1><ul>\n' "$title"
    while [ "$#" -gt 0 ]; do
      printf '<li><a href="%s">%s</a></li>\n' "$1" "$1"
      shift
    done
    printf '</ul></body></html>\n'
  } > "$dir/index.html"
  chmod 644 "$dir/index.html"
}

write_facts() {
  export STEP RESULT PHASE FAILURE_REASON TS REPORT_URL LOCAL_PLAN_URL GLOBAL_PROJECT_PLAN_URL
  export ACCESS_MAP_URL XS_MAP_URL VM100_PUBLIC_URL VM101_PUBLIC_URL VM121_PUBLIC_URL
  export VM101_MODEL_URL METHODS_URL R13A_REPORT_URL
  export LEGACY_FIXTURE_COUNT CANONICAL_ROOT_COUNT CANONICAL_SUBDIR_COUNT CANONICAL_TMP_COUNT ARCHIVE_FIXTURE_COUNT
  export R13A_REPORTED_COUNT R13A_CORRECT_TREE_COUNT R13A_CHANGED_FILE_COUNT R13A_CHANGED_FILE
  export R13A_ARCHIVE_FILE_COUNT R13A_ARCHIVE_SHA_EXPECTED R13A_ARCHIVE_SHA_ACTUAL
  export ACTIVE_AUDIT_PASS REGRESSION_PASS BAD_CODE_REJECTED GOOD_CODE_ACCEPTED
  export OLD_FACTS_REJECTED CORRECTED_FACTS_ACCEPTED CWD_INDEPENDENCE
  export ROUTER_OPS_GIT_USED ROUTER_OPS_GIT_BEFORE ROUTER_OPS_GIT_COMMIT ROUTER_OPS_GIT_REMOTE_HEAD ROUTER_OPS_GIT_BRANCH
  export RUNTIME_IMPACT VM100_CHANGED VM101_CHANGED VM121_CHANGED EXTERNAL_VERIFY
  python3 - "$REPORT_DIR/facts.json" <<'PY'
import json, os, sys

def b(k): return os.environ.get(k, '').lower() == 'true'
def i(k):
    v=os.environ.get(k,'')
    return int(v) if v.isdigit() else None

data={
  "schema":"router-step-facts-v1",
  "step":os.environ.get("STEP"),
  "timestamp_utc":os.environ.get("TS"),
  "assessment":{
    "decision":os.environ.get("RESULT"),
    "phase":os.environ.get("PHASE"),
    "failure_reason":os.environ.get("FAILURE_REASON") or None,
  },
  "change":{
    "machine":"router-ops",
    "files_added":[
      "/opt/router-ops/lib/machine-git-count-contract.sh",
      "/opt/router-ops/bin/router-machine-git-facts",
      "/opt/router-ops/bin/router-step-machine-git-guard",
      "/opt/router-ops/tests/test-machine-git-count-contract.sh",
      "/opt/router-ops/policy/machine-git-count-contract-v1.md",
    ],
    "runtime_impact":b("RUNTIME_IMPACT"),
  },
  "count_contract":{
    "fixture":{
      "legacy_subtree_file_count":i("LEGACY_FIXTURE_COUNT"),
      "git_tree_file_count_from_worktree_root":i("CANONICAL_ROOT_COUNT"),
      "git_tree_file_count_from_subdirectory":i("CANONICAL_SUBDIR_COUNT"),
      "git_tree_file_count_from_tmp":i("CANONICAL_TMP_COUNT"),
      "published_archive_file_count":i("ARCHIVE_FIXTURE_COUNT"),
      "cwd_independence":b("CWD_INDEPENDENCE"),
    },
    "guards":{
      "regression_pass":b("REGRESSION_PASS"),
      "bad_code_rejected":b("BAD_CODE_REJECTED"),
      "good_code_accepted":b("GOOD_CODE_ACCEPTED"),
      "old_ambiguous_facts_rejected":b("OLD_FACTS_REJECTED"),
      "corrected_facts_accepted":b("CORRECTED_FACTS_ACCEPTED"),
      "active_code_audit_pass":b("ACTIVE_AUDIT_PASS"),
    },
  },
  "r13a_correction":{
    "original_report":os.environ.get("R13A_REPORT_URL"),
    "previous_reported_count":i("R13A_REPORTED_COUNT"),
    "previous_count_scope":"root subtree caused by ls-tree without --full-tree from /root",
    "git_tree_file_count":i("R13A_CORRECT_TREE_COUNT"),
    "changed_file_count":i("R13A_CHANGED_FILE_COUNT"),
    "changed_files":[os.environ.get("R13A_CHANGED_FILE")] if os.environ.get("R13A_CHANGED_FILE") else [],
    "published_archive_file_count":i("R13A_ARCHIVE_FILE_COUNT"),
    "published_archive_sha256_expected":os.environ.get("R13A_ARCHIVE_SHA_EXPECTED") or None,
    "published_archive_sha256_actual":os.environ.get("R13A_ARCHIVE_SHA_ACTUAL") or None,
    "runtime_classification":"PASS",
  },
  "router_ops_git":{
    "used":b("ROUTER_OPS_GIT_USED"),
    "branch":os.environ.get("ROUTER_OPS_GIT_BRANCH") or None,
    "before_commit":os.environ.get("ROUTER_OPS_GIT_BEFORE") or None,
    "commit":os.environ.get("ROUTER_OPS_GIT_COMMIT") or None,
    "remote_head":os.environ.get("ROUTER_OPS_GIT_REMOTE_HEAD") or None,
  },
  "safety":{
    "vm100_changed":b("VM100_CHANGED"),
    "vm101_changed":b("VM101_CHANGED"),
    "vm121_changed":b("VM121_CHANGED"),
    "runtime_impact":b("RUNTIME_IMPACT"),
    "external_http_verify":b("EXTERNAL_VERIFY"),
  },
  "links":{
    "step_report":os.environ.get("REPORT_URL"),
    "local_m07_plan":os.environ.get("LOCAL_PLAN_URL"),
    "global_project_plan":os.environ.get("GLOBAL_PROJECT_PLAN_URL"),
    "access_map":os.environ.get("ACCESS_MAP_URL"),
    "xs_map":os.environ.get("XS_MAP_URL"),
    "vm100":os.environ.get("VM100_PUBLIC_URL"),
    "vm101":os.environ.get("VM101_PUBLIC_URL"),
    "vm121":os.environ.get("VM121_PUBLIC_URL"),
    "vm101_model":os.environ.get("VM101_MODEL_URL"),
    "vm101_methods":os.environ.get("METHODS_URL"),
  }
}
with open(sys.argv[1], 'w', encoding='utf-8') as f:
    json.dump(data, f, ensure_ascii=False, indent=2)
    f.write('\n')
PY
  chmod 644 "$REPORT_DIR/facts.json"
}

write_report() {
  cat > "$REPORT_DIR/report.txt" <<EOF
RESULT=$RESULT
STEP=$STEP
TIMESTAMP_UTC=$TS
PHASE=$PHASE
FAILURE_REASON=${FAILURE_REASON:-NONE}

PURPOSE:
- Make full Git tree counting independent of current working directory.
- Require an independent git-archive count.
- Reject unsafe ls-tree usage without --full-tree.
- Reject ambiguous tracked_file_count facts.
- Publish a formal correction for R13A without rewriting its historical report.

RESULTS:
- legacy_fixture_count=${LEGACY_FIXTURE_COUNT:-UNKNOWN}
- canonical_count_from_worktree_root=${CANONICAL_ROOT_COUNT:-UNKNOWN}
- canonical_count_from_subdirectory=${CANONICAL_SUBDIR_COUNT:-UNKNOWN}
- canonical_count_from_tmp=${CANONICAL_TMP_COUNT:-UNKNOWN}
- archive_fixture_count=${ARCHIVE_FIXTURE_COUNT:-UNKNOWN}
- cwd_independence=$CWD_INDEPENDENCE
- bad_code_rejected=$BAD_CODE_REJECTED
- good_code_accepted=$GOOD_CODE_ACCEPTED
- old_ambiguous_facts_rejected=$OLD_FACTS_REJECTED
- corrected_facts_accepted=$CORRECTED_FACTS_ACCEPTED
- active_code_audit_pass=$ACTIVE_AUDIT_PASS

R13A_CORRECTION:
- previous_reported_count=${R13A_REPORTED_COUNT:-UNKNOWN}
- previous_count_scope=root subtree
- correct_git_tree_file_count=${R13A_CORRECT_TREE_COUNT:-UNKNOWN}
- correct_changed_file_count=${R13A_CHANGED_FILE_COUNT:-UNKNOWN}
- changed_file=${R13A_CHANGED_FILE:-UNKNOWN}
- published_archive_file_count=${R13A_ARCHIVE_FILE_COUNT:-UNKNOWN}
- R13A_RUNTIME_CLASSIFICATION=PASS

ROUTER_OPS_GIT:
- used=$ROUTER_OPS_GIT_USED
- branch=${ROUTER_OPS_GIT_BRANCH:-NONE}
- before_commit=${ROUTER_OPS_GIT_BEFORE:-NONE}
- commit=${ROUTER_OPS_GIT_COMMIT:-NONE}
- remote_head=${ROUTER_OPS_GIT_REMOTE_HEAD:-NONE}

RUNTIME:
- RUNTIME_IMPACT=$RUNTIME_IMPACT
- VM100_CHANGED=$VM100_CHANGED
- VM101_CHANGED=$VM101_CHANGED
- VM121_CHANGED=$VM121_CHANGED
- No refresh, rebalance, service restart, route change or VPN change was executed.

ROLLBACK:
- script=${REPORT_URL}rollback.sh

STEP_REPORT_URL=$REPORT_URL
LOCAL_M07_PLAN_URL=$LOCAL_PLAN_URL
GLOBAL_PROJECT_PLAN_URL=$GLOBAL_PROJECT_PLAN_URL
ACCESS_MAP_URL=$ACCESS_MAP_URL
XS_MAP_URL=$XS_MAP_URL
VM100_PUBLIC_URL=$VM100_PUBLIC_URL
VM101_PUBLIC_URL=$VM101_PUBLIC_URL
VM121_PUBLIC_URL=$VM121_PUBLIC_URL
VM101_MODEL_URL=$VM101_MODEL_URL
VM101_METHODS_URL=$METHODS_URL
FACTS_URL=${REPORT_URL}facts.json
VERIFY_URL=${REPORT_URL}verify.txt
ROLLBACK_URL=${REPORT_URL}rollback.sh
ARCHITECTURE_PLAN_URL=$LOCAL_PLAN_URL
EOF
  chmod 644 "$REPORT_DIR/report.txt"
}

write_materials() {
  write_facts
  write_report
  make_index "$REPORT_DIR" "$STEP" \
    report.txt facts.json step-code.sh progress.log verify.txt rollback.sh \
    r13a-correction.json regression.log active-code-audit.log install.log git.log \
    machine-git-count-contract.sh router-machine-git-facts router-step-machine-git-guard \
    test-machine-git-count-contract.sh machine-git-count-contract-v1.md methods-and-limitations.txt
}

print_final() {
  printf '\nRESULT=%s\n' "$RESULT"
  printf 'STEP_REPORT_URL=%s\n' "$REPORT_URL"
  printf 'LOCAL_M07_PLAN_URL=%s\n' "$LOCAL_PLAN_URL"
  printf 'GLOBAL_PROJECT_PLAN_URL=%s\n' "$GLOBAL_PROJECT_PLAN_URL"
  printf 'ACCESS_MAP_URL=%s\n' "$ACCESS_MAP_URL"
  printf 'XS_MAP_URL=%s\n' "$XS_MAP_URL"
  printf 'VM100_PUBLIC_URL=%s\n' "$VM100_PUBLIC_URL"
  printf 'VM101_PUBLIC_URL=%s\n' "$VM101_PUBLIC_URL"
  printf 'VM121_PUBLIC_URL=%s\n' "$VM121_PUBLIC_URL"
  printf 'VM101_MODEL_URL=%s\n' "$VM101_MODEL_URL"
  printf 'VM101_METHODS_URL=%s\n' "$METHODS_URL"
  printf 'FACTS_URL=%sfacts.json\n' "$REPORT_URL"
  printf 'VERIFY_URL=%sverify.txt\n' "$REPORT_URL"
  printf 'ROLLBACK_URL=%srollback.sh\n' "$REPORT_URL"
  printf 'ARCHITECTURE_PLAN_URL=%s\n' "$LOCAL_PLAN_URL"
}

on_error() {
  local rc=$?
  trap - ERR
  set +e
  RESULT="$STOP_RESULT"
  FAILURE_REASON="${FAILURE_REASON:-COMMAND_FAILED_IN_${PHASE}_RC_${rc}}"
  printf '>>> [STOP] phase=%s rc=%s reason=%s\n' "$PHASE" "$rc" "$FAILURE_REASON"
  write_materials
  print_final
  exit "$rc"
}
trap on_error ERR

PHASE="preflight"
log "checking router-ops environment and immutable machine publication inputs"
[ "$(id -un)" = "ops" ] || { FAILURE_REASON="RUN_AS_OPS_ON_ROUTER_OPS"; false; }
for cmd in git tar awk python3 curl sha256sum find grep install; do
  command -v "$cmd" >/dev/null 2>&1 || { FAILURE_REASON="MISSING_COMMAND_${cmd}"; false; }
done
[ -d "$ROUTER_OPS_ROOT" ] && [ -w "$ROUTER_OPS_ROOT" ] || { FAILURE_REASON="ROUTER_OPS_ROOT_NOT_WRITABLE"; false; }
[ -d "$PUBLIC_ROOT" ] && [ -w "$PUBLIC_ROOT" ] || { FAILURE_REASON="PUBLIC_ROOT_NOT_WRITABLE"; false; }

for rel in "${TARGET_RELS[@]}"; do
  if [ -e "$ROUTER_OPS_ROOT/$rel" ]; then
    FAILURE_REASON="TARGET_ALREADY_EXISTS_${rel//\//_}"
    false
  fi
done

if git -C "$ROUTER_OPS_ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
  ROOT_ACTUAL="$(git -C "$ROUTER_OPS_ROOT" rev-parse --show-toplevel)"
  [ "$ROOT_ACTUAL" = "$ROUTER_OPS_ROOT" ] || { FAILURE_REASON="UNEXPECTED_ROUTER_OPS_GIT_ROOT"; false; }
  [ -z "$(git -C "$ROUTER_OPS_ROOT" status --porcelain --untracked-files=no)" ] || { FAILURE_REASON="ROUTER_OPS_GIT_NOT_CLEAN"; false; }
  ROUTER_OPS_GIT_USED="true"
  ROUTER_OPS_GIT_BEFORE="$(git -C "$ROUTER_OPS_ROOT" rev-parse HEAD)"
  ROUTER_OPS_GIT_BRANCH="$(git -C "$ROUTER_OPS_ROOT" symbolic-ref --short HEAD)"
fi

PHASE="build-tools"
log "building canonical helper, CLI, guard, regression test and policy"
CANDIDATE_ROOT="$WORK_DIR/candidate"
mkdir -p "$CANDIDATE_ROOT/lib" "$CANDIDATE_ROOT/bin" "$CANDIDATE_ROOT/tests" "$CANDIDATE_ROOT/policy"

cat > "$CANDIDATE_ROOT/$LIB_REL" <<'EOF_LIB'
#!/usr/bin/env bash
# Canonical Machine Git count contract v1.
# All full-tree counts are independent of current working directory.

router_machine_git_tree_file_count() {
  local git_dir="$1" work_tree="$2" commit="$3"
  git --git-dir="$git_dir" --work-tree="$work_tree" \
    ls-tree -r --full-tree --name-only "$commit" |
    awk 'END { print NR + 0 }'
}

router_machine_git_archive_file_count() {
  local git_dir="$1" work_tree="$2" commit="$3"
  git --git-dir="$git_dir" --work-tree="$work_tree" \
    archive --format=tar "$commit" |
    tar -tf - |
    awk '!/\/$/ { n++ } END { print n + 0 }'
}

router_machine_git_changed_files() {
  local git_dir="$1" work_tree="$2" commit="$3"
  git --git-dir="$git_dir" --work-tree="$work_tree" \
    diff-tree --root --no-commit-id --name-only -r "$commit"
}

router_machine_git_changed_file_count() {
  local git_dir="$1" work_tree="$2" commit="$3"
  router_machine_git_changed_files "$git_dir" "$work_tree" "$commit" |
    awk 'NF { n++ } END { print n + 0 }'
}

router_machine_git_assert_count_contract() {
  local git_dir="$1" work_tree="$2" commit="$3"
  local tree_count archive_count
  tree_count="$(router_machine_git_tree_file_count "$git_dir" "$work_tree" "$commit")"
  archive_count="$(router_machine_git_archive_file_count "$git_dir" "$work_tree" "$commit")"
  [ "$tree_count" = "$archive_count" ] || {
    printf 'ERROR: git tree count %s != git archive count %s\n' "$tree_count" "$archive_count" >&2
    return 1
  }
  printf '%s\n' "$tree_count"
}
EOF_LIB
chmod 644 "$CANDIDATE_ROOT/$LIB_REL"

cat > "$CANDIDATE_ROOT/$FACTS_REL" <<'EOF_FACTS'
#!/usr/bin/env bash
set -Eeuo pipefail

LIB="/opt/router-ops/lib/machine-git-count-contract.sh"
[ -r "$LIB" ] || { echo "ERROR: missing $LIB" >&2; exit 2; }
# shellcheck source=/opt/router-ops/lib/machine-git-count-contract.sh
. "$LIB"

GIT_DIR=""
WORK_TREE=""
COMMIT="HEAD"
FORMAT="shell"
while [ "$#" -gt 0 ]; do
  case "$1" in
    --git-dir) GIT_DIR="$2"; shift 2 ;;
    --work-tree) WORK_TREE="$2"; shift 2 ;;
    --commit) COMMIT="$2"; shift 2 ;;
    --json) FORMAT="json"; shift ;;
    *) echo "ERROR: unknown argument: $1" >&2; exit 2 ;;
  esac
done
[ -n "$GIT_DIR" ] || { echo "ERROR: --git-dir is required" >&2; exit 2; }
[ -n "$WORK_TREE" ] || { echo "ERROR: --work-tree is required" >&2; exit 2; }

RESOLVED="$(git --git-dir="$GIT_DIR" --work-tree="$WORK_TREE" rev-parse "$COMMIT^{commit}")"
TREE="$(git --git-dir="$GIT_DIR" --work-tree="$WORK_TREE" rev-parse "$RESOLVED^{tree}")"
TREE_COUNT="$(router_machine_git_tree_file_count "$GIT_DIR" "$WORK_TREE" "$RESOLVED")"
ARCHIVE_COUNT="$(router_machine_git_archive_file_count "$GIT_DIR" "$WORK_TREE" "$RESOLVED")"
[ "$TREE_COUNT" = "$ARCHIVE_COUNT" ] || {
  echo "ERROR: GIT_TREE_FILE_COUNT=$TREE_COUNT differs from PUBLISHED_ARCHIVE_FILE_COUNT=$ARCHIVE_COUNT" >&2
  exit 3
}
mapfile -t CHANGED < <(router_machine_git_changed_files "$GIT_DIR" "$WORK_TREE" "$RESOLVED")
CHANGED_COUNT="${#CHANGED[@]}"

if [ "$FORMAT" = "json" ]; then
  python3 - "$RESOLVED" "$TREE" "$TREE_COUNT" "$ARCHIVE_COUNT" "$CHANGED_COUNT" "${CHANGED[@]}" <<'PY'
import json, sys
commit, tree, tc, ac, cc, *changed = sys.argv[1:]
print(json.dumps({
  "commit": commit,
  "tree": tree,
  "git_tree_file_count": int(tc),
  "published_archive_file_count": int(ac),
  "changed_file_count": int(cc),
  "changed_files": changed,
  "count_contract_pass": int(tc) == int(ac),
}, ensure_ascii=False, indent=2))
PY
else
  printf 'COMMIT=%s\n' "$RESOLVED"
  printf 'TREE=%s\n' "$TREE"
  printf 'GIT_TREE_FILE_COUNT=%s\n' "$TREE_COUNT"
  printf 'PUBLISHED_ARCHIVE_FILE_COUNT=%s\n' "$ARCHIVE_COUNT"
  printf 'CHANGED_FILE_COUNT=%s\n' "$CHANGED_COUNT"
  printf 'CHANGED_FILES_BEGIN\n'
  printf '%s\n' "${CHANGED[@]}"
  printf 'CHANGED_FILES_END\n'
  printf 'COUNT_CONTRACT_PASS=true\n'
fi
EOF_FACTS
chmod 755 "$CANDIDATE_ROOT/$FACTS_REL"

cat > "$CANDIDATE_ROOT/$GUARD_REL" <<'EOF_GUARD'
#!/usr/bin/env python3
import argparse
import json
import pathlib
import re
import sys

p=argparse.ArgumentParser(description="Guard Machine Git count contract")
p.add_argument("--step-code", action="append", default=[])
p.add_argument("--facts-json", action="append", default=[])
p.add_argument("--published-facts-json")
a=p.parse_args()
errors=[]

for raw in a.step_code:
    path=pathlib.Path(raw)
    text=path.read_text(encoding="utf-8", errors="replace")
    lines=text.splitlines()
    for i,line in enumerate(lines):
        if "ls-tree" not in line:
            continue
        window="\n".join(lines[max(0,i-2):min(len(lines),i+5)])
        if "LEGACY_FIXTURE_ONLY" in window:
            continue
        if "--name-only" in window and "--full-tree" not in window:
            errors.append(f"{path}:{i+1}: unsafe ls-tree count without --full-tree")

published_count=None
if a.published_facts_json:
    pub=json.loads(pathlib.Path(a.published_facts_json).read_text(encoding="utf-8"))
    for key in ("file_count","git_tree_file_count","published_archive_file_count"):
        if isinstance(pub.get(key), int):
            published_count=pub[key]
            break
    if published_count is None and isinstance(pub.get("machine_git"),dict):
        for key in ("git_tree_file_count","published_archive_file_count"):
            if isinstance(pub["machine_git"].get(key), int):
                published_count=pub["machine_git"][key]
                break

for raw in a.facts_json:
    path=pathlib.Path(raw)
    data=json.loads(path.read_text(encoding="utf-8"))
    def walk(obj, where="$"):
        if isinstance(obj,dict):
            if "tracked_file_count" in obj:
                errors.append(f"{path}:{where}: ambiguous field tracked_file_count is forbidden")
            if "changed_files" in obj and "changed_file_count" in obj:
                if isinstance(obj["changed_files"],list) and isinstance(obj["changed_file_count"],int):
                    if len(obj["changed_files"]) != obj["changed_file_count"]:
                        errors.append(f"{path}:{where}: changed_file_count does not match changed_files")
            if "git_tree_file_count" in obj and "published_archive_file_count" in obj:
                if obj["git_tree_file_count"] != obj["published_archive_file_count"]:
                    errors.append(f"{path}:{where}: tree/archive count mismatch")
            if published_count is not None and "git_tree_file_count" in obj:
                if obj["git_tree_file_count"] != published_count:
                    errors.append(f"{path}:{where}: git_tree_file_count differs from published facts")
            for k,v in obj.items(): walk(v, f"{where}.{k}")
        elif isinstance(obj,list):
            for n,v in enumerate(obj): walk(v, f"{where}[{n}]")
    walk(data)

if errors:
    for e in errors: print(f"GUARD_ERROR={e}")
    print("MACHINE_GIT_COUNT_CONTRACT_PASS=false")
    sys.exit(1)
print("MACHINE_GIT_COUNT_CONTRACT_PASS=true")
EOF_GUARD
chmod 755 "$CANDIDATE_ROOT/$GUARD_REL"

cat > "$CANDIDATE_ROOT/$TEST_REL" <<'EOF_TEST'
#!/usr/bin/env bash
set -Eeuo pipefail

ROOT="${1:-$(mktemp -d)}"
KEEP="${KEEP_FIXTURE:-false}"
LIB="/opt/router-ops/lib/machine-git-count-contract.sh"
FACTS="/opt/router-ops/bin/router-machine-git-facts"
GUARD="/opt/router-ops/bin/router-step-machine-git-guard"
cleanup() { [ "$KEEP" = true ] || rm -rf "$ROOT"; }
trap cleanup EXIT

mkdir -p "$ROOT/wt/root" "$ROOT/wt/etc" "$ROOT/wt/usr" "$ROOT/samples"
printf a > "$ROOT/wt/root/file-1"
printf b > "$ROOT/wt/root/file-2"
printf c > "$ROOT/wt/etc/file-3"
printf d > "$ROOT/wt/usr/file-4"
git init --bare -q "$ROOT/gitdir"
git --git-dir="$ROOT/gitdir" config core.bare false
git --git-dir="$ROOT/gitdir" config core.worktree "$ROOT/wt"
git --git-dir="$ROOT/gitdir" config user.name regression
git --git-dir="$ROOT/gitdir" config user.email regression@example.invalid
git --git-dir="$ROOT/gitdir" --work-tree="$ROOT/wt" add -A
git --git-dir="$ROOT/gitdir" --work-tree="$ROOT/wt" commit -qm fixture
HEAD="$(git --git-dir="$ROOT/gitdir" rev-parse HEAD)"

# This line intentionally reproduces the historical bug. LEGACY_FIXTURE_ONLY
LEGACY_COUNT="$(cd "$ROOT/wt/root" && git --git-dir="$ROOT/gitdir" ls-tree -r --name-only "$HEAD" | wc -l | tr -d ' ')"

# shellcheck source=/opt/router-ops/lib/machine-git-count-contract.sh
. "$LIB"
ROOT_COUNT="$(cd "$ROOT/wt" && router_machine_git_tree_file_count "$ROOT/gitdir" "$ROOT/wt" "$HEAD")"
SUBDIR_COUNT="$(cd "$ROOT/wt/root" && router_machine_git_tree_file_count "$ROOT/gitdir" "$ROOT/wt" "$HEAD")"
TMP_COUNT="$(cd /tmp && router_machine_git_tree_file_count "$ROOT/gitdir" "$ROOT/wt" "$HEAD")"
ARCHIVE_COUNT="$(router_machine_git_archive_file_count "$ROOT/gitdir" "$ROOT/wt" "$HEAD")"
[ "$LEGACY_COUNT" = 2 ]
[ "$ROOT_COUNT" = 4 ]
[ "$SUBDIR_COUNT" = 4 ]
[ "$TMP_COUNT" = 4 ]
[ "$ARCHIVE_COUNT" = 4 ]

cat > "$ROOT/samples/bad-step.sh" <<'BAD'
COUNT="$(git --git-dir="$GIT_DIR" ls-tree -r --name-only "$HEAD" | wc -l)"
BAD
cat > "$ROOT/samples/good-step.sh" <<'GOOD'
COUNT="$(git --git-dir="$GIT_DIR" --work-tree=/ ls-tree -r --full-tree --name-only "$HEAD" | wc -l)"
GOOD
cat > "$ROOT/samples/old-facts.json" <<'OLD'
{"machine_git":{"tracked_file_count":21,"changed_files":["root/a"]}}
OLD
cat > "$ROOT/samples/published-facts.json" <<'PUB'
{"file_count":4}
PUB
cat > "$ROOT/samples/corrected-facts.json" <<'NEW'
{"machine_git":{"git_tree_file_count":4,"published_archive_file_count":4,"changed_file_count":1,"changed_files":["root/a"]}}
NEW

set +e
"$GUARD" --step-code "$ROOT/samples/bad-step.sh" > "$ROOT/bad-code.log" 2>&1
BAD_RC=$?
"$GUARD" --facts-json "$ROOT/samples/old-facts.json" > "$ROOT/old-facts.log" 2>&1
OLD_RC=$?
set -e
[ "$BAD_RC" -ne 0 ]
[ "$OLD_RC" -ne 0 ]
"$GUARD" --step-code "$ROOT/samples/good-step.sh" > "$ROOT/good-code.log"
"$GUARD" --facts-json "$ROOT/samples/corrected-facts.json" --published-facts-json "$ROOT/samples/published-facts.json" > "$ROOT/corrected-facts.log"
"$FACTS" --git-dir "$ROOT/gitdir" --work-tree "$ROOT/wt" --commit "$HEAD" --json > "$ROOT/facts.json"

printf 'LEGACY_FIXTURE_COUNT=%s\n' "$LEGACY_COUNT"
printf 'CANONICAL_ROOT_COUNT=%s\n' "$ROOT_COUNT"
printf 'CANONICAL_SUBDIR_COUNT=%s\n' "$SUBDIR_COUNT"
printf 'CANONICAL_TMP_COUNT=%s\n' "$TMP_COUNT"
printf 'ARCHIVE_FIXTURE_COUNT=%s\n' "$ARCHIVE_COUNT"
printf 'BAD_CODE_REJECTED=true\n'
printf 'GOOD_CODE_ACCEPTED=true\n'
printf 'OLD_FACTS_REJECTED=true\n'
printf 'CORRECTED_FACTS_ACCEPTED=true\n'
printf 'CWD_INDEPENDENCE=true\n'
printf 'REGRESSION_PASS=true\n'
EOF_TEST
chmod 755 "$CANDIDATE_ROOT/$TEST_REL"

cat > "$CANDIDATE_ROOT/$POLICY_REL" <<'EOF_POLICY'
# Machine Git count contract v1

This contract is mandatory for every WG Paid / MGTS STEP that reports Machine Git facts.

1. A full commit tree count must use `git ls-tree -r --full-tree --name-only` with explicit `--git-dir` and `--work-tree`.
2. The full tree count must be independently equal to the number of non-directory entries produced by `git archive`.
3. Future STEP facts must use explicit fields:
   - `git_tree_file_count`
   - `published_archive_file_count`
   - `changed_file_count`
   - `changed_files`
4. The ambiguous field `tracked_file_count` is forbidden.
5. STEP code must pass `/opt/router-ops/bin/router-step-machine-git-guard --step-code <step-code.sh>` before changes and again before PASS.
6. STEP facts must pass the same guard with `--facts-json`; when a Machine Git publication exists, also provide its published `facts.json`.
7. Any tree/archive mismatch or unsafe `ls-tree` use produces STOP, never PASS.
8. Historical reports are immutable. Corrections are published as later timestamped STEP artifacts.
EOF_POLICY
chmod 644 "$CANDIDATE_ROOT/$POLICY_REL"

bash -n "$CANDIDATE_ROOT/$LIB_REL"
bash -n "$CANDIDATE_ROOT/$FACTS_REL"
python3 -m py_compile "$CANDIDATE_ROOT/$GUARD_REL"
bash -n "$CANDIDATE_ROOT/$TEST_REL"

cp "$CANDIDATE_ROOT/$LIB_REL" "$REPORT_DIR/machine-git-count-contract.sh"
cp "$CANDIDATE_ROOT/$FACTS_REL" "$REPORT_DIR/router-machine-git-facts"
cp "$CANDIDATE_ROOT/$GUARD_REL" "$REPORT_DIR/router-step-machine-git-guard"
cp "$CANDIDATE_ROOT/$TEST_REL" "$REPORT_DIR/test-machine-git-count-contract.sh"
cp "$CANDIDATE_ROOT/$POLICY_REL" "$REPORT_DIR/machine-git-count-contract-v1.md"
chmod 644 "$REPORT_DIR"/{machine-git-count-contract.sh,router-machine-git-facts,router-step-machine-git-guard,test-machine-git-count-contract.sh,machine-git-count-contract-v1.md}

PHASE="install"
log "installing source-controlled router-ops tools atomically"
: > "$REPORT_DIR/install.log"
for rel in "${TARGET_RELS[@]}"; do
  src="$CANDIDATE_ROOT/$rel"
  dst="$ROUTER_OPS_ROOT/$rel"
  mode=644
  case "$rel" in bin/*|tests/*) mode=755 ;; esac
  mkdir -p "$(dirname "$dst")"
  tmp="${dst}.r13b.${TS}.tmp"
  install -m "$mode" "$src" "$tmp"
  mv -f "$tmp" "$dst"
  printf '%s  %s\n' "$(sha256sum "$dst" | awk '{print $1}')" "$dst" | tee -a "$REPORT_DIR/install.log"
done

PHASE="regression"
log "running exact regression fixture and CWD-independence proof"
REGRESSION_ROOT="$WORK_DIR/regression"
KEEP_FIXTURE=true "$TEST_TARGET" "$REGRESSION_ROOT" | tee "$REPORT_DIR/regression.log"
LEGACY_FIXTURE_COUNT="$(awk -F= '/^LEGACY_FIXTURE_COUNT=/{print $2}' "$REPORT_DIR/regression.log")"
CANONICAL_ROOT_COUNT="$(awk -F= '/^CANONICAL_ROOT_COUNT=/{print $2}' "$REPORT_DIR/regression.log")"
CANONICAL_SUBDIR_COUNT="$(awk -F= '/^CANONICAL_SUBDIR_COUNT=/{print $2}' "$REPORT_DIR/regression.log")"
CANONICAL_TMP_COUNT="$(awk -F= '/^CANONICAL_TMP_COUNT=/{print $2}' "$REPORT_DIR/regression.log")"
ARCHIVE_FIXTURE_COUNT="$(awk -F= '/^ARCHIVE_FIXTURE_COUNT=/{print $2}' "$REPORT_DIR/regression.log")"
BAD_CODE_REJECTED="$(awk -F= '/^BAD_CODE_REJECTED=/{print $2}' "$REPORT_DIR/regression.log")"
GOOD_CODE_ACCEPTED="$(awk -F= '/^GOOD_CODE_ACCEPTED=/{print $2}' "$REPORT_DIR/regression.log")"
OLD_FACTS_REJECTED="$(awk -F= '/^OLD_FACTS_REJECTED=/{print $2}' "$REPORT_DIR/regression.log")"
CORRECTED_FACTS_ACCEPTED="$(awk -F= '/^CORRECTED_FACTS_ACCEPTED=/{print $2}' "$REPORT_DIR/regression.log")"
CWD_INDEPENDENCE="$(awk -F= '/^CWD_INDEPENDENCE=/{print $2}' "$REPORT_DIR/regression.log")"
REGRESSION_PASS="$(awk -F= '/^REGRESSION_PASS=/{print $2}' "$REPORT_DIR/regression.log")"
[ "$LEGACY_FIXTURE_COUNT" = 2 ]
[ "$CANONICAL_ROOT_COUNT" = 4 ]
[ "$CANONICAL_SUBDIR_COUNT" = 4 ]
[ "$CANONICAL_TMP_COUNT" = 4 ]
[ "$ARCHIVE_FIXTURE_COUNT" = 4 ]
[ "$REGRESSION_PASS" = true ]

PHASE="r13a-correction"
log "verifying R13A against immutable published facts and archive"
mkdir -p "$WORK_DIR/r13a"
curl -fsS --retry 4 --retry-all-errors --connect-timeout 10 --max-time 60 "$R13A_FACTS_URL" -o "$WORK_DIR/r13a/r13a-facts.json"
curl -fsS --retry 4 --retry-all-errors --connect-timeout 10 --max-time 60 "$VM101_FACTS_URL" -o "$WORK_DIR/r13a/vm101-facts.json"
curl -fsS --retry 4 --retry-all-errors --connect-timeout 10 --max-time 60 "$VM101_PUBLISH_LOG_URL" -o "$WORK_DIR/r13a/vm101-publish.txt"
curl -fsS --retry 4 --retry-all-errors --connect-timeout 10 --max-time 120 "$VM101_ARCHIVE_URL" -o "$WORK_DIR/r13a/vm101.tar.gz"

readarray -t CORRECTION_VALUES < <(python3 - "$WORK_DIR/r13a/r13a-facts.json" "$WORK_DIR/r13a/vm101-facts.json" "$WORK_DIR/r13a/vm101-publish.txt" <<'PY'
import json,re,sys
r=json.load(open(sys.argv[1]))
v=json.load(open(sys.argv[2]))
log=open(sys.argv[3],encoding='utf-8',errors='replace').read()
old=r.get('machine_git',{}).get('tracked_file_count')
full=v.get('file_count')
sha=v.get('archive_sha256') or v.get('sha256')
m=re.search(r'^STAGED_CHANGE_COUNT=(\d+)$',log,re.M)
changed_count=int(m.group(1)) if m else None
files=[]
for pat in [r'^STAGED_FILES_BEGIN\n(.*?)\nSTAGED_FILES_END$',r'^CHANGED_FILES_BEGIN\n(.*?)\nCHANGED_FILES_END$']:
    q=re.search(pat,log,re.M|re.S)
    if q:
        files=[x for x in q.group(1).splitlines() if x.strip()]
        break
if not files and 'root/hmn/hmn-download-all-awg.sh' in log:
    files=['root/hmn/hmn-download-all-awg.sh']
print(old if isinstance(old,int) else '')
print(full if isinstance(full,int) else '')
print(changed_count if isinstance(changed_count,int) else len(files))
print(files[0] if len(files)==1 else '')
print(sha or '')
PY
)
R13A_REPORTED_COUNT="${CORRECTION_VALUES[0]}"
R13A_CORRECT_TREE_COUNT="${CORRECTION_VALUES[1]}"
R13A_CHANGED_FILE_COUNT="${CORRECTION_VALUES[2]}"
R13A_CHANGED_FILE="${CORRECTION_VALUES[3]}"
R13A_ARCHIVE_SHA_EXPECTED="${CORRECTION_VALUES[4]}"
R13A_ARCHIVE_SHA_ACTUAL="$(sha256sum "$WORK_DIR/r13a/vm101.tar.gz" | awk '{print $1}')"
R13A_ARCHIVE_FILE_COUNT="$(tar -tzf "$WORK_DIR/r13a/vm101.tar.gz" | awk '!/\/$/ {n++} END{print n+0}')"
[ "$R13A_REPORTED_COUNT" = 21 ] || { FAILURE_REASON="R13A_PREVIOUS_COUNT_NOT_21"; false; }
[ "$R13A_CORRECT_TREE_COUNT" = 44 ] || { FAILURE_REASON="R13A_PUBLISHED_TREE_COUNT_NOT_44"; false; }
[ "$R13A_CHANGED_FILE_COUNT" = 1 ] || { FAILURE_REASON="R13A_CHANGED_FILE_COUNT_NOT_1"; false; }
[ "$R13A_CHANGED_FILE" = "root/hmn/hmn-download-all-awg.sh" ] || { FAILURE_REASON="R13A_CHANGED_FILE_UNEXPECTED"; false; }
[ "$R13A_ARCHIVE_FILE_COUNT" = 44 ] || { FAILURE_REASON="R13A_ARCHIVE_COUNT_NOT_44"; false; }
[ -z "$R13A_ARCHIVE_SHA_EXPECTED" ] || [ "$R13A_ARCHIVE_SHA_ACTUAL" = "$R13A_ARCHIVE_SHA_EXPECTED" ] || { FAILURE_REASON="R13A_ARCHIVE_SHA_MISMATCH"; false; }

cat > "$REPORT_DIR/r13a-correction.json" <<EOF
{
  "schema": "machine-git-report-correction-v1",
  "corrects_step": "STEP_050M07R13A_REBUILD_HMN_DOWNLOAD_ALL_AWG_SELECTOR",
  "original_report_url": "$R13A_REPORT_URL",
  "previous_reported_count": $R13A_REPORTED_COUNT,
  "previous_count_scope": "root subtree",
  "root_cause": "git ls-tree was called without --full-tree while core.worktree=/ and current directory=/root",
  "git_tree_file_count": $R13A_CORRECT_TREE_COUNT,
  "published_archive_file_count": $R13A_ARCHIVE_FILE_COUNT,
  "changed_file_count": $R13A_CHANGED_FILE_COUNT,
  "changed_files": ["$R13A_CHANGED_FILE"],
  "runtime_classification": "PASS",
  "runtime_impact": false
}
EOF
chmod 644 "$REPORT_DIR/r13a-correction.json"

PHASE="active-code-audit"
log "auditing active router-ops Machine Git code and this STEP with the new guard"
: > "$REPORT_DIR/active-code-audit.log"
AUDIT_FILES=("$REPORT_DIR/step-code.sh" "$FACTS_TARGET" "$TEST_TARGET")
MACHINE_CLOSE_PATH="$(command -v router-machine-close || true)"
[ -n "$MACHINE_CLOSE_PATH" ] && AUDIT_FILES+=("$MACHINE_CLOSE_PATH")
while IFS= read -r f; do
  case "$f" in
    "$GUARD_TARGET") continue ;;
  esac
  if grep -q 'ls-tree' "$f" 2>/dev/null; then AUDIT_FILES+=("$f"); fi
done < <(find "$ROUTER_OPS_ROOT/bin" "$ROUTER_OPS_ROOT/lib" -maxdepth 2 -type f 2>/dev/null | sort -u)

printf '%s\n' "${AUDIT_FILES[@]}" | sort -u > "$WORK_DIR/audit-files.txt"
while IFS= read -r f; do
  [ -f "$f" ] || continue
  printf 'AUDIT=%s\n' "$f" | tee -a "$REPORT_DIR/active-code-audit.log"
  "$GUARD_TARGET" --step-code "$f" | tee -a "$REPORT_DIR/active-code-audit.log"
done < "$WORK_DIR/audit-files.txt"
ACTIVE_AUDIT_PASS="true"

PHASE="git-commit"
if [ "$ROUTER_OPS_GIT_USED" = true ]; then
  log "committing and pushing exact router-ops hardening files"
  : > "$REPORT_DIR/git.log"
  git -C "$ROUTER_OPS_ROOT" add -- "${TARGET_RELS[@]}"
  mapfile -t STAGED < <(git -C "$ROUTER_OPS_ROOT" diff --cached --name-only)
  [ "${#STAGED[@]}" -eq 5 ] || { FAILURE_REASON="UNEXPECTED_STAGED_FILE_COUNT"; false; }
  for rel in "${TARGET_RELS[@]}"; do
    printf '%s\n' "${STAGED[@]}" | grep -Fxq "$rel" || { FAILURE_REASON="EXPECTED_FILE_NOT_STAGED_${rel//\//_}"; false; }
  done
  git -C "$ROUTER_OPS_ROOT" commit -m "$STEP: enforce Machine Git count contract" | tee -a "$REPORT_DIR/git.log"
  ROUTER_OPS_GIT_COMMIT="$(git -C "$ROUTER_OPS_ROOT" rev-parse HEAD)"
  git -C "$ROUTER_OPS_ROOT" push origin "$ROUTER_OPS_GIT_BRANCH" | tee -a "$REPORT_DIR/git.log"
  ROUTER_OPS_GIT_REMOTE_HEAD="$(git -C "$ROUTER_OPS_ROOT" ls-remote origin "refs/heads/$ROUTER_OPS_GIT_BRANCH" | awk '{print $1}')"
  [ "$ROUTER_OPS_GIT_COMMIT" = "$ROUTER_OPS_GIT_REMOTE_HEAD" ] || { FAILURE_REASON="ROUTER_OPS_LOCAL_REMOTE_MISMATCH"; false; }
  [ -z "$(git -C "$ROUTER_OPS_ROOT" status --porcelain --untracked-files=no)" ] || { FAILURE_REASON="ROUTER_OPS_GIT_DIRTY_AFTER_COMMIT"; false; }
else
  printf 'ROUTER_OPS_GIT_USED=false\n' > "$REPORT_DIR/git.log"
fi

PHASE="publish-plan-methods"
log "publishing correction methods and updated local M07 plan"
cat > "$METHODS_DIR/methods.txt" <<EOF
VM101 / WG Paid Machine Git count methods
Generated UTC: $TS

Canonical tools on router-ops:
- $LIB_TARGET
- $FACTS_TARGET
- $GUARD_TARGET
- $TEST_TARGET
- $POLICY_TARGET

Mandatory contract:
- Full tree count: git ls-tree -r --full-tree --name-only.
- Independent count: non-directory entries from git archive.
- Counts must match or STEP is STOP.
- tracked_file_count is forbidden.
- Use git_tree_file_count, published_archive_file_count, changed_file_count, changed_files.
- Run router-step-machine-git-guard before changes and again before PASS.

Regression proof:
- legacy command from a root subdirectory returns 2 of 4 files;
- canonical command returns 4 from work-tree root, subdirectory and /tmp;
- git archive independently returns 4;
- unsafe sample code and ambiguous facts are rejected.

R13A correction:
- previous reported count: $R13A_REPORTED_COUNT (root subtree);
- correct Git tree count: $R13A_CORRECT_TREE_COUNT;
- changed files: $R13A_CHANGED_FILE_COUNT ($R13A_CHANGED_FILE);
- published archive files: $R13A_ARCHIVE_FILE_COUNT;
- runtime classification remains PASS.

Access and machines:
- No VM was inspected or modified in this STEP.
- Machine source facts were taken from the published R13A and VM101 Machine Git folders.
- VM101 current model remains: $VM101_MODEL_URL
EOF
cp "$REPORT_DIR/machine-git-count-contract-v1.md" "$METHODS_DIR/"
cp "$REPORT_DIR/r13a-correction.json" "$METHODS_DIR/"
cp "$REPORT_DIR/regression.log" "$METHODS_DIR/"
chmod 644 "$METHODS_DIR"/*
make_index "$METHODS_DIR" "VM101 methods Machine Git count contract $TS" methods.txt machine-git-count-contract-v1.md r13a-correction.json regression.log

cat > "$LOCAL_PLAN_DIR/plan.md" <<EOF
# Канонический локальный план VM101

Актуальность: $TS UTC  
Выполнено: 6/15  
Текущий этап: M07 — В РАБОТЕ

## Последний STEP

- $STEP
- RESULT=$PASS_RESULT
- Runtime VM100/VM101/VM121 не менялся.
- Исправлен процесс формирования Machine Git facts на router-ops.
- Полный Git tree теперь считается CWD-независимо и сверяется с git archive.
- Поле tracked_file_count запрещено.
- Опубликована формальная коррекция R13A: 21 было поддеревом /root; полный tree=44; changed files=1.

## Следующая runtime-цель

- STEP_050M07R14A
- /root/hmn/hmn-validate-current-pool.sh
- Убрать оставшуюся legacy-зависимость selector-а от table 200 с переиспользованием существующей runtime library.

## Этапы

| ID | Этап | Статус |
|---|---|---|
| M01 | Health monitor обнаруживает отказ VPN-слота | ВЫПОЛНЕНО |
| M02 | Локальная замена отказавшего VPN-туннеля | ВЫПОЛНЕНО |
| M03 | Quarantine отказавших endpoints | ВЫПОЛНЕНО |
| M04 | Глобальный repair counter после локальных замен | ВЫПОЛНЕНО |
| M05 | Порог full refresh и dry-run решения | ВЫПОЛНЕНО |
| M06 | Постоянно включить emergency commit на VM101 | ВЫПОЛНЕНО |
| M07 | Выполнить контролируемый реальный full HideMyName refresh | В РАБОТЕ |
| M08 | DEGRADED_POOL при ошибке HideMyName refresh | ОЖИДАЕТ |
| M09 | SLOT_EXHAUSTED и консолидация пользователей | ОЖИДАЕТ |
| M10 | DIRECT_EMERGENCY при нуле рабочих VPN | ОЖИДАЕТ |
| M11 | Cached-pool bootstrap через vpn1 | ОЖИДАЕТ |
| M12 | Абстрактный bootstrap strategy contract | ОЖИДАЕТ |
| M13 | Безопасный возврат Direct → VPN | ОЖИДАЕТ |
| M14 | Полная последовательная имитация отказов VM101 | ОЖИДАЕТ |
| M15 | Перезагрузочная устойчивость state machine | ОЖИДАЕТ |

## Актуальные ссылки

- STEP report: $REPORT_URL
- VM101 Machine Git: $VM101_PUBLIC_URL
- VM101 model: $VM101_MODEL_URL
- VM101 methods: $METHODS_URL
- Global project plan: $GLOBAL_PROJECT_PLAN_URL
- Access / XS Map: $XS_MAP_URL
EOF
cat > "$LOCAL_PLAN_DIR/facts.json" <<EOF
{
  "schema": "vm101-local-plan-v1",
  "generated_at_utc": "$TS",
  "completed": 6,
  "total": 15,
  "current_milestone": "M07",
  "current_status": "IN_PROGRESS",
  "latest_step": "$STEP",
  "latest_result": "$PASS_RESULT",
  "next_step": "STEP_050M07R14A",
  "next_target": "/root/hmn/hmn-validate-current-pool.sh"
}
EOF
chmod 644 "$LOCAL_PLAN_DIR"/*
make_index "$LOCAL_PLAN_DIR" "Канонический локальный план VM101 $TS" plan.md facts.json

cat > "$REPORT_DIR/methods-and-limitations.txt" <<EOF
Methods:
- immutable published R13A facts and VM101 Machine Git archive;
- source-controlled router-ops helper and guard;
- regression fixture reproducing the exact core.worktree/CWD failure;
- independent git archive count;
- active-code audit;
- optional router-ops Git commit/push when /opt/router-ops is a clean Git repository.

Limitations:
- no VM100, VM101 or VM121 live inspection;
- no machine code or runtime change;
- no refresh, rebalance, route, service or VPN action;
- historical R13A report remains immutable and is corrected by this later STEP.
EOF
chmod 644 "$REPORT_DIR/methods-and-limitations.txt"

cat > "$ROLLBACK_FILE" <<EOF
#!/usr/bin/env bash
set -Eeuo pipefail
ROUTER_OPS_ROOT='$ROUTER_OPS_ROOT'
FILES=(
  '$LIB_REL'
  '$FACTS_REL'
  '$GUARD_REL'
  '$TEST_REL'
  '$POLICY_REL'
)
for rel in "\${FILES[@]}"; do
  rm -f "\$ROUTER_OPS_ROOT/\$rel"
done
if git -C "\$ROUTER_OPS_ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
  git -C "\$ROUTER_OPS_ROOT" add -A -- "\${FILES[@]}"
  if ! git -C "\$ROUTER_OPS_ROOT" diff --cached --quiet; then
    git -C "\$ROUTER_OPS_ROOT" commit -m 'ROLLBACK_$STEP: remove Machine Git count contract tools'
    git -C "\$ROUTER_OPS_ROOT" push origin "\$(git -C "\$ROUTER_OPS_ROOT" symbolic-ref --short HEAD)"
  fi
fi
printf 'ROLLBACK_COMPLETE=true\n'
EOF
chmod 700 "$ROLLBACK_FILE"

PHASE="final-guard"
log "validating final STEP code and corrected facts before PASS"
RESULT="$PASS_RESULT"
write_facts
"$GUARD_TARGET" --step-code "$REPORT_DIR/step-code.sh" | tee "$REPORT_DIR/final-step-code-guard.log"
"$GUARD_TARGET" --facts-json "$REPORT_DIR/facts.json" | tee "$REPORT_DIR/final-facts-guard.log"

cat > "$VERIFY_FILE" <<EOF
legacy_fixture_count=$LEGACY_FIXTURE_COUNT
canonical_count_from_worktree_root=$CANONICAL_ROOT_COUNT
canonical_count_from_subdirectory=$CANONICAL_SUBDIR_COUNT
canonical_count_from_tmp=$CANONICAL_TMP_COUNT
archive_fixture_count=$ARCHIVE_FIXTURE_COUNT
cwd_independence=$CWD_INDEPENDENCE
regression_pass=$REGRESSION_PASS
bad_code_rejected=$BAD_CODE_REJECTED
good_code_accepted=$GOOD_CODE_ACCEPTED
old_ambiguous_facts_rejected=$OLD_FACTS_REJECTED
corrected_facts_accepted=$CORRECTED_FACTS_ACCEPTED
active_code_audit_pass=$ACTIVE_AUDIT_PASS
r13a_previous_reported_count=$R13A_REPORTED_COUNT
r13a_git_tree_file_count=$R13A_CORRECT_TREE_COUNT
r13a_changed_file_count=$R13A_CHANGED_FILE_COUNT
r13a_changed_file=$R13A_CHANGED_FILE
r13a_published_archive_file_count=$R13A_ARCHIVE_FILE_COUNT
router_ops_git_used=$ROUTER_OPS_GIT_USED
router_ops_git_commit=${ROUTER_OPS_GIT_COMMIT:-NONE}
router_ops_git_remote_head=${ROUTER_OPS_GIT_REMOTE_HEAD:-NONE}
vm100_changed=$VM100_CHANGED
vm101_changed=$VM101_CHANGED
vm121_changed=$VM121_CHANGED
runtime_impact=$RUNTIME_IMPACT
external_http_verify=pending
EOF
chmod 644 "$VERIFY_FILE"

PHASE="external-verify"
log "publishing and externally checking all required artifacts"
write_materials
HTTP_LOG="$REPORT_DIR/http-verify.log"
: > "$HTTP_LOG"
check_url() {
  local url="$1"
  printf 'CHECK %s\n' "$url" | tee -a "$HTTP_LOG"
  curl -fsS --retry 4 --retry-all-errors --connect-timeout 10 --max-time 40 "$url" >/dev/null
  printf 'PASS %s\n' "$url" | tee -a "$HTTP_LOG"
}
for url in \
  "$REPORT_URL" "${REPORT_URL}report.txt" "${REPORT_URL}facts.json" \
  "${REPORT_URL}step-code.sh" "${REPORT_URL}verify.txt" "${REPORT_URL}rollback.sh" \
  "${REPORT_URL}r13a-correction.json" "$LOCAL_PLAN_URL" "$GLOBAL_PROJECT_PLAN_URL" \
  "$ACCESS_MAP_URL" "$VM100_PUBLIC_URL" "$VM101_PUBLIC_URL" "$VM121_PUBLIC_URL" \
  "$VM101_MODEL_URL" "$METHODS_URL"
do
  check_url "$url"
done
EXTERNAL_VERIFY="true"
sed -i 's/external_http_verify=pending/external_http_verify=true/' "$VERIFY_FILE"

PHASE="complete"
RESULT="$PASS_RESULT"
FAILURE_REASON=""
write_materials
check_url "${REPORT_URL}report.txt"
check_url "${REPORT_URL}facts.json"
log "STEP completed"
print_final
