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

STEP="STEP_050M07R10F_FIX_EXTRACTOR_VERIFY_AND_PUBLISH"

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_TOOL_SHA="de75b69afb1b0a99615e42b4192b67ff0396fc057b841230e150c2d811bcfdde"

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}_step050m07r10f_fix_extractor_verify_and_publish"
REPORT_DIR="$PUBROOT/$REPORT_SLUG"

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

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_ERROR="NONE"

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
SELF_TEST_PASS=false
TOOL_PASS=false
SYNC_PASS=false
MODEL_SHA_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

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

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

  if [ -s "$SYNC_STDERR" ]; then
    SYNC_ERROR="$(
      tr '\r\n\t' '   ' < "$SYNC_STDERR" |
        cut -c1-1000
    )"
  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" \
    "$SYNC_ERROR" \
    "$OLD_TOOL_SHA" \
    "$NEW_TOOL_SHA" \
    "$TOOL_BACKUP" \
    "$PATCH_PASS" \
    "$SELF_TEST_PASS" \
    "$TOOL_PASS" \
    "$SYNC_PASS" \
    "$MODEL_SHA_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,
    sync_error,
    old_tool_sha,
    new_tool_sha,
    backup,
    patch_pass,
    self_test_pass,
    tool_pass,
    sync_pass,
    model_sha_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
            ),
            "exact_sync_error": (
                None
                if sync_error == "NONE"
                else sync_error
            ),
        },

        "tool_patch": {
            "old_sha256": old_tool_sha,
            "new_sha256": new_tool_sha,
            "backup": backup,
            "patch_pass":
                patch_pass == "true",
            "self_test_pass":
                self_test_pass == "true",
            "compile_pass":
                tool_pass == "true",
            "fixes": [
                "Python-version-independent safe tar extraction",
                "ignore structural rootfs ancestor directories during exact verification",
                "do not follow symlinks while generating public SHA256SUMS"
            ],
        },

        "model_sync": {
            "passed":
                sync_pass == "true",
            "public_sha256_pass":
                model_sha_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}
exact_sync_error=${SYNC_ERROR}

tool_patch:
  old_tool_sha256=${OLD_TOOL_SHA}
  new_tool_sha256=${NEW_TOOL_SHA}
  backup=${TOOL_BACKUP}
  patch_pass=${PATCH_PASS}
  self_test_pass=${SELF_TEST_PASS}
  compile_pass=${TOOL_PASS}

model_sync:
  sync_pass=${SYNC_PASS}
  public_sha256_pass=${MODEL_SHA_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="self-test.json">Self-test</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 "exact_sync_error=$SYNC_ERROR"

  echo
  echo "patch_pass=$PATCH_PASS"
  echo "self_test_pass=$SELF_TEST_PASS"
  echo "tool_pass=$TOOL_PASS"
  echo "sync_pass=$SYNC_PASS"
  echo "model_sha_pass=$MODEL_SHA_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_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",
)

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

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

            relative_parts = [
                part
                for part in pure.parts
                if part not in {"", "."}
            ]

            if not relative_parts:
                continue

            destination = rootfs.joinpath(
                *relative_parts
            )

            parent = destination.parent

            while parent != rootfs:
                if parent.is_symlink():
                    raise SyncError(
                        "archive parent is a symlink: "
                        f"{parent}"
                    )

                parent = parent.parent

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

            if member.isdir():
                if (
                    destination.exists()
                    and not destination.is_dir()
                ) or destination.is_symlink():
                    remove_path(destination)

                destination.mkdir(
                    parents=True,
                    exist_ok=True,
                )

            elif member.isfile() or member.islnk():
                if (
                    destination.exists()
                    or destination.is_symlink()
                ):
                    remove_path(destination)

                source_handle = archive.extractfile(
                    member
                )

                if source_handle is None:
                    raise SyncError(
                        "archive file has no payload: "
                        f"{member.name}"
                    )

                temporary = destination.with_name(
                    destination.name
                    + f".tmp.{os.getpid()}"
                )

                with source_handle:
                    with temporary.open("wb") as output:
                        shutil.copyfileobj(
                            source_handle,
                            output,
                        )

                os.replace(
                    temporary,
                    destination,
                )

            elif member.issym():
                if (
                    destination.exists()
                    or destination.is_symlink()
                ):
                    remove_path(destination)

                os.symlink(
                    member.linkname,
                    destination,
                )

            else:
                raise SyncError(
                    "unsupported archive member type: "
                    f"{member.name}"
                )
