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

[ "$(id -un)" = "ops" ] || {
  echo "STOP: запускать на router-ops пользователем ops"
  exit 1
}

STEP="STEP_050M07R03_BUILD_CANONICAL_WORKSPACE_AND_SOURCE_LOCKS"
PASS_DECISION="PASS_${STEP}"

TOKEN="e94a0859747d7b96f29c7fdafc2d0351ba603bb0a7e9e5a4"
PUBLIC_BASE="https://helena-background-beam-harry.trycloudflare.com/r/${TOKEN}"

ROOT="/opt/router-ops"
STATE_ROOT="${ROOT}/state"
PUBROOT="${ROOT}/public/r/${TOKEN}"

REFERENCE_CURRENT="${PUBROOT}/vm101-reference/current"
REFERENCE_STATE="${STATE_ROOT}/current-vm101-reference.env"

CANONICAL_PRIVATE_ROOT="${STATE_ROOT}/canonical/vm101"
CANONICAL_PUBLIC_ROOT="${PUBROOT}/vm101-canonical"

BUILD_TS="$(date -u +%Y%m%d-%H%M%S)"
CANONICAL_ID="${BUILD_TS}_vm101_m07_canonical_v1"

WORKSPACE="${CANONICAL_PRIVATE_ROOT}/snapshots/${CANONICAL_ID}"
SOURCE_DIR="${WORKSPACE}/source"
CANDIDATE_DIR="${WORKSPACE}/candidate"
CONTRACT_DIR="${WORKSPACE}/contracts"
TEST_DIR="${WORKSPACE}/tests"
DOC_DIR="${WORKSPACE}/docs"

PUBLIC_SNAPSHOT="${CANONICAL_PUBLIC_ROOT}/snapshots/${CANONICAL_ID}"
PUBLIC_CURRENT="${CANONICAL_PUBLIC_ROOT}/current"

REPORT_SLUG="${BUILD_TS}_step050m07r03_build_canonical_workspace_and_source_locks"
REPORT_DIR="${PUBROOT}/${REPORT_SLUG}"

TRYCF_REPORT="${PUBLIC_BASE}/${REPORT_SLUG}/"
REPORT_TXT="${TRYCF_REPORT}report.txt"
FACTS_JSON="${TRYCF_REPORT}facts.json"

ARCHITECTURE_PLAN="${PUBLIC_BASE}/20260711-181158_local_architecture_plan_vm101_autonomous_hmn_recovery/"
XS_MAP="${PUBLIC_BASE}/20260711-120734_xs_map_audit_repair_publish/"
GLOBAL_PROJECT_PLAN="${PUBLIC_BASE}/20260711-123348_global_project_plan_wg_paid/"
VM101_REFERENCE="${PUBLIC_BASE}/vm101-reference/current/"
VM101_CANONICAL="${PUBLIC_BASE}/vm101-canonical/current/"
VM101_CANONICAL_SNAPSHOT="${PUBLIC_BASE}/vm101-canonical/snapshots/${CANONICAL_ID}/"

CURRENT_STAGE="initialization"
LAST_SUCCESS="step_saved"

REFERENCE_ID="UNRESOLVED"
SOURCE_REFERENCE_ID="UNRESOLVED"
PRIVATE_RAW_SNAPSHOT="UNRESOLVED"

TARGET_COUNT=0
SOURCE_COUNT=0
SENSITIVE_TARGETS=0
SYNTAX_FAILURES=0
CONTRACT_WARNINGS=0

VM101_CONTACTED=false
VM101_MODIFIED=false
PUBLIC_SNAPSHOT_PUBLISHED=false
PUBLIC_CURRENT_UPDATED=false

mkdir -p \
  "$REPORT_DIR" \
  "$SOURCE_DIR" \
  "$CANDIDATE_DIR" \
  "$CONTRACT_DIR" \
  "$TEST_DIR" \
  "$DOC_DIR"

chmod -R go-rwx "$CANONICAL_PRIVATE_ROOT" 2>/dev/null || true

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

PROGRESS_LOG="$REPORT_DIR/progress.log"
: > "$PROGRESS_LOG"

stage() {
  CURRENT_STAGE="$1"

  echo
  echo ">>> [$1] $2" | tee -a "$PROGRESS_LOG"

  date -u '+    utc=%Y-%m-%dT%H:%M:%SZ' |
    tee -a "$PROGRESS_LOG"
}

mark_success() {
  LAST_SUCCESS="$1"
  echo "last_success=$LAST_SUCCESS" >> "$PROGRESS_LOG"
}

print_links() {
  echo
  echo "TRYCF_REPORT=$TRYCF_REPORT"
  echo "REPORT_TXT=$REPORT_TXT"
  echo "FACTS_JSON=$FACTS_JSON"
  echo "ARCHITECTURE_PLAN=$ARCHITECTURE_PLAN"
  echo "XS_MAP=$XS_MAP"
  echo "GLOBAL_PROJECT_PLAN=$GLOBAL_PROJECT_PLAN"
  echo "VM101_REFERENCE=$VM101_REFERENCE"
  echo "VM101_CANONICAL=$VM101_CANONICAL"
}

create_report_index() {
  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:1100px;margin:40px auto;padding:0 20px">
<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="assessment.json">assessment.json</a></li>
<li><a href="public-canonical-manifest.json">public-canonical-manifest.json</a></li>
<li><a href="source-analysis.json">source-analysis.json</a></li>
<li><a href="change-plan.md">change-plan.md</a></li>
<li><a href="syntax-checks.txt">syntax-checks.txt</a></li>
</ul>

<h2>Постоянные ссылки</h2>
<ul>
<li><a href="${VM101_REFERENCE}">VM101 Reference</a></li>
<li><a href="${VM101_CANONICAL}">VM101 Canonical current</a></li>
<li><a href="${VM101_CANONICAL_SNAPSHOT}">Immutable canonical snapshot</a></li>
</ul>

<ul>
<li><a href="step.sh">step.sh</a></li>
</ul>
</body>
</html>
EOF
}

