#!/usr/bin/env bash
set +e
set +u
umask 077

STEP="STEP_050M07R10E_FIX_TRANSPORT_AND_PUBLISH_VM101_MODEL"

TOKEN="e94a0859747d7b96f29c7fdafc2d0351ba603bb0a7e9e5a4"

ROOT="/opt/router-ops"
STATE="$ROOT/state"
PUBROOT="$ROOT/public/r/$TOKEN"

PROFILE="$ROOT/etc/machine-models/vm101.json"
TOOL="$ROOT/bin/router-machine-model-sync"

TUNNEL_STATE="$STATE/current-quick-tunnel.env"
MODEL_STATE="$STATE/current-vm101-model.env"
METHODS_STATE="$STATE/current-vm101-methods.env"
WORKFLOW_STATE="$STATE/current-m07-workflow.env"

EXPECTED_OLD_TOOL_SHA="1e6d8291a793355a34373e1159e299c81c18c0b773020e6aae016584869ef0b2"

LOCAL_PLAN_SLUG="20260711-181158_local_architecture_plan_vm101_autonomous_hmn_recovery"
GLOBAL_PLAN_SLUG="20260711-123348_global_project_plan_wg_paid"
XS_MAP_SLUG="20260711-120734_xs_map_audit_repair_publish"

TS="$(date -u +%Y%m%d-%H%M%S)"

REPORT_SLUG="${TS}_step050m07r10e_fix_transport_and_publish_vm101_model"
REPORT_DIR="$PUBROOT/$REPORT_SLUG"

BACKUP_DIR="$STATE/tool-backups/router-machine-model-sync"
TOOL_BACKUP="$BACKUP_DIR/router-machine-model-sync.before-r10e.${TS}"
TOOL_CANDIDATE="/tmp/router-machine-model-sync.r10e.$$"

SYNC_RESULT="$REPORT_DIR/sync-result.json"
SYNC_STDOUT="$REPORT_DIR/sync.stdout"
SYNC_STDERR="$REPORT_DIR/sync.stderr"

METHODS_ID="${TS}_vm101_methods_machine_model_sync_v1"
METHODS_PRIVATE="$STATE/vm101-methods/snapshots/$METHODS_ID"
METHODS_PUBLIC="$PUBROOT/$METHODS_ID"

BASE=""

PREVIOUS_MODEL_ID=""
PREVIOUS_METHODS_ID=""
PREVIOUS_METHODS_PRIVATE=""

MODEL_ID=""
MODEL_PRIVATE=""
MODEL_PUBLIC=""
VM101_MODEL=""
VM101_METHODS=""

OLD_TOOL_SHA=""
NEW_TOOL_SHA=""

SYNC_MODE="UNRESOLVED"
MANIFEST_COUNT=0
ADDED_COUNT=0
CHANGED_COUNT=0
REMOVED_COUNT=0
DOWNLOADED_COUNT=0
DOWNLOADED_BYTES=0
SCAN_SECONDS="0"
TOTAL_SECONDS="0"

PATCH_PASS=false
TOOL_PASS=false
SYNC_PASS=false
METHODS_PASS=false
WORKFLOW_UPDATED=false
ALL_OK=false

FAILURE_REASON="NONE"
OPERATION_RESULT="VM101_MODEL_NOT_PUBLISHED"
NEXT_STEP="REPAIR_MACHINE_MODEL_SYNC"

mkdir -p \
  "$REPORT_DIR" \
  "$BACKUP_DIR" \
  "$STATE/vm101-methods/snapshots"

cp -a "$0" "$REPORT_DIR/step.sh"
chmod 600 "$REPORT_DIR/step.sh"

state_value() {
  local file="$1"
  local key="$2"

  sed -n "s/^${key}=//p" "$file" 2>/dev/null |
    tail -n1
}

file_sha() {
  sha256sum "$1" 2>/dev/null |
    awk '{print $1}'
}

json_value() {
  local file="$1"
  local expression="$2"

  python3 - "$file" "$expression" <<'PY'
import json
import sys

data = json.load(open(sys.argv[1], encoding="utf-8"))
value = data

for component in sys.argv[2].split("."):
    if isinstance(value, dict):
        value = value.get(component)
    else:
        value = None
        break

if value is None:
    print("")
elif isinstance(value, bool):
    print("true" if value else "false")
else:
    print(value)
PY
}

update_env_file() {
  local file="$1"
  shift

  python3 - "$file" "$@" <<'PY'
import sys
from pathlib import Path

path = Path(sys.argv[1])

updates = {}

for item in sys.argv[2:]:
    key, value = item.split("=", 1)
    updates[key] = value

if path.exists():
    lines = path.read_text(
        encoding="utf-8",
        errors="replace",
    ).splitlines()
else:
    lines = []

result = []
seen = set()

for line in lines:
    if "=" not in line:
        result.append(line)
        continue

    key, _ = line.split("=", 1)

    if key in updates:
        result.append(f"{key}={updates[key]}")
        seen.add(key)
    else:
        result.append(line)

for key, value in updates.items():
    if key not in seen:
        result.append(f"{key}={value}")

path.parent.mkdir(
    parents=True,
    exist_ok=True,
)

path.write_text(
    "\n".join(result) + "\n",
    encoding="utf-8",
)
PY
}