'''

old_verify = '''    remote_paths = set(remote_manifest)
    local_paths = set(local)

    if remote_paths != local_paths:
        missing = sorted(
            remote_paths - local_paths
        )

        extra = sorted(
            local_paths - remote_paths
        )

        raise SyncError(
            "snapshot path mismatch: "
            f"missing={missing[:20]} "
            f"extra={extra[:20]}"
        )

    failures: list[str] = []
'''

new_verify = '''    remote_paths = set(remote_manifest)
    local_paths = set(local)

    structural_parents: set[str] = set()

    for absolute in remote_paths:
        parent = PurePosixPath(absolute).parent

        while str(parent) not in {"", ".", "/"}:
            structural_parents.add(
                str(parent)
            )
            parent = parent.parent

    ignored_structural = {
        path
        for path in local_paths - remote_paths
        if (
            path in structural_parents
            and local[path]["type"] == "directory"
        )
    }

    effective_local_paths = (
        local_paths - ignored_structural
    )

    if remote_paths != effective_local_paths:
        missing = sorted(
            remote_paths - effective_local_paths
        )

        extra = sorted(
            effective_local_paths - remote_paths
        )

        raise SyncError(
            "snapshot path mismatch: "
            f"missing={missing[:20]} "
            f"extra={extra[:20]} "
            f"ignored_structural="
            f"{sorted(ignored_structural)[:20]}"
        )

    failures: list[str] = []
'''

old_sha_loop = '''        if not path.is_file() or path == checksum_path:
            continue
'''

new_sha_loop = '''        if path.is_symlink():
            continue

        if not path.is_file() or path == checksum_path:
            continue