write_stop() {
  local reason="$1"
  local rc="$2"
  local line="$3"

  cat > "$REPORT_DIR/report.txt" <<EOF
=== ${STEP} RESULT ===
step=${STEP}
decision=STOP_${STEP}_${reason}
step_execution=STOP
operation_result=CANONICAL_WORKSPACE_NOT_PUBLISHED
production_health=UNCHANGED
milestone_status=M07_IN_PROGRESS
all_ok=false

failure:
  rc=${rc}
  line=${line}
  stage=${CURRENT_STAGE}
  last_success=${LAST_SUCCESS}

reference:
  reference_id=${REFERENCE_ID}
  source_reference_id=${SOURCE_REFERENCE_ID}
  private_raw_snapshot=${PRIVATE_RAW_SNAPSHOT}

counts:
  target_count=${TARGET_COUNT}
  source_count=${SOURCE_COUNT}
  sensitive_targets=${SENSITIVE_TARGETS}
  syntax_failures=${SYNTAX_FAILURES}
  contract_warnings=${CONTRACT_WARNINGS}

safety:
  local_only=true
  vm101_contacted=${VM101_CONTACTED}
  vm101_modified=${VM101_MODIFIED}
  network_changed=false
  services_changed=false
  state_changed=false
  refresh_ran=false
  rebalance_ran=false

TRYCF_REPORT=${TRYCF_REPORT}
REPORT_TXT=${REPORT_TXT}
FACTS_JSON=${FACTS_JSON}
ARCHITECTURE_PLAN=${ARCHITECTURE_PLAN}
XS_MAP=${XS_MAP}
GLOBAL_PROJECT_PLAN=${GLOBAL_PROJECT_PLAN}
VM101_REFERENCE=${VM101_REFERENCE}
VM101_CANONICAL=${VM101_CANONICAL}
EOF

  python3 - \
    "$STEP" \
    "$reason" \
    "$rc" \
    "$line" \
    "$CURRENT_STAGE" \
    "$LAST_SUCCESS" \
    "$REFERENCE_ID" \
    "$SOURCE_REFERENCE_ID" \
    "$PRIVATE_RAW_SNAPSHOT" \
    "$TARGET_COUNT" \
    "$SOURCE_COUNT" \
    "$SENSITIVE_TARGETS" \
    "$SYNTAX_FAILURES" \
    "$CONTRACT_WARNINGS" \
    "$TRYCF_REPORT" \
    "$REPORT_TXT" \
    "$FACTS_JSON" \
    "$ARCHITECTURE_PLAN" \
    "$XS_MAP" \
    "$GLOBAL_PROJECT_PLAN" \
    "$VM101_REFERENCE" \
    "$VM101_CANONICAL" \
    > "$REPORT_DIR/facts.json" <<'PY'
import json
import sys

(
    step,
    reason,
    rc,
    line,
    stage,
    last_success,
    reference_id,
    source_reference_id,
    private_raw_snapshot,
    target_count,
    source_count,
    sensitive_targets,
    syntax_failures,
    contract_warnings,
    report,
    report_txt,
    facts_json,
    architecture,
    xs_map,
    global_plan,
    vm101_reference,
    vm101_canonical,
) = sys.argv[1:]

print(json.dumps({
    "schema": "router-step-facts-v1",
    "step": step,
    "assessment": {
        "decision": f"STOP_{step}_{reason}",
        "step_execution": "STOP",
        "operation_result":
            "CANONICAL_WORKSPACE_NOT_PUBLISHED",
        "production_health": "UNCHANGED",
        "milestone_status": "M07_IN_PROGRESS",
        "all_ok": False,
        "failure": {
            "reason": reason,
            "rc": int(rc),
            "line": int(line),
            "stage": stage,
            "last_success": last_success,
        },
    },
    "reference": {
        "reference_id": reference_id,
        "source_reference_id": source_reference_id,
        "private_raw_snapshot": private_raw_snapshot,
    },
    "counts": {
        "target_count": int(target_count),
        "source_count": int(source_count),
        "sensitive_targets": int(sensitive_targets),
        "syntax_failures": int(syntax_failures),
        "contract_warnings": int(contract_warnings),
    },
    "safety": {
        "local_only": True,
        "vm101_contacted": False,
        "vm101_modified": False,
        "network_changed": False,
        "services_changed": False,
        "state_changed": False,
        "refresh_ran": False,
        "rebalance_ran": False,
    },
    "publish": {
        "trycf_report": report,
        "report_txt": report_txt,
        "facts_json": facts_json,
        "architecture_plan": architecture,
        "xs_map": xs_map,
        "global_project_plan": global_plan,
        "vm101_reference": vm101_reference,
        "vm101_canonical": vm101_canonical,
    },
}, ensure_ascii=False, indent=2))
PY

  create_report_index

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

  print_links
  exit "$rc"
}

fatal() {
  local reason="$1"
  local rc="${2:-1}"
  local line="${3:-$LINENO}"

  trap - ERR
  write_stop "$reason" "$rc" "$line"
}

trap 'rc=$?; fatal "UNEXPECTED_ERROR" "$rc" "$LINENO"' ERR

stage "01/08" "Проверяю валидированный VM101 Reference"

for required in \
  "$REFERENCE_CURRENT/manifest.json" \
  "$REFERENCE_CURRENT/machine-profile.json" \
  "$REFERENCE_CURRENT/runtime/current.json" \
  "$REFERENCE_CURRENT/secret-scan.json" \
  "$REFERENCE_CURRENT/reference-validation.json" \
  "$REFERENCE_CURRENT/tools/validate-reference.py" \
  "$REFERENCE_CURRENT/SHA256SUMS" \
  "$REFERENCE_STATE"
do
  [ -s "$required" ] || {
    echo "MISSING_REFERENCE_COMPONENT=$required"
    fatal "REFERENCE_COMPONENT_MISSING" 2 "$LINENO"
  }
done

python3 \
  "$REFERENCE_CURRENT/tools/validate-reference.py" \
  "$REFERENCE_CURRENT" \
  > "$REPORT_DIR/reference-validator.json" ||
  fatal "REFERENCE_VALIDATION_FAILED" 3 "$LINENO"

(
  cd "$REFERENCE_CURRENT"
  sha256sum -c SHA256SUMS
) > "$REPORT_DIR/reference-sha256-check.txt" 2>&1 ||
  fatal "REFERENCE_SHA256_FAILED" 4 "$LINENO"