publish_result() {
  local decision execution
  local step_report local_plan global_plan xs_map
  local managed_rootfs profile_url tool_url
  local reference canonical report_txt facts_json

  [ -n "$BASE" ] ||
    BASE="$(state_value "$TUNNEL_STATE" QUICK_TUNNEL_URL)"

  if [ -z "$VM101_MODEL" ]; then
    if [ -n "$PREVIOUS_MODEL_ID" ]; then
      VM101_MODEL="${BASE}/r/${TOKEN}/${PREVIOUS_MODEL_ID}/"
    else
      VM101_MODEL="${BASE}/r/${TOKEN}/vm101-model/current/"
    fi
  fi

  if [ -z "$VM101_METHODS" ]; then
    if [ -n "$PREVIOUS_METHODS_ID" ]; then
      VM101_METHODS="${BASE}/r/${TOKEN}/${PREVIOUS_METHODS_ID}/"
    else
      VM101_METHODS="${BASE}/r/${TOKEN}/vm101-methods/current/"
    fi
  fi

  step_report="${BASE}/r/${TOKEN}/${REPORT_SLUG}/"
  local_plan="${BASE}/r/${TOKEN}/${LOCAL_PLAN_SLUG}/"
  global_plan="${BASE}/r/${TOKEN}/${GLOBAL_PLAN_SLUG}/"
  xs_map="${BASE}/r/${TOKEN}/${XS_MAP_SLUG}/"

  managed_rootfs="${VM101_MODEL}rootfs/"
  profile_url="${step_report}vm101-profile.json"
  tool_url="${step_report}router-machine-model-sync.py"

  reference="${BASE}/r/${TOKEN}/vm101-reference/current/"
  canonical="${BASE}/r/${TOKEN}/vm101-canonical/current/"

  report_txt="${step_report}report.txt"
  facts_json="${step_report}facts.json"

  if [ "$ALL_OK" = "true" ]; then
    decision="PASS_${STEP}"
    execution="PASS"
  else
    decision="STOP_${STEP}_${FAILURE_REASON}"
    execution="STOP"
  fi

  python3 - \
    "$REPORT_DIR/facts.json" \
    "$STEP" \
    "$decision" \
    "$execution" \
    "$OPERATION_RESULT" \
    "$FAILURE_REASON" \
    "$OLD_TOOL_SHA" \
    "$NEW_TOOL_SHA" \
    "$TOOL_BACKUP" \
    "$PATCH_PASS" \
    "$TOOL_PASS" \
    "$SYNC_PASS" \
    "$METHODS_PASS" \
    "$MODEL_ID" \
    "$MODEL_PRIVATE" \
    "$MODEL_PUBLIC" \
    "$SYNC_MODE" \
    "$MANIFEST_COUNT" \
    "$ADDED_COUNT" \
    "$CHANGED_COUNT" \
    "$REMOVED_COUNT" \
    "$DOWNLOADED_COUNT" \
    "$DOWNLOADED_BYTES" \
    "$SCAN_SECONDS" \
    "$TOTAL_SECONDS" \
    "$WORKFLOW_UPDATED" \
    "$ALL_OK" \
    "$NEXT_STEP" \
    "$step_report" \
    "$local_plan" \
    "$global_plan" \
    "$xs_map" \
    "$VM101_MODEL" \
    "$VM101_METHODS" \
    "$managed_rootfs" \
    "$profile_url" \
    "$tool_url" \
    "$reference" \
    "$canonical" <<'PY'
import json
import sys
from pathlib import Path

(
    output,
    step,
    decision,
    execution,
    operation,
    reason,
    old_tool_sha,
    new_tool_sha,
    tool_backup,
    patch_pass,
    tool_pass,
    sync_pass,
    methods_pass,
    model_id,
    model_private,
    model_public,
    sync_mode,
    manifest_count,
    added_count,
    changed_count,
    removed_count,
    downloaded_count,
    downloaded_bytes,
    scan_seconds,
    total_seconds,
    workflow_updated,
    all_ok,
    next_step,
    step_report,
    local_plan,
    global_plan,
    xs_map,
    model_url,
    methods_url,
    managed_rootfs,
    profile_url,
    tool_url,
    reference,
    canonical,
) = sys.argv[1:]

def integer(value):
    try:
        return int(value)
    except ValueError:
        return 0

def number(value):
    try:
        return float(value)
    except ValueError:
        return 0.0

Path(output).write_text(
    json.dumps({
        "schema": "router-step-facts-v1",
        "step": step,

        "assessment": {
            "decision": decision,
            "step_execution": execution,
            "operation_result": operation,
            "production_health":
                "UNCHANGED_READ_ONLY_MODEL_SYNC",
            "milestone_status":
                "M07_WORKFLOW_REPAIR",
            "all_ok": all_ok == "true",
            "failure_reason": (
                None if execution == "PASS" else reason
            ),
        },

        "transport_fix": {
            "old_tool_sha256": old_tool_sha,
            "new_tool_sha256": new_tool_sha,
            "backup_path": tool_backup,
            "patch_pass": patch_pass == "true",
            "fix":
                "send remote tar script through sh -s instead of nested sh -c",
        },

        "sync_tool": {
            "path":
                "/opt/router-ops/bin/router-machine-model-sync",
            "compile_pass": tool_pass == "true",
        },

        "model_sync": {
            "passed": sync_pass == "true",
            "model_id": model_id,
            "private_snapshot": model_private,
            "public_snapshot": model_public,
            "sync_mode": sync_mode,
            "manifest_entry_count":
                integer(manifest_count),
            "added_count":
                integer(added_count),
            "changed_count":
                integer(changed_count),
            "removed_count":
                integer(removed_count),
            "downloaded_count":
                integer(downloaded_count),
            "downloaded_archive_bytes":
                integer(downloaded_bytes),
            "remote_manifest_seconds":
                number(scan_seconds),
            "total_seconds":
                number(total_seconds),
        },

        "methods": {
            "published": methods_pass == "true",
            "url": methods_url,
        },

        "workflow_state_updated":
            workflow_updated == "true",

        "safety": {
            "vm101_contacted_read_only": True,
            "vm101_modified": False,
            "installation_performed": False,
            "refresh_ran": False,
            "rebalance_ran": False,
            "services_restarted": False,
        },

        "mandatory_links": {
            "step_report": step_report,
            "local_plan": local_plan,
            "global_plan": global_plan,
            "xs_map": xs_map,
            "vm101_model": model_url,
            "vm101_methods": methods_url,
        },

        "model_links": {
            "managed_rootfs": managed_rootfs,
            "profile": profile_url,
            "sync_tool": tool_url,
        },

        "additional_links": {
            "vm101_reference": reference,
            "vm101_canonical": canonical,
        },

        "next_step": next_step,
    }, ensure_ascii=False, indent=2) + "\n",
    encoding="utf-8",
)
PY

  cat > "$REPORT_DIR/report.txt" <<EOF
=== ${STEP} RESULT ===
step=${STEP}
decision=${decision}
step_execution=${execution}
operation_result=${OPERATION_RESULT}
production_health=UNCHANGED_READ_ONLY_MODEL_SYNC
milestone_status=M07_WORKFLOW_REPAIR
all_ok=${ALL_OK}
failure_reason=${FAILURE_REASON}

transport_fix:
  old_tool_sha256=${OLD_TOOL_SHA}
  new_tool_sha256=${NEW_TOOL_SHA}
  backup_path=${TOOL_BACKUP}
  patch_pass=${PATCH_PASS}
  transport=remote_script_over_stdin_to_sh_s

sync_tool:
  path=${TOOL}
  compile_pass=${TOOL_PASS}

model_sync:
  sync_pass=${SYNC_PASS}
  model_id=${MODEL_ID}
  private_snapshot=${MODEL_PRIVATE}
  public_snapshot=${MODEL_PUBLIC}
  sync_mode=${SYNC_MODE}
  manifest_entry_count=${MANIFEST_COUNT}
  added_count=${ADDED_COUNT}
  changed_count=${CHANGED_COUNT}
  removed_count=${REMOVED_COUNT}
  downloaded_count=${DOWNLOADED_COUNT}
  downloaded_archive_bytes=${DOWNLOADED_BYTES}
  remote_manifest_seconds=${SCAN_SECONDS}
  total_seconds=${TOTAL_SECONDS}

methods:
  methods_pass=${METHODS_PASS}
  methods_id=${METHODS_ID}

safety:
  vm101_contacted_read_only=true
  vm101_modified=false
  installation_performed=false
  refresh_ran=false
  rebalance_ran=false
  services_restarted=false

next_step=${NEXT_STEP}

STEP_REPORT=${step_report}
LOCAL_PLAN=${local_plan}
GLOBAL_PLAN=${global_plan}
XS_MAP=${xs_map}
VM101_MODEL=${VM101_MODEL}
VM101_METHODS=${VM101_METHODS}

MANAGED_ROOTFS=${managed_rootfs}
PROFILE_JSON=${profile_url}
SYNC_TOOL=${tool_url}

VM101_REFERENCE=${reference}
VM101_CANONICAL=${canonical}
REPORT_TXT=${report_txt}
FACTS_JSON=${facts_json}
EOF

  cat > "$REPORT_DIR/index.html" <<EOF
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>${STEP}</title>
</head>
<body style="font-family:system-ui;max-width:1150px;margin:40px auto">
<h1>${STEP}</h1>

<h2>Результат</h2>
<ul>
<li><a href="report.txt">report.txt</a></li>
<li><a href="facts.json">facts.json</a></li>
<li><a href="sync-result.json">Sync result</a></li>
<li><a href="sync.stdout">Sync stdout</a></li>
<li><a href="sync.stderr">Sync stderr</a></li>
<li><a href="patch-report.json">Patch report</a></li>
<li><a href="vm101-profile.json">VM101 profile</a></li>
<li><a href="router-machine-model-sync.py">Universal sync tool</a></li>
<li><a href="step.sh">step.sh</a></li>
</ul>

<h2>Рабочая модель VM101</h2>
<ul>
<li><a href="${VM101_MODEL}">Timestamped VM101 model</a></li>
<li><a href="${managed_rootfs}">Managed rootfs</a></li>
</ul>

<h2>Обязательные ссылки</h2>
<ul>
<li><a href="${step_report}">Current STEP report</a></li>
<li><a href="${local_plan}">Local M07 plan</a></li>
<li><a href="${global_plan}">Global project plan</a></li>
<li><a href="${xs_map}">XS Map</a></li>
<li><a href="${VM101_MODEL}">Timestamped VM101 model</a></li>
<li><a href="${VM101_METHODS}">Timestamped VM101 methods</a></li>
</ul>

<h2>Дополнительно</h2>
<ul>
<li><a href="${reference}">Legacy VM101 reference</a></li>
<li><a href="${canonical}">Legacy VM101 canonical</a></li>
</ul>
</body>
</html>
EOF

  find "$REPORT_DIR" \
    -type f \
    ! -name SHA256SUMS \
    -print0 |
  sort -z |
  xargs -0 sha256sum \
    > "$REPORT_DIR/SHA256SUMS"

  chmod -R a+rX "$REPORT_DIR"

  echo
  echo "decision=$decision"
  echo "operation_result=$OPERATION_RESULT"
  echo "all_ok=$ALL_OK"
  echo "failure_reason=$FAILURE_REASON"

  echo
  echo "patch_pass=$PATCH_PASS"
  echo "tool_pass=$TOOL_PASS"
  echo "sync_pass=$SYNC_PASS"
  echo "methods_pass=$METHODS_PASS"
  echo "workflow_updated=$WORKFLOW_UPDATED"

  echo
  echo "sync_mode=$SYNC_MODE"
  echo "manifest_entry_count=$MANIFEST_COUNT"
  echo "added_count=$ADDED_COUNT"
  echo "changed_count=$CHANGED_COUNT"
  echo "removed_count=$REMOVED_COUNT"
  echo "downloaded_count=$DOWNLOADED_COUNT"
  echo "downloaded_archive_bytes=$DOWNLOADED_BYTES"
  echo "remote_manifest_seconds=$SCAN_SECONDS"
  echo "total_seconds=$TOTAL_SECONDS"

  echo
  echo "vm101_modified=false"
  echo "installation_performed=false"

  echo
  echo "STEP_REPORT=$step_report"
  echo "LOCAL_PLAN=$local_plan"
  echo "GLOBAL_PLAN=$global_plan"
  echo "XS_MAP=$xs_map"
  echo "VM101_MODEL=$VM101_MODEL"
  echo "VM101_METHODS=$VM101_METHODS"

  echo
  echo "MANAGED_ROOTFS=$managed_rootfs"
  echo "PROFILE_JSON=$profile_url"
  echo "SYNC_TOOL=$tool_url"

  echo
  echo "VM101_REFERENCE=$reference"
  echo "VM101_CANONICAL=$canonical"
  echo "REPORT_TXT=$report_txt"
  echo "FACTS_JSON=$facts_json"

  true
}