'''

checks = {
    "extract_block_occurs_once":
        source.count(old_extract) == 1,

    "verify_block_occurs_once":
        source.count(old_verify) == 1,

    "sha_loop_occurs_once":
        source.count(old_sha_loop) == 1,
}

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

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

patched = source.replace(
    old_extract,
    new_extract,
    1,
)

patched = patched.replace(
    old_verify,
    new_verify,
    1,
)

patched = patched.replace(
    old_sha_loop,
    new_sha_loop,
    1,
)

post_checks = {
    "extractall_filter_removed":
        'filter="data"' not in patched,

    "manual_file_extraction_present":
        "shutil.copyfileobj(" in patched,

    "symlink_member_handled":
        "elif member.issym():" in patched,

    "unsafe_parent_symlink_rejected":
        "archive parent is a symlink"
        in patched,

    "structural_parent_handling_present":
        "ignored_structural"
        in patched,

    "checksum_symlink_skip_present":
        "if path.is_symlink():"
        in patched,

    "single_main":
        patched.count(
            'if __name__ == "__main__":'
        ) == 1,
}

post_failures = [
    name
    for name, passed in post_checks.items()
    if not passed
]

if post_failures:
    raise SystemExit(
        "PATCH_POSTCHECK_FAILED:"
        + ",".join(post_failures)
    )

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

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

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

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

    "fixes": [
        {
            "component":
                "tar extraction",
            "change":
                "manual validated extraction without TarFile filter parameter",
        },
        {
            "component":
                "snapshot verification",
            "change":
                "ignore synthetic structural ancestor directories",
        },
        {
            "component":
                "public checksums",
            "change":
                "skip symlinks rather than following them",
        },
    ],

    "pre_checks":
        checks,

    "post_checks":
        post_checks,

    "failures":
        [],
}

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

PATCH_RC=$?

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

chmod 700 "$TOOL_CANDIDATE"

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

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

python3 - \
  "$TOOL_CANDIDATE" \
  "$REPORT_DIR/self-test.json" <<'PY'
import hashlib
import importlib.util
import io
import json
import os
import stat
import tarfile
import tempfile
from pathlib import Path
from types import SimpleNamespace
import sys

tool_path = Path(sys.argv[1])
output_path = Path(sys.argv[2])

spec = importlib.util.spec_from_file_location(
    "router_machine_model_sync_test",
    tool_path,
)

if spec is None or spec.loader is None:
    raise SystemExit("IMPORT_SPEC_FAILED")

module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)

checks = {}

with tempfile.TemporaryDirectory() as temporary:
    work = Path(temporary)
    rootfs = work / "rootfs"
    rootfs.mkdir()

    payload = io.BytesIO()

    with tarfile.open(
        fileobj=payload,
        mode="w",
    ) as archive:
        content = b"#!/bin/sh\ntrue\n"

        file_info = tarfile.TarInfo(
            "root/hmn/test.sh"
        )
        file_info.size = len(content)
        file_info.mode = 0o755

        archive.addfile(
            file_info,
            io.BytesIO(content),
        )

        link_info = tarfile.TarInfo(
            "root/hmn/test-link"
        )
        link_info.type = tarfile.SYMTYPE
        link_info.linkname = "test.sh"

        archive.addfile(link_info)

    archive_bytes = payload.getvalue()

    original_run_remote = module.run_remote

    module.run_remote = (
        lambda *args, **kwargs:
        SimpleNamespace(
            returncode=0,
            stdout=archive_bytes,
            stderr=b"",
        )
    )

    transferred = module.download_changed_paths(
        {},
        [
            "/root/hmn/test.sh",
            "/root/hmn/test-link",
        ],
        rootfs,
    )

    checks["regular_file_extracted"] = (
        rootfs
        / "root/hmn/test.sh"
    ).read_bytes() == content

    checks["symlink_extracted"] = (
        rootfs
        / "root/hmn/test-link"
    ).is_symlink()

    checks["symlink_target_exact"] = (
        os.readlink(
            rootfs / "root/hmn/test-link"
        )
        == "test.sh"
    )

    checks["archive_bytes_reported"] = (
        transferred == len(archive_bytes)
    )

    file_path = rootfs / "root/hmn/test.sh"
    directory_path = rootfs / "root/hmn"

    os.chmod(directory_path, 0o755)
    os.chmod(file_path, 0o755)

    file_info_local = os.lstat(file_path)
    dir_info_local = os.lstat(directory_path)

    remote_manifest = {
        "/root/hmn": {
            "path": "/root/hmn",
            "type": "directory",
            "mode": format(
                stat.S_IMODE(
                    dir_info_local.st_mode
                ),
                "o",
            ),
            "uid": dir_info_local.st_uid,
            "gid": dir_info_local.st_gid,
            "size": 0,
            "mtime": int(
                dir_info_local.st_mtime
            ),
            "sha256": "-",
            "target": "-",
        },

        "/root/hmn/test.sh": {
            "path": "/root/hmn/test.sh",
            "type": "file",
            "mode": format(
                stat.S_IMODE(
                    file_info_local.st_mode
                ),
                "o",
            ),
            "uid": file_info_local.st_uid,
            "gid": file_info_local.st_gid,
            "size": file_info_local.st_size,
            "mtime": int(
                file_info_local.st_mtime
            ),
            "sha256": hashlib.sha256(
                content
            ).hexdigest(),
            "target": "-",
        },

        "/root/hmn/test-link": {
            "path": "/root/hmn/test-link",
            "type": "symlink",
            "mode": format(
                stat.S_IMODE(
                    os.lstat(
                        rootfs
                        / "root/hmn/test-link"
                    ).st_mode
                ),
                "o",
            ),
            "uid": os.lstat(
                rootfs / "root/hmn/test-link"
            ).st_uid,
            "gid": os.lstat(
                rootfs / "root/hmn/test-link"
            ).st_gid,
            "size": 0,
            "mtime": int(
                os.lstat(
                    rootfs
                    / "root/hmn/test-link"
                ).st_mtime
            ),
            "sha256": "-",
            "target": "test.sh",
        },
    }

    try:
        module.verify_exact_snapshot(
            rootfs,
            remote_manifest,
        )
        checks[
            "structural_ancestor_verification"
        ] = True
    except Exception:
        checks[
            "structural_ancestor_verification"
        ] = False

    checksum_root = work / "checksum"
    checksum_root.mkdir()

    (checksum_root / "regular.txt").write_text(
        "regular\n",
        encoding="utf-8",
    )

    os.symlink(
        "/etc/passwd",
        checksum_root / "external-link",
    )

    module.sha256_tree(checksum_root)

    checks["checksum_regular_present"] = (
        "regular.txt"
        in (
            checksum_root / "SHA256SUMS"
        ).read_text(encoding="utf-8")
    )

    checks["checksum_symlink_not_followed"] = (
        "external-link"
        not in (
            checksum_root / "SHA256SUMS"
        ).read_text(encoding="utf-8")
    )

    malicious = io.BytesIO()

    with tarfile.open(
        fileobj=malicious,
        mode="w",
    ) as archive:
        content = b"escape\n"
        info = tarfile.TarInfo("../escape")
        info.size = len(content)

        archive.addfile(
            info,
            io.BytesIO(content),
        )

    module.run_remote = (
        lambda *args, **kwargs:
        SimpleNamespace(
            returncode=0,
            stdout=malicious.getvalue(),
            stderr=b"",
        )
    )

    traversal_rejected = False

    try:
        module.download_changed_paths(
            {},
            ["/escape"],
            rootfs,
        )
    except module.SyncError:
        traversal_rejected = True

    checks["path_traversal_rejected"] = (
        traversal_rejected
    )

    checks["path_traversal_not_written"] = (
        not (work / "escape").exists()
    )

    module.run_remote = original_run_remote

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

output_path.write_text(
    json.dumps({
        "schema":
            "router-machine-model-sync-self-test-v1",
        "passed":
            not failures,
        "checks":
            checks,
        "failures":
            failures,
    }, ensure_ascii=False, indent=2) + "\n",
    encoding="utf-8",
)

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

SELF_TEST_RC=$?

if [ "$SELF_TEST_RC" -ne 0 ]; then
  stop_step "PATCHED_TOOL_SELF_TEST_FAILED"
fi

SELF_TEST_PASS=true

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_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" \
  "$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 [ ! -e "$required" ]; then
    stop_step "PUBLISHED_VM101_MODEL_INCOMPLETE"
  fi
done

(
  cd "$MODEL_PUBLIC" || exit 1
  sha256sum -c SHA256SUMS
) > "$REPORT_DIR/model-sha256-check.txt" 2>&1

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

MODEL_SHA_PASS=true
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"

cp -a \
  "$REPORT_DIR/self-test.json" \
  "$METHODS_PRIVATE/tool-self-test.json"

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

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

Tool:

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

VM101 profile:

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

Current model:

${VM101_MODEL}

Current rootfs:

${VM101_MODEL}rootfs/

## Operation

The tool performs a complete managed manifest scan and transfers only
added or changed files. Removed live files are also removed from the new
snapshot.

The private model is verified against the complete live managed manifest
before the public snapshot and current pointer are created.

## Compatibility and safety

- Changed-file archives are produced by a remote script sent to \`sh -s\`.
- Tar extraction does not depend on the Python \`filter\` argument.
- Absolute paths and \`..\` archive paths are rejected.
- Archive writes through symlink parent directories are rejected.
- Structural directories such as \`rootfs/root\` and \`rootfs/etc\` are
  permitted but are not confused with managed live files.
- Public SHA256 generation never follows symlinks.

## Manual execution

\`\`\`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":
            "manifest and delta machine-model synchronization",
        "vm101_model":
            model_url,
        "profile":
            profile,
        "tool":
            tool,
        "tool_sha256":
            tool_sha,
        "self_test":
            "tool-self-test.json",
    }, 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="tool-self-test.json">Tool self-test</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_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_R10F_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_VALIDATED_DELTA_SYNC"
NEXT_STEP="REVIEW_PUBLISHED_VM101_ROOTFS_MODEL"

publish_result