REFERENCE_ID="$(
  python3 - "$REFERENCE_CURRENT/manifest.json" <<'PY'
import json
import sys

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

SOURCE_REFERENCE_ID="$(
  python3 - "$REFERENCE_CURRENT/manifest.json" <<'PY'
import json
import sys

data = json.load(open(sys.argv[1], encoding="utf-8"))
print(data.get("source_reference_id", data["reference_id"]))
PY
)"

PRIVATE_RAW_SNAPSHOT="${STATE_ROOT}/private-mirrors/vm101/snapshots/${SOURCE_REFERENCE_ID}"

[ -d "$PRIVATE_RAW_SNAPSHOT/managed-files" ] || {
  echo "PRIVATE_RAW_SNAPSHOT_MISSING=$PRIVATE_RAW_SNAPSHOT"
  fatal "PRIVATE_RAW_SNAPSHOT_MISSING" 5 "$LINENO"
}

echo "reference_id=$REFERENCE_ID" |
  tee -a "$PROGRESS_LOG"

echo "source_reference_id=$SOURCE_REFERENCE_ID" |
  tee -a "$PROGRESS_LOG"

echo "private_raw_snapshot=$PRIVATE_RAW_SNAPSHOT" |
  tee -a "$PROGRESS_LOG"

mark_success "reference_and_private_raw_verified"

stage "02/08" "Фиксирую набор M07 canonical targets"

cat > "$WORKSPACE/targets.tsv" <<'EOF'
label	source_path	purpose
refresh_pool_safe	/root/hmn/hmn-refresh-pool-safe.sh	atomic refresh backup preflight and rollback manifest
code_test	/root/hmn/hmn-code-test.sh	healthy bootstrap interface detection
download_all_awg	/root/hmn/hmn-download-all-awg.sh	healthy bootstrap interface detection
refresh_awg	/root/hmn/hmn-refresh-awg.sh	healthy bootstrap interface detection
validate_current_pool	/root/hmn/hmn-validate-current-pool.sh	healthy bootstrap interface detection
planner	/usr/local/sbin/router-egress-hmn-plan-top5.sh	runtime endpoint source of truth
rebalance_apply	/usr/local/sbin/router-egress-hmn-rebalance-top5-apply.sh	runtime source apply retry and nonzero failure exit
emergency_runner	/usr/local/sbin/router-egress-emergency-refresh.sh	validate rebalance JSON contract
slots_apply	/usr/local/sbin/router-egress-slots-apply.sh	table 200 optional contract
EOF

TARGET_COUNT="$(
  tail -n +2 "$WORKSPACE/targets.tsv" |
    grep -c . ||
  true
)"

[ "$TARGET_COUNT" -eq 9 ] ||
  fatal "TARGET_SET_INVALID" 6 "$LINENO"

mark_success "target_set_locked"

stage "03/08" "Копирую точные private sources и candidate baseline"

printf 'label\tsource_path\tsource_sha256\tsize_bytes\tsensitive\n' \
  > "$CONTRACT_DIR/source-locks.tsv"

while IFS=$'\t' read -r label source_path purpose; do
  [ "$label" = "label" ] && continue

  relative="${source_path#/}"
  private_file="${PRIVATE_RAW_SNAPSHOT}/managed-files/${relative}"

  [ -s "$private_file" ] || {
    echo "PRIVATE_SOURCE_MISSING=$private_file"
    fatal "PRIVATE_SOURCE_MISSING" 7 "$LINENO"
  }

  source_destination="${SOURCE_DIR}/${relative}"
  candidate_destination="${CANDIDATE_DIR}/${relative}"

  mkdir -p \
    "$(dirname "$source_destination")" \
    "$(dirname "$candidate_destination")"

  cp -a "$private_file" "$source_destination"
  cp -a "$private_file" "$candidate_destination"

  chmod 600 \
    "$source_destination" \
    "$candidate_destination"

  source_hash="$(
    sha256sum "$source_destination" |
      cut -d' ' -f1
  )"

  size="$(
    wc -c < "$source_destination" |
      tr -d '[:space:]'
  )"

  sensitive="$(
    python3 - \
      "$REFERENCE_CURRENT/manifest.json" \
      "$source_path" <<'PY'
import json
import sys

manifest_path, source_path = sys.argv[1:]

data = json.load(open(manifest_path, encoding="utf-8"))

match = next(
    (
        item
        for item in data["managed_files"]
        if item["source_path"] == source_path
    ),
    None,
)

if match is None:
    raise SystemExit(
        f"source absent from reference manifest: {source_path}"
    )

print(
    "true"
    if int(match.get("redactions", 0)) > 0
    else "false"
)
PY
  )"

  if [ "$sensitive" = "true" ]; then
    SENSITIVE_TARGETS=$((SENSITIVE_TARGETS + 1))
  fi

  printf '%s\t%s\t%s\t%s\t%s\n' \
    "$label" \
    "$source_path" \
    "$source_hash" \
    "$size" \
    "$sensitive" \
    >> "$CONTRACT_DIR/source-locks.tsv"

  SOURCE_COUNT=$((SOURCE_COUNT + 1))
done < "$WORKSPACE/targets.tsv"

[ "$SOURCE_COUNT" -eq "$TARGET_COUNT" ] ||
  fatal "SOURCE_COPY_INCOMPLETE" 8 "$LINENO"

mark_success "private_sources_and_candidates_created"

stage "04/08" "Создаю общую BusyBox-compatible runtime library"

mkdir -p "$CANDIDATE_DIR/usr/local/lib"

cat > "$CANDIDATE_DIR/usr/local/lib/router-egress-vm101-runtime.sh" <<'LIB'
#!/bin/sh

# Canonical VM101 BusyBox-compatible runtime helpers.
# Functions are read-only unless explicitly stated otherwise.

vm101_runtime_cli() {
  if command -v amneziawg >/dev/null 2>&1; then
    command -v amneziawg
    return 0
  fi

  return 1
}

vm101_runtime_peer_line() {
  iface="$1"

  cli="$(
    vm101_runtime_cli ||
    true
  )"

  [ -n "$cli" ] || return 1

  "$cli" show "$iface" dump 2>/dev/null |
    sed -n '2p'
}