stop_step() {
  FAILURE_REASON="$1"
  ALL_OK=false
  publish_result
  exit 0
}

if [ "$(id -un)" != "ops" ]; then
  stop_step "WRONG_USER"
fi

for required in \
  "$TUNNEL_STATE" \
  "$PROFILE" \
  "$TOOL"
do
  if [ ! -s "$required" ]; then
    stop_step "REQUIRED_PROFILE_TOOL_OR_STATE_MISSING"
  fi
done

BASE="$(state_value "$TUNNEL_STATE" QUICK_TUNNEL_URL)"

if [ -z "$BASE" ]; then
  stop_step "QUICK_TUNNEL_URL_MISSING"
fi

PUBLIC_SCOPE="${BASE}/r/${TOKEN}"

PREVIOUS_MODEL_ID="$(
  state_value "$MODEL_STATE" VM101_MODEL_ID
)"

PREVIOUS_METHODS_ID="$(
  state_value "$METHODS_STATE" VM101_METHODS_ID
)"

PREVIOUS_METHODS_PRIVATE="$(
  state_value "$METHODS_STATE" VM101_METHODS_PRIVATE
)"

OLD_TOOL_SHA="$(file_sha "$TOOL")"

if [ "$OLD_TOOL_SHA" != "$EXPECTED_OLD_TOOL_SHA" ]; then
  stop_step "TOOL_SHA_LOCK_MISMATCH"
fi

cp -a "$TOOL" "$TOOL_BACKUP"

python3 - \
  "$TOOL" \
  "$TOOL_CANDIDATE" \
  "$REPORT_DIR/patch-report.json" <<'PY'
import hashlib
import json
import sys
from pathlib import Path

source_path = Path(sys.argv[1])
candidate_path = Path(sys.argv[2])
report_path = Path(sys.argv[3])

source = source_path.read_text(
    encoding="utf-8",
    errors="strict",
)

start_marker = "def download_changed_paths("
end_marker = "def apply_manifest_modes("

start = source.find(start_marker)

if start < 0:
    raise SystemExit(
        "PATCH_STOP:DOWNLOAD_FUNCTION_START_MISSING"
    )

end = source.find(
    end_marker,
    start,
)

if end < 0:
    raise SystemExit(
        "PATCH_STOP:DOWNLOAD_FUNCTION_END_MISSING"
    )

old_function = source[start:end]

required_old_fragments = {
    "nested_sh_c":
        '"sh -c \'"' in old_function,

    "tar_help_branch":
        'tar --help 2>&1' in old_function,

    "stdin_path_list":
        'input_data=input_data' in old_function,

    "remote_tar_error":
        '"remote tar failed: "' in old_function,
}

old_failures = [
    name
    for name, passed in required_old_fragments.items()
    if not passed
]