vm101_runtime_endpoint() {
  iface="$1"

  line="$(
    vm101_runtime_peer_line "$iface" ||
    true
  )"

  [ -n "$line" ] || return 1

  endpoint="$(
    printf '%s\n' "$line" |
      cut -f3
  )"

  case "$endpoint" in
    ""|"(none)")
      return 1
      ;;
  esac

  printf '%s\n' "$endpoint"
}

vm101_strict_iface() {
  iface="$1"
  attempts="${2:-3}"
  wait_seconds="${3:-1}"
  attempt=1

  while [ "$attempt" -le "$attempts" ]; do
    if ping \
      -I "$iface" \
      -c 1 \
      -W 3 \
      1.1.1.1 \
      >/dev/null 2>&1
    then
      return 0
    fi

    attempt=$((attempt + 1))

    if [ "$attempt" -le "$attempts" ]; then
      sleep "$wait_seconds"
    fi
  done

  return 1
}

vm101_healthy_bootstrap_iface() {
  candidate="$(
    ip -4 route show table 200 2>/dev/null |
      sed -n \
        's/^default dev \(vpn[1-5]\)\([[:space:]].*\)\{0,1\}$/\1/p' |
      head -n1
  )"

  if [ -n "$candidate" ] &&
     vm101_strict_iface "$candidate" 1 0
  then
    printf '%s\n' "$candidate"
    return 0
  fi

  for candidate in vpn1 vpn2 vpn3 vpn4 vpn5; do
    if ip link show dev "$candidate" >/dev/null 2>&1 &&
       vm101_strict_iface "$candidate" 1 0
    then
      printf '%s\n' "$candidate"
      return 0
    fi
  done

  return 1
}

vm101_routes_201_205_ok() {
  for table in 201 202 203 204 205; do
    ip route show table "$table" 2>/dev/null |
      grep -q '^default ' ||
      return 1
  done

  return 0
}

vm101_table200_default_present() {
  ip route show table 200 2>/dev/null |
    grep -q '^default '
}

vm101_storage_kb() {
  path="$1"

  df -Pk "$path" 2>/dev/null |
    awk 'NR == 2 {
      print "total_kb=" $2
      print "used_kb=" $3
      print "available_kb=" $4
    }'
}

vm101_require_free_kb() {
  path="$1"
  required_kb="$2"

  available="$(
    df -Pk "$path" 2>/dev/null |
      awk 'NR == 2 {print $4}'
  )"

  case "$available:$required_kb" in
    *[!0-9:]*|'')
      return 2
      ;;
  esac

  [ "$available" -ge "$required_kb" ]
}

vm101_file_sha256() {
  path="$1"

  sha256sum "$path" 2>/dev/null |
    sed 's/[[:space:]].*$//'
}
LIB

chmod 700 \
  "$CANDIDATE_DIR/usr/local/lib/router-egress-vm101-runtime.sh"

sh -n \
  "$CANDIDATE_DIR/usr/local/lib/router-egress-vm101-runtime.sh"

mark_success "canonical_runtime_library_created"

stage "05/08" "Анализирую source contracts без публикации содержимого"

python3 - \
  "$WORKSPACE/targets.tsv" \
  "$SOURCE_DIR" \
  "$CONTRACT_DIR/source-locks.tsv" \
  "$CONTRACT_DIR/source-analysis.json" \
  "$CONTRACT_DIR/requirements.json" <<'PY'
import hashlib
import json
import re
import sys
from pathlib import Path

(
    targets_path,
    source_root,
    locks_path,
    analysis_output,
    requirements_output,
) = sys.argv[1:]

source_root = Path(source_root)

targets = []

for number, line in enumerate(
    Path(targets_path).read_text(
        encoding="utf-8"
    ).splitlines()
):
    if number == 0:
        continue

    label, source_path, purpose = line.split("\t", 2)

    targets.append({
        "label": label,
        "source_path": source_path,
        "purpose": purpose,
    })

locks = {}

for number, line in enumerate(
    Path(locks_path).read_text(
        encoding="utf-8"
    ).splitlines()
):
    if number == 0:
        continue

    (
        label,
        source_path,
        source_sha256,
        size_bytes,
        sensitive,
    ) = line.split("\t")

    locks[label] = {
        "source_path": source_path,
        "source_sha256": source_sha256,
        "size_bytes": int(size_bytes),
        "sensitive": sensitive == "true",
    }

tokens = {
    "table_200": re.compile(
        r"\btable\s+200\b|table[[:space:]]*200",
        re.IGNORECASE,
    ),
    "hmn_endpoint": re.compile(
        r"hmn_endpoint",
        re.IGNORECASE,
    ),
    "amneziawg": re.compile(
        r"\bamneziawg\b",
        re.IGNORECASE,
    ),
    "wg_show": re.compile(
        r"\bwg\s+show\b",
        re.IGNORECASE,
    ),
    "commit_failed": re.compile(
        r"commit_failed",
        re.IGNORECASE,
    ),
    "apply_ok": re.compile(
        r"apply_ok",
        re.IGNORECASE,
    ),
    "rebalance_rc": re.compile(
        r"rebalance_rc",
        re.IGNORECASE,
    ),
    "strict_ping": re.compile(
        r"strict_ping",
        re.IGNORECASE,
    ),
    "root_backup_path": re.compile(
        r"/root/hmn/backups",
        re.IGNORECASE,
    ),
    "tmp_backup_path": re.compile(
        r"/tmp/",
        re.IGNORECASE,
    ),
    "active_interface_error": re.compile(
        r"active VPN interface",
        re.IGNORECASE,
    ),
}

function_pattern = re.compile(
    r"^\s*([A-Za-z_][A-Za-z0-9_]*)\s*\(\)\s*\{"
)

assignment_pattern = re.compile(
    r"^\s*([A-Z][A-Z0-9_]*)="
)

analysis = {
    "schema": "vm101-canonical-source-analysis-v1",
    "targets": [],
    "warnings": [],
}