if old_failures:
    raise SystemExit(
        "PATCH_STOP:OLD_FUNCTION_CONTRACT:"
        + ",".join(old_failures)
    )

new_function = r'''def download_changed_paths(
    profile: dict[str, Any],
    changed_paths: list[str],
    rootfs: Path,
) -> int:
    transferable = [
        path.lstrip("/")
        for path in changed_paths
    ]

    if not transferable:
        return 0

    script_lines = [
        "set -eu",
        "set --",
    ]

    for relative in transferable:
        archive_path = "./" + relative

        script_lines.append(
            'set -- "$@" '
            + shell_quote(archive_path)
        )

    script_lines.append(
        'exec tar -C / -cf - "$@"'
    )

    remote_script = (
        "\n".join(script_lines) + "\n"
    ).encode("utf-8")

    process = run_remote(
        profile,
        "sh -s",
        input_data=remote_script,
        timeout=600,
    )

    if process.returncode != 0:
        raise SyncError(
            "remote tar failed: "
            + process.stderr.decode(
                "utf-8",
                errors="replace",
            )
        )

    archive_bytes = process.stdout

    with tarfile.open(
        fileobj=io.BytesIO(archive_bytes),
        mode="r:",
    ) as archive:
        members = archive.getmembers()

        for member in members:
            pure = PurePosixPath(member.name)

            if pure.is_absolute():
                raise SyncError(
                    f"absolute archive member: "
                    f"{member.name}"
                )

            if ".." in pure.parts:
                raise SyncError(
                    f"unsafe archive member: "
                    f"{member.name}"
                )

        archive.extractall(
            path=rootfs,
            members=members,
            filter="data",
        )

    return len(archive_bytes)


'''

patched = (
    source[:start]
    + new_function
    + source[end:]
)

checks = {
    "one_download_function":
        patched.count(
            "def download_changed_paths("
        ) == 1,

    "one_apply_function":
        patched.count(
            "def apply_manifest_modes("
        ) == 1,

    "nested_sh_c_removed":
        '"sh -c \'"' not in (
            patched[
                patched.find(
                    "def download_changed_paths("
                ):
                patched.find(
                    "def apply_manifest_modes("
                )
            ]
        ),

    "remote_sh_s_present":
        'profile,\n        "sh -s",' in patched,

    "remote_script_stdin":
        "input_data=remote_script"
        in patched,

    "tar_exec_present":
        'exec tar -C / -cf - "$@"'
        in patched,

    "safe_archive_validation_preserved":
        "unsafe archive member"
        in patched,
}

failures = [
    name
    for name, passed in checks.items()
    if not passed
]

if failures:
    raise SystemExit(
        "PATCH_STOP:"
        + ",".join(failures)
    )

candidate_path.write_text(
    patched,
    encoding="utf-8",
)