for target in targets:
    label = target["label"]
    source_path = target["source_path"]
    relative = source_path.lstrip("/")
    path = source_root / relative

    text = path.read_text(
        encoding="utf-8",
        errors="strict",
    )

    lines = text.splitlines()

    functions = []
    assignments = []
    token_lines = {
        name: []
        for name in tokens
    }

    for line_number, line in enumerate(lines, start=1):
        function_match = function_pattern.match(line)

        if function_match:
            functions.append({
                "name": function_match.group(1),
                "line": line_number,
            })

        assignment_match = assignment_pattern.match(line)

        if assignment_match:
            assignments.append({
                "name": assignment_match.group(1),
                "line": line_number,
            })

        for name, pattern in tokens.items():
            if pattern.search(line):
                token_lines[name].append(line_number)

    item = {
        "label": label,
        "source_path": source_path,
        "purpose": target["purpose"],
        "sha256": hashlib.sha256(
            text.encode("utf-8")
        ).hexdigest(),
        "size_bytes": len(text.encode("utf-8")),
        "line_count": len(lines),
        "shebang": lines[0] if lines else "",
        "sensitive": locks[label]["sensitive"],
        "functions": functions,
        "uppercase_assignments": assignments,
        "token_lines": token_lines,
    }

    analysis["targets"].append(item)

    if not item["shebang"].startswith("#!"):
        analysis["warnings"].append({
            "label": label,
            "warning": "missing_shebang",
        })

    if "table_200" in token_lines and token_lines["table_200"]:
        if label in {
            "code_test",
            "download_all_awg",
            "refresh_awg",
            "validate_current_pool",
            "slots_apply",
        }:
            analysis["warnings"].append({
                "label": label,
                "warning": "table_200_contract_present",
                "lines": token_lines["table_200"],
            })

    if (
        label in {"planner", "rebalance_apply"}
        and token_lines["hmn_endpoint"]
    ):
        analysis["warnings"].append({
            "label": label,
            "warning": "uci_hmn_endpoint_used",
            "lines": token_lines["hmn_endpoint"],
        })

requirements = {
    "schema": "vm101-m07-canonical-requirements-v1",
    "source_reference": {
        "immutable_source_locks": True,
        "exact_private_sources": True,
        "regex_patch_of_unknown_live_file": False,
    },
    "runtime": {
        "authoritative_cli": "/usr/bin/amneziawg",
        "endpoint_source":
            "amneziawg show IFACE dump peer line",
        "bootstrap_interface":
            "first strict healthy vpn1-vpn5; "
            "table 200 candidate optional",
        "route_tables":
            [201, 202, 203, 204, 205],
        "table_200_required": False,
    },
    "refresh": {
        "storage_preflight_required": True,
        "backup_manifest_required": True,
        "rollback_manifest_validation_required": True,
        "large_root_backup_forbidden": True,
    },
    "planner": {
        "current_endpoint_from_runtime": True,
        "uci_hmn_endpoint_is_fallback_only": True,
    },
    "apply": {
        "strict_retry_required": True,
        "commit_failed_exit_nonzero": True,
        "refuse_commit_exit_nonzero": True,
        "json_and_shell_exit_must_agree": True,
    },
    "runner": {
        "shell_rc_validation_required": True,
        "json_decision_validation_required": True,
        "json_apply_ok_validation_required": True,
    },
    "installation": {
        "whole_file_candidate_required": True,
        "source_hash_match_required": True,
        "syntax_check_required": True,
        "unified_diff_required": True,
        "surgical_backup_required": True,
        "atomic_replace_required": True,
        "automatic_rollback_required": True,
    },
}

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

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

cp -a \
  "$CONTRACT_DIR/source-analysis.json" \
  "$REPORT_DIR/source-analysis.json"

CONTRACT_WARNINGS="$(
  python3 - "$CONTRACT_DIR/source-analysis.json" <<'PY'
import json
import sys

data = json.load(open(sys.argv[1], encoding="utf-8"))
print(len(data["warnings"]))
PY
)"

echo "contract_warnings=$CONTRACT_WARNINGS" |
  tee -a "$PROGRESS_LOG"

mark_success "source_contracts_analyzed"

stage "06/08" "Проверяю candidate syntax и создаю private manifests"

: > "$TEST_DIR/syntax-checks.txt"

while IFS= read -r file; do
  relative="${file#${CANDIDATE_DIR}/}"

  if sh -n "$file" \
    >> "$TEST_DIR/syntax-checks.txt" \
    2>&1
  then
    echo "PASS $relative" \
      >> "$TEST_DIR/syntax-checks.txt"
  else
    echo "FAIL $relative" \
      >> "$TEST_DIR/syntax-checks.txt"

    SYNTAX_FAILURES=$((SYNTAX_FAILURES + 1))
  fi
done < <(
  find "$CANDIDATE_DIR" \
    -type f |
    sort
)

cp -a \
  "$TEST_DIR/syntax-checks.txt" \
  "$REPORT_DIR/syntax-checks.txt"

[ "$SYNTAX_FAILURES" -eq 0 ] ||
  fatal "CANDIDATE_SYNTAX_FAILED" 9 "$LINENO"

python3 - \
  "$WORKSPACE" \
  "$REFERENCE_ID" \
  "$SOURCE_REFERENCE_ID" \
  "$CANONICAL_ID" \
  "$CONTRACT_DIR/source-locks.tsv" \
  > "$WORKSPACE/canonical-manifest-private.json" <<'PY'
import hashlib
import json
import sys
from pathlib import Path

(
    workspace_arg,
    reference_id,
    source_reference_id,
    canonical_id,
    locks_path,
) = sys.argv[1:]

workspace = Path(workspace_arg)

locks = []

for number, line in enumerate(
    Path(locks_path).read_text(
        encoding="utf-8"
    ).splitlines()
):
    if number == 0:
        continue

    (
        label,
        source_path,
        source_sha256,
        size_bytes,
        sensitive,
    ) = line.split("\t")

    relative = source_path.lstrip("/")
    candidate = workspace / "candidate" / relative

    candidate_hash = hashlib.sha256(
        candidate.read_bytes()
    ).hexdigest()

    locks.append({
        "label": label,
        "source_path": source_path,
        "source_sha256": source_sha256,
        "candidate_sha256": candidate_hash,
        "source_size_bytes": int(size_bytes),
        "sensitive": sensitive == "true",
        "candidate_state": "unchanged_baseline",
    })