report = {
    "schema":
        "router-machine-model-sync-transport-patch-v1",

    "source_sha256":
        hashlib.sha256(
            source.encode("utf-8")
        ).hexdigest(),

    "candidate_sha256":
        hashlib.sha256(
            patched.encode("utf-8")
        ).hexdigest(),

    "function_replaced":
        "download_changed_paths",

    "reason":
        "nested sh -c command did not survive two SSH hops",

    "new_transport":
        "remote shell script sent over stdin to sh -s",

    "old_function_sha256":
        hashlib.sha256(
            old_function.encode("utf-8")
        ).hexdigest(),

    "checks":
        checks,

    "failures":
        failures,
}

report_path.write_text(
    json.dumps(
        report,
        ensure_ascii=False,
        indent=2,
    ) + "\n",
    encoding="utf-8",
)

print(
    "candidate_sha256="
    + report["candidate_sha256"]
)
PY

PATCH_RC=$?

if [ "$PATCH_RC" -ne 0 ]; then
  stop_step "SYNC_TOOL_TRANSPORT_PATCH_FAILED"
fi

chmod 700 "$TOOL_CANDIDATE"

python3 -m py_compile "$TOOL_CANDIDATE" \
  > "$REPORT_DIR/tool-candidate-compile.stdout" \
  2> "$REPORT_DIR/tool-candidate-compile.stderr"

if [ "$?" -ne 0 ]; then
  stop_step "PATCHED_TOOL_COMPILE_FAILED"
fi

"$TOOL_CANDIDATE" --help \
  > "$REPORT_DIR/tool-candidate-help.txt" \
  2> "$REPORT_DIR/tool-candidate-help.stderr"

if [ "$?" -ne 0 ]; then
  stop_step "PATCHED_TOOL_HELP_FAILED"
fi

mv "$TOOL_CANDIDATE" "$TOOL"
chmod 700 "$TOOL"

NEW_TOOL_SHA="$(file_sha "$TOOL")"

EXPECTED_NEW_TOOL_SHA="$(
  json_value \
    "$REPORT_DIR/patch-report.json" \
    candidate_sha256
)"

if [ "$NEW_TOOL_SHA" != "$EXPECTED_NEW_TOOL_SHA" ]; then
  cp -a "$TOOL_BACKUP" "$TOOL"
  chmod 700 "$TOOL"

  stop_step "PATCHED_TOOL_FINAL_SHA_MISMATCH"
fi

PATCH_PASS=true
TOOL_PASS=true

cp -a "$PROFILE" \
  "$REPORT_DIR/vm101-profile.json"

cp -a "$TOOL" \
  "$REPORT_DIR/router-machine-model-sync.py"

chmod 644 \
  "$REPORT_DIR/vm101-profile.json" \
  "$REPORT_DIR/router-machine-model-sync.py"

"$TOOL" \
  --config "$PROFILE" \
  --state-root "$STATE" \
  --public-root "$PUBROOT" \
  --public-base "$PUBLIC_SCOPE" \
  --timestamp "$TS" \
  --result "$SYNC_RESULT" \
  > "$SYNC_STDOUT" \
  2> "$SYNC_STDERR"

SYNC_RC=$?

if [ "$SYNC_RC" -ne 0 ]; then
  stop_step "VM101_MACHINE_MODEL_SYNC_FAILED"
fi

if [ ! -s "$SYNC_RESULT" ]; then
  stop_step "SYNC_RESULT_MISSING"
fi

MODEL_ID="$(
  json_value "$SYNC_RESULT" model_id
)"

VM101_MODEL="$(
  json_value "$SYNC_RESULT" public_url
)"

MODEL_PRIVATE="$(
  json_value "$SYNC_RESULT" private_snapshot
)"

MODEL_PUBLIC="$(
  json_value "$SYNC_RESULT" public_snapshot
)"

SYNC_MODE="$(
  json_value "$SYNC_RESULT" sync_mode
)"

MANIFEST_COUNT="$(
  json_value "$SYNC_RESULT" manifest.entry_count
)"

ADDED_COUNT="$(
  json_value "$SYNC_RESULT" manifest.added_count
)"

CHANGED_COUNT="$(
  json_value "$SYNC_RESULT" manifest.changed_count
)"

REMOVED_COUNT="$(
  json_value "$SYNC_RESULT" manifest.removed_count
)"

DOWNLOADED_COUNT="$(
  json_value "$SYNC_RESULT" transfer.downloaded_path_count
)"

DOWNLOADED_BYTES="$(
  json_value "$SYNC_RESULT" transfer.downloaded_archive_bytes
)"

SCAN_SECONDS="$(
  json_value "$SYNC_RESULT" timing.remote_manifest_seconds
)"

TOTAL_SECONDS="$(
  json_value "$SYNC_RESULT" timing.total_seconds
)"

for required in \
  "$MODEL_PUBLIC/index.html" \
  "$MODEL_PUBLIC/rootfs" \
  "$MODEL_PUBLIC/manifest.json" \
  "$MODEL_PUBLIC/publication-map.json" \
  "$MODEL_PUBLIC/sync-result.json" \
  "$MODEL_PUBLIC/profile.json" \
  "$MODEL_PUBLIC/SHA256SUMS"