helper = (
    workspace
    / "candidate"
    / "usr/local/lib/router-egress-vm101-runtime.sh"
)

manifest = {
    "schema": "vm101-canonical-private-manifest-v1",
    "canonical_id": canonical_id,
    "reference_id": reference_id,
    "source_reference_id": source_reference_id,
    "candidate_status":
        "BASELINE_COPIED_NEW_RUNTIME_LIBRARY_ADDED",
    "targets": locks,
    "new_files": [{
        "destination":
            "/usr/local/lib/router-egress-vm101-runtime.sh",
        "sha256":
            hashlib.sha256(helper.read_bytes()).hexdigest(),
        "size_bytes": helper.stat().st_size,
        "sensitive": False,
    }],
    "vm101_contacted": False,
    "vm101_modified": False,
}

print(json.dumps(
    manifest,
    ensure_ascii=False,
    indent=2,
))
PY

(
  cd "$WORKSPACE"

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

mark_success "private_canonical_manifest_created"

stage "07/08" "Формирую безопасную public canonical summary"

PUBLIC_STAGE="${WORKSPACE}/public-summary"

rm -rf "$PUBLIC_STAGE"

mkdir -p \
  "$PUBLIC_STAGE/contracts" \
  "$PUBLIC_STAGE/docs" \
  "$PUBLIC_STAGE/tests"

python3 - \
  "$WORKSPACE/canonical-manifest-private.json" \
  "$PUBLIC_STAGE/canonical-manifest.json" <<'PY'
import json
import sys
from pathlib import Path

source_path, destination_path = sys.argv[1:]

private = json.load(
    open(source_path, encoding="utf-8")
)

public = {
    "schema": "vm101-canonical-public-manifest-v1",
    "canonical_id": private["canonical_id"],
    "reference_id": private["reference_id"],
    "source_reference_id":
        private["source_reference_id"],
    "candidate_status":
        private["candidate_status"],
    "targets": [],
    "new_files": private["new_files"],
    "safety": {
        "private_source_contents_published": False,
        "sensitive_source_hashes_published": False,
        "vm101_contacted": False,
        "vm101_modified": False,
    },
}

for item in private["targets"]:
    public["targets"].append({
        "label": item["label"],
        "source_path": item["source_path"],
        "source_sha256": (
            "PRIVATE_ONLY"
            if item["sensitive"]
            else item["source_sha256"]
        ),
        "candidate_sha256": (
            "PRIVATE_ONLY"
            if item["sensitive"]
            else item["candidate_sha256"]
        ),
        "source_size_bytes":
            item["source_size_bytes"],
        "sensitive": item["sensitive"],
        "candidate_state":
            item["candidate_state"],
    })

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

cp -a \
  "$CONTRACT_DIR/source-analysis.json" \
  "$PUBLIC_STAGE/contracts/source-analysis.json"

cp -a \
  "$CONTRACT_DIR/requirements.json" \
  "$PUBLIC_STAGE/contracts/requirements.json"

cp -a \
  "$TEST_DIR/syntax-checks.txt" \
  "$PUBLIC_STAGE/tests/syntax-checks.txt"

cat > "$PUBLIC_STAGE/docs/change-plan.md" <<'EOF'
# VM101 M07 canonical change plan

The canonical workspace is based on exact private source copies from
the VM101 raw mirror.

No VM101 connection or production change occurred during this step.

## Current candidate state

The nine managed candidate files are exact copies of their locked
sources. One new common helper library has been added:

    /usr/local/lib/router-egress-vm101-runtime.sh

## Planned canonical replacements

1. Refresh-safe wrapper:
   storage preflight, surgical backup manifest and validated rollback.

2. HMN bootstrap scripts:
   select an actually healthy vpn1-vpn5 interface; table 200 is only
   an optional preferred candidate.

3. Planner:
   read current endpoints from AmneziaWG runtime.

4. Rebalance apply:
   use runtime current values, retry strict health and return nonzero
   for commit failure.

5. Emergency runner:
   require both a successful shell status and valid JSON fields.

6. Slots apply:
   tables 201-205 are authoritative; table 200 is optional.

## Installation policy

The next step will create complete replacement files in the private
canonical workspace. It will not install them.

A later installation step will require:

- live source hashes equal the private source locks;
- syntax checks;
- unified diffs;
- surgical backups;
- atomic replacement;
- automatic rollback;
- post-change strict 5/5 validation.
EOF

cp -a \
  "$PUBLIC_STAGE/docs/change-plan.md" \
  "$REPORT_DIR/change-plan.md"

cat > "$PUBLIC_STAGE/index.html" <<EOF
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>VM101 Canonical ${CANONICAL_ID}</title>
</head>
<body style="font-family:system-ui;max-width:1100px;margin:40px auto;padding:0 20px">
<h1>VM101 Canonical</h1>

<p>Canonical ID: <code>${CANONICAL_ID}</code></p>
<p>Reference ID: <code>${REFERENCE_ID}</code></p>
<p>Source reference ID: <code>${SOURCE_REFERENCE_ID}</code></p>

<ul>
<li><a href="canonical-manifest.json">canonical-manifest.json</a></li>
<li><a href="contracts/source-analysis.json">source-analysis.json</a></li>
<li><a href="contracts/requirements.json">requirements.json</a></li>
<li><a href="docs/change-plan.md">change-plan.md</a></li>
<li><a href="tests/syntax-checks.txt">syntax-checks.txt</a></li>
<li><a href="SHA256SUMS">SHA256SUMS</a></li>
</ul>

<p>Private source contents and sensitive source hashes are not published.</p>
</body>
</html>
EOF

(
  cd "$PUBLIC_STAGE"

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

  sha256sum -c SHA256SUMS
) > "$REPORT_DIR/public-summary-sha256.txt" 2>&1 ||
  fatal "PUBLIC_SUMMARY_SHA256_FAILED" 10 "$LINENO"

cp -a \
  "$PUBLIC_STAGE/canonical-manifest.json" \
  "$REPORT_DIR/public-canonical-manifest.json"

cp -a \
  "$PUBLIC_STAGE/contracts/source-analysis.json" \
  "$REPORT_DIR/source-analysis.json"

mark_success "safe_public_summary_created"

stage "08/08" "Публикую canonical snapshot и current"

[ ! -e "$PUBLIC_SNAPSHOT" ] ||
  fatal "CANONICAL_SNAPSHOT_ALREADY_EXISTS" 11 "$LINENO"

PUBLIC_TMP="${PUBLIC_SNAPSHOT}.new.$$"
CURRENT_NEW="${CANONICAL_PUBLIC_ROOT}/current.new.$$"
CURRENT_OLD="${CANONICAL_PUBLIC_ROOT}/current.old.$$"

mkdir -p "$CANONICAL_PUBLIC_ROOT/snapshots"

rm -rf \
  "$PUBLIC_TMP" \
  "$CURRENT_NEW" \
  "$CURRENT_OLD"

cp -a "$PUBLIC_STAGE" "$PUBLIC_TMP"
chmod -R a+rX "$PUBLIC_TMP"

mv "$PUBLIC_TMP" "$PUBLIC_SNAPSHOT"
PUBLIC_SNAPSHOT_PUBLISHED=true

cp -a "$PUBLIC_SNAPSHOT" "$CURRENT_NEW"

if [ -e "$PUBLIC_CURRENT" ]; then
  mv "$PUBLIC_CURRENT" "$CURRENT_OLD"
fi

mv "$CURRENT_NEW" "$PUBLIC_CURRENT"
rm -rf "$CURRENT_OLD"

PUBLIC_CURRENT_UPDATED=true

rm -f "${CANONICAL_PRIVATE_ROOT}/current"

ln -s \
  "snapshots/${CANONICAL_ID}" \
  "${CANONICAL_PRIVATE_ROOT}/current"

cat > "$STATE_ROOT/current-vm101-canonical.env" <<EOF
VM101_CANONICAL_ID=${CANONICAL_ID}
VM101_CANONICAL=${VM101_CANONICAL}
VM101_CANONICAL_SNAPSHOT=${VM101_CANONICAL_SNAPSHOT}
VM101_CANONICAL_PRIVATE_WORKSPACE=${WORKSPACE}
VM101_CANONICAL_REFERENCE_ID=${REFERENCE_ID}
VM101_CANONICAL_SOURCE_REFERENCE_ID=${SOURCE_REFERENCE_ID}
VM101_CANONICAL_GENERATED_AT_UTC=${BUILD_TS}
EOF

chmod 600 \
  "$STATE_ROOT/current-vm101-canonical.env"

python3 - \
  "$STATE_ROOT/current-project-links.env" \
  "$VM101_REFERENCE" \
  "$VM101_CANONICAL" <<'PY'
import sys
from pathlib import Path

path = Path(sys.argv[1])
reference = sys.argv[2]
canonical = sys.argv[3]

lines = []

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

lines = [
    line
    for line in lines
    if not line.startswith("VM101_REFERENCE=")
    and not line.startswith("VM101_CANONICAL=")
]

lines.extend([
    f"VM101_REFERENCE={reference}",
    f"VM101_CANONICAL={canonical}",
])

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

chmod 600 \
  "$STATE_ROOT/current-project-links.env"

cat > "$ROOT/bin/vm101-canonical-status" <<'STATUS'
#!/usr/bin/env bash
set -Eeuo pipefail

STATE="/opt/router-ops/state/current-vm101-canonical.env"

[ -s "$STATE" ] || {
  echo "VM101 canonical state is unavailable"
  exit 1
}

# shellcheck disable=SC1090
. "$STATE"

echo "VM101_CANONICAL_ID=$VM101_CANONICAL_ID"
echo "VM101_CANONICAL=$VM101_CANONICAL"
echo "VM101_CANONICAL_SNAPSHOT=$VM101_CANONICAL_SNAPSHOT"
echo "VM101_CANONICAL_PRIVATE_WORKSPACE=$VM101_CANONICAL_PRIVATE_WORKSPACE"
echo "VM101_CANONICAL_REFERENCE_ID=$VM101_CANONICAL_REFERENCE_ID"
echo "VM101_CANONICAL_SOURCE_REFERENCE_ID=$VM101_CANONICAL_SOURCE_REFERENCE_ID"
STATUS

chmod 755 "$ROOT/bin/vm101-canonical-status"

PRIVATE_MANIFEST_SHA="$(
  sha256sum "$WORKSPACE/canonical-manifest-private.json" |
    cut -d' ' -f1
)"

PUBLIC_MANIFEST_SHA="$(
  sha256sum "$PUBLIC_CURRENT/canonical-manifest.json" |
    cut -d' ' -f1
)"

python3 - \
  "$STEP" \
  "$CANONICAL_ID" \
  "$REFERENCE_ID" \
  "$SOURCE_REFERENCE_ID" \
  "$PRIVATE_RAW_SNAPSHOT" \
  "$WORKSPACE" \
  "$TARGET_COUNT" \
  "$SOURCE_COUNT" \
  "$SENSITIVE_TARGETS" \
  "$SYNTAX_FAILURES" \
  "$CONTRACT_WARNINGS" \
  "$PRIVATE_MANIFEST_SHA" \
  "$PUBLIC_MANIFEST_SHA" \
  "$VM101_REFERENCE" \
  "$VM101_CANONICAL" \
  "$VM101_CANONICAL_SNAPSHOT" \
  > "$REPORT_DIR/assessment.json" <<'PY'
import json
import sys

(
    step,
    canonical_id,
    reference_id,
    source_reference_id,
    private_raw_snapshot,
    workspace,
    target_count,
    source_count,
    sensitive_targets,
    syntax_failures,
    contract_warnings,
    private_manifest_sha,
    public_manifest_sha,
    reference_url,
    canonical_url,
    canonical_snapshot_url,
) = sys.argv[1:]