do
  if [ ! -e "$required" ]; then
    stop_step "PUBLISHED_VM101_MODEL_INCOMPLETE"
  fi
done

for required in \
  "$MODEL_PUBLIC/rootfs/root/hmn/hmn-refresh-pool-safe.sh" \
  "$MODEL_PUBLIC/rootfs/root/hmn/hmn-code-test.sh" \
  "$MODEL_PUBLIC/rootfs/usr/local/lib/router-egress-vm101-runtime.sh"
do
  if [ ! -s "$required" ]; then
    stop_step "REQUIRED_PUBLIC_MODEL_FILE_MISSING"
  fi
done

sh -n \
  "$MODEL_PUBLIC/rootfs/root/hmn/hmn-refresh-pool-safe.sh" ||
  stop_step "PUBLIC_REFRESH_POOL_SAFE_SYNTAX_FAILED"

sh -n \
  "$MODEL_PUBLIC/rootfs/root/hmn/hmn-code-test.sh" ||
  stop_step "PUBLIC_CODE_TEST_SYNTAX_FAILED"

sh -n \
  "$MODEL_PUBLIC/rootfs/usr/local/lib/router-egress-vm101-runtime.sh" ||
  stop_step "PUBLIC_RUNTIME_LIBRARY_SYNTAX_FAILED"

SYNC_PASS=true

rm -rf \
  "$METHODS_PRIVATE" \
  "$METHODS_PUBLIC"

if [ -n "$PREVIOUS_METHODS_PRIVATE" ] &&
   [ -d "$PREVIOUS_METHODS_PRIVATE" ]
then
  cp -a \
    "$PREVIOUS_METHODS_PRIVATE" \
    "$METHODS_PRIVATE"
else
  mkdir -p "$METHODS_PRIVATE"
fi

mkdir -p \
  "$METHODS_PRIVATE/tools" \
  "$METHODS_PRIVATE/profiles"

rm -f \
  "$METHODS_PRIVATE/index.html" \
  "$METHODS_PRIVATE/SHA256SUMS" \
  "$METHODS_PRIVATE/file-list.txt"

cp -a \
  "$TOOL" \
  "$METHODS_PRIVATE/tools/router-machine-model-sync.py"

cp -a \
  "$PROFILE" \
  "$METHODS_PRIVATE/profiles/vm101.json"

chmod 644 \
  "$METHODS_PRIVATE/tools/router-machine-model-sync.py" \
  "$METHODS_PRIVATE/profiles/vm101.json"

cat > "$METHODS_PRIVATE/machine-model-sync.md" <<EOF
# Universal machine-model synchronization

Tool:

\`\`\`
${TOOL}
\`\`\`

VM101 profile:

\`\`\`
${PROFILE}
\`\`\`

Current published VM101 model:

${VM101_MODEL}

Current managed rootfs:

${VM101_MODEL}rootfs/

## Transport

Both manifest scanning and changed-file transfer use a remote shell script
sent through standard input to:

\`\`\`
sh -s
\`\`\`

The tool does not send nested \`sh -c\` programs through the two SSH hops.

## Synchronization model

1. Scan the complete configured managed scope.
2. Calculate path, type, mode, SHA256 and symlink target.
3. Compare with the previous exact private snapshot.
4. Clone the previous snapshot.
5. Download only added and changed files.
6. Remove files absent from the live machine.
7. Verify the private snapshot against the live manifest.
8. Create a sanitized public snapshot.
9. Preserve previous timestamped snapshots.
10. Move \`current\` only after successful verification.

## Manual command

\`\`\`bash
${TOOL} \\
  --config ${PROFILE} \\
  --state-root ${STATE} \\
  --public-root ${PUBROOT} \\
  --public-base ${PUBLIC_SCOPE} \\
  --timestamp "\$(date -u +%Y%m%d-%H%M%S)" \\
  --result /tmp/vm101-model-sync-result.json
\`\`\`
EOF

python3 - \
  "$METHODS_PRIVATE/methods-manifest.json" \
  "$METHODS_ID" \
  "$TS" \
  "$VM101_MODEL" \
  "$PROFILE" \
  "$TOOL" \
  "$NEW_TOOL_SHA" <<'PY'
import json
import sys
from pathlib import Path

(
    output,
    methods_id,
    generated,
    model_url,
    profile,
    tool,
    tool_sha,
) = sys.argv[1:]

Path(output).write_text(
    json.dumps({
        "schema":
            "vm101-methods-snapshot-v1",

        "methods_id":
            methods_id,

        "generated_at_utc":
            generated,

        "capability":
            "universal manifest and delta machine-model synchronization",

        "transport":
            "remote script over stdin to sh -s",

        "vm101_model":
            model_url,

        "profile":
            profile,

        "tool":
            tool,

        "tool_sha256":
            tool_sha,
    }, ensure_ascii=False, indent=2) + "\n",
    encoding="utf-8",
)
PY

find "$METHODS_PRIVATE" \
  -type f \
  ! -name file-list.txt \
  ! -name SHA256SUMS \
  -printf '%P\n' |
sort > "$METHODS_PRIVATE/file-list.txt"

cat > "$METHODS_PRIVATE/index.html" <<EOF
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>${METHODS_ID}</title>
</head>
<body style="font-family:system-ui;max-width:1050px;margin:40px auto">
<h1>VM101 methods</h1>
<p>Snapshot: <code>${METHODS_ID}</code></p>
<ul>
<li><a href="machine-model-sync.md">Machine-model synchronization</a></li>
<li><a href="tools/router-machine-model-sync.py">Universal sync tool</a></li>
<li><a href="profiles/vm101.json">VM101 profile</a></li>
<li><a href="methods-manifest.json">Methods manifest</a></li>
<li><a href="file-list.txt">File list</a></li>
<li><a href="SHA256SUMS">SHA256SUMS</a></li>
</ul>
</body>
</html>
EOF

(
  cd "$METHODS_PRIVATE" || exit 1

  find . \
    -type f \
    ! -name SHA256SUMS \
    -print0 |
  sort -z |
  xargs -0 sha256sum \
    > SHA256SUMS

  sha256sum -c SHA256SUMS
) > "$REPORT_DIR/methods-sha256-check.txt" 2>&1

if [ "$?" -ne 0 ]; then
  stop_step "METHODS_SHA256_VALIDATION_FAILED"
fi

cp -a \
  "$METHODS_PRIVATE" \
  "$METHODS_PUBLIC"

chmod -R a+rX "$METHODS_PUBLIC"

mkdir -p \
  "$STATE/vm101-methods" \
  "$PUBROOT/vm101-methods"

if [ -e "$STATE/vm101-methods/current" ] ||
   [ -L "$STATE/vm101-methods/current" ]
then
  if [ ! -L "$STATE/vm101-methods/current" ]; then
    stop_step "PRIVATE_METHODS_CURRENT_NOT_SYMLINK"
  fi
fi

if [ -e "$PUBROOT/vm101-methods/current" ] ||
   [ -L "$PUBROOT/vm101-methods/current" ]
then
  if [ ! -L "$PUBROOT/vm101-methods/current" ]; then
    stop_step "PUBLIC_METHODS_CURRENT_NOT_SYMLINK"
  fi
fi

ln -sfn \
  "snapshots/$METHODS_ID" \
  "$STATE/vm101-methods/current"

ln -sfn \
  "../$METHODS_ID" \
  "$PUBROOT/vm101-methods/current"

VM101_METHODS="${PUBLIC_SCOPE}/${METHODS_ID}/"

cat > "$METHODS_STATE" <<EOF
VM101_METHODS_ID=${METHODS_ID}
VM101_METHODS_PRIVATE=${METHODS_PRIVATE}
VM101_METHODS_PUBLIC=${METHODS_PUBLIC}
VM101_METHODS_URL=${VM101_METHODS}
VM101_METHODS_GENERATED_AT_UTC=${TS}
EOF

chmod 600 "$METHODS_STATE"

METHODS_PASS=true

if [ -s "$WORKFLOW_STATE" ]; then
  update_env_file \
    "$WORKFLOW_STATE" \
    "M07_VM101_MODEL_URL=${VM101_MODEL}" \
    "M07_VM101_METHODS_URL=${VM101_METHODS}" \
    "M07_VM101_MODEL_PROFILE=${PROFILE}" \
    "M07_VM101_MODEL_SYNC_TOOL=${TOOL}" \
    "M07_VM101_MODEL_SYNC_TOOL_SHA256=${NEW_TOOL_SHA}" \
    "M07_VM101_MODEL_FORMAT=rootfs_v1" \
    "M07_VM101_MODEL_SYNC_STATUS=MANIFEST_DELTA_SYNC_PASS" \
    "M07_R10E_REPORT=${PUBLIC_SCOPE}/${REPORT_SLUG}/" \
    "M07_NEXT_STEP=REVIEW_PUBLISHED_VM101_ROOTFS_MODEL"

  if [ "$?" -ne 0 ]; then
    stop_step "WORKFLOW_STATE_UPDATE_FAILED"
  fi

  chmod 600 "$WORKFLOW_STATE"
  WORKFLOW_UPDATED=true
fi

ALL_OK=true
OPERATION_RESULT="VM101_ROOTFS_MODEL_PUBLISHED_BY_MANIFEST_DELTA_SYNC"
NEXT_STEP="REVIEW_PUBLISHED_VM101_ROOTFS_MODEL"

publish_result