print(json.dumps({
    "schema": "vm101-canonical-assessment-v1",
    "decision": f"PASS_{step}",
    "step_execution": "PASS",
    "operation_result":
        "CANONICAL_WORKSPACE_AND_SOURCE_LOCKS_CREATED",
    "production_health": "UNCHANGED",
    "milestone_status": "M07_IN_PROGRESS",
    "all_ok": True,
    "canonical": {
        "canonical_id": canonical_id,
        "reference_id": reference_id,
        "source_reference_id": source_reference_id,
        "private_raw_snapshot": private_raw_snapshot,
        "private_workspace": workspace,
        "private_manifest_sha256":
            private_manifest_sha,
        "public_manifest_sha256":
            public_manifest_sha,
        "current_url": canonical_url,
        "snapshot_url":
            canonical_snapshot_url,
    },
    "counts": {
        "target_count": int(target_count),
        "source_count": int(source_count),
        "sensitive_targets":
            int(sensitive_targets),
        "syntax_failures":
            int(syntax_failures),
        "contract_warnings":
            int(contract_warnings),
    },
    "state": {
        "candidate_managed_files":
            "UNCHANGED_BASELINE",
        "new_runtime_library":
            "/usr/local/lib/router-egress-vm101-runtime.sh",
        "source_locks_created": True,
        "source_contents_public": False,
    },
    "safety": {
        "local_only": True,
        "vm101_contacted": False,
        "vm101_modified": False,
        "network_changed": False,
        "services_changed": False,
        "state_changed": False,
        "refresh_ran": False,
        "rebalance_ran": False,
    },
    "next_step":
        "WRITE_COMPLETE_CANONICAL_REPLACEMENT_FILES",
    "links": {
        "vm101_reference": reference_url,
        "vm101_canonical": canonical_url,
    },
}, ensure_ascii=False, indent=2))
PY

cat > "$REPORT_DIR/report.txt" <<EOF
=== ${STEP} RESULT ===
step=${STEP}
decision=${PASS_DECISION}
step_execution=PASS
operation_result=CANONICAL_WORKSPACE_AND_SOURCE_LOCKS_CREATED
production_health=UNCHANGED
milestone_status=M07_IN_PROGRESS
all_ok=true

canonical:
  canonical_id=${CANONICAL_ID}
  reference_id=${REFERENCE_ID}
  source_reference_id=${SOURCE_REFERENCE_ID}
  private_raw_snapshot=${PRIVATE_RAW_SNAPSHOT}
  private_workspace=${WORKSPACE}
  public_current=${VM101_CANONICAL}
  public_snapshot=${VM101_CANONICAL_SNAPSHOT}
  private_manifest_sha256=${PRIVATE_MANIFEST_SHA}
  public_manifest_sha256=${PUBLIC_MANIFEST_SHA}

contents:
  target_count=${TARGET_COUNT}
  source_count=${SOURCE_COUNT}
  sensitive_targets=${SENSITIVE_TARGETS}
  syntax_failures=${SYNTAX_FAILURES}
  contract_warnings=${CONTRACT_WARNINGS}
  source_locks_created=true
  private_exact_sources=true
  candidate_baseline_created=true
  canonical_runtime_library_created=true
  public_source_contents=false
  public_sensitive_hashes=false

candidate_state:
  managed_files=UNCHANGED_BASELINE
  new_file=/usr/local/lib/router-egress-vm101-runtime.sh
  installed_on_vm101=false

safety:
  local_only=true
  vm101_contacted=false
  vm101_modified=false
  network_changed=false
  services_changed=false
  state_changed=false
  refresh_ran=false
  rebalance_ran=false

plan:
  current_milestone=M07
  milestone_completed=false

next_step:
  WRITE_COMPLETE_CANONICAL_REPLACEMENT_FILES

TRYCF_REPORT=${TRYCF_REPORT}
REPORT_TXT=${REPORT_TXT}
FACTS_JSON=${FACTS_JSON}
ARCHITECTURE_PLAN=${ARCHITECTURE_PLAN}
XS_MAP=${XS_MAP}
GLOBAL_PROJECT_PLAN=${GLOBAL_PROJECT_PLAN}
VM101_REFERENCE=${VM101_REFERENCE}
VM101_CANONICAL=${VM101_CANONICAL}
EOF

python3 - \
  "$REPORT_DIR/assessment.json" \
  "$STEP" \
  "$BUILD_TS" \
  "$TRYCF_REPORT" \
  "$REPORT_TXT" \
  "$FACTS_JSON" \
  "$ARCHITECTURE_PLAN" \
  "$XS_MAP" \
  "$GLOBAL_PROJECT_PLAN" \
  "$VM101_REFERENCE" \
  "$VM101_CANONICAL" \
  > "$REPORT_DIR/facts.json" <<'PY'
import json
import sys

(
    assessment_path,
    step,
    generated_at,
    report,
    report_txt,
    facts_json,
    architecture,
    xs_map,
    global_plan,
    vm101_reference,
    vm101_canonical,
) = sys.argv[1:]

assessment = json.load(
    open(assessment_path, encoding="utf-8")
)

print(json.dumps({
    "schema": "router-step-facts-v1",
    "step": step,
    "generated_at_utc": generated_at,
    "assessment": assessment,
    "safety": assessment["safety"],
    "plan": {
        "current_milestone": "M07",
        "milestone_completed": False,
    },
    "next_step":
        "WRITE_COMPLETE_CANONICAL_REPLACEMENT_FILES",
    "publish": {
        "trycf_report": report,
        "report_txt": report_txt,
        "facts_json": facts_json,
        "architecture_plan": architecture,
        "xs_map": xs_map,
        "global_project_plan": global_plan,
        "vm101_reference": vm101_reference,
        "vm101_canonical": vm101_canonical,
    },
}, ensure_ascii=False, indent=2))
PY

create_report_index

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

trap - ERR

echo "decision=$PASS_DECISION"
echo "step_execution=PASS"
echo "operation_result=CANONICAL_WORKSPACE_AND_SOURCE_LOCKS_CREATED"
echo "production_health=UNCHANGED"
echo "canonical_id=$CANONICAL_ID"
echo "reference_id=$REFERENCE_ID"
echo "source_reference_id=$SOURCE_REFERENCE_ID"
echo "target_count=$TARGET_COUNT"
echo "source_count=$SOURCE_COUNT"
echo "sensitive_targets=$SENSITIVE_TARGETS"
echo "syntax_failures=$SYNTAX_FAILURES"
echo "contract_warnings=$CONTRACT_WARNINGS"
echo "vm101_contacted=false"
echo "vm101_modified=false"

print_links
