#!/usr/bin/env bash
set -Eeuo pipefail
umask 077
BASE="${ROUTER_OPS_BASE:-/opt/router-ops}"
ROOT="${BUNDLE_ROOT:?}"
RUN_DIR="${ROUTER_DETACHED_RUN_DIR:?}"
CHILD_RESULT="${ROUTER_DETACHED_CHILD_RESULT:?}"
INCOMING="${ROUTER_STEP_INCOMING:-/home/ops/incoming}"
SELFTEST="${ROUTER_EXACT_WORKER_SELFTEST:-false}"
STEP=STEP_050M07P22A_R01_VM121_IDENTITY_CLEANUP_READONLY_INVENTORY
CURRENT="$RUN_DIR/workflow-current.txt"; EVENTS="$RUN_DIR/workflow-events.tsv"; EVIDENCE="$RUN_DIR/evidence"; REPORT="$RUN_DIR/report"; PAYLOAD="$RUN_DIR/public-payload"; FINAL_ENV="$RUN_DIR/final-publication.env"; TARGET_DIR="$RUN_DIR/target"
mkdir -p "$EVIDENCE" "$REPORT" "$PAYLOAD" "$TARGET_DIR" "$INCOMING"; chmod 700 "$EVIDENCE" "$REPORT" "$PAYLOAD" "$TARGET_DIR"
source "$BASE/lib/router-step-workflow.sh"
router_step_workflow_init "$CURRENT" "$EVENTS" preflight inventory postcheck report_publish archive
CORE_CHANGE_COMPLETE=false; CORE_CHANGE_REPEATED=false; ROLLBACK_STATUS=not_applicable_read_only; STOP_DETAIL=unknown; STOP_HANDLED=false; LATEST_ARCHIVE=not_created; LATEST_ARCHIVE_SHA256=unknown; STEP_REPORT_URL=unknown
PRIVATE_INVENTORY_DIR=unknown; EMAIL_MISSING_COUNT=unknown; NORMALIZED_EMAIL_DUPLICATE_GROUP_COUNT=unknown; CANARY_TUNNEL_CANDIDATE_PEER_COUNT=unknown; CANARY_TUNNEL_IDENTITY_COUNT=unknown
LIVE_DATABASE_WRITTEN=false; DATABASE_SCHEMA_CHANGED=false; CLEANUP_MUTATION_PERFORMED=false
write_child(){ local result="$1" rc="$2" tmp="${CHILD_RESULT}.tmp.$$"; { echo RESULT="$result"; echo WORKER_RC="$rc"; echo CORE_CHANGE_COMPLETE="$CORE_CHANGE_COMPLETE"; echo CORE_CHANGE_REPEATED="$CORE_CHANGE_REPEATED"; echo ROLLBACK_STATUS="$ROLLBACK_STATUS"; echo STOP_DETAIL="$STOP_DETAIL"; echo PRIVATE_INVENTORY_DIR="$PRIVATE_INVENTORY_DIR"; echo EMAIL_MISSING_COUNT="$EMAIL_MISSING_COUNT"; echo NORMALIZED_EMAIL_DUPLICATE_GROUP_COUNT="$NORMALIZED_EMAIL_DUPLICATE_GROUP_COUNT"; echo CANARY_TUNNEL_CANDIDATE_PEER_COUNT="$CANARY_TUNNEL_CANDIDATE_PEER_COUNT"; echo CANARY_TUNNEL_IDENTITY_COUNT="$CANARY_TUNNEL_IDENTITY_COUNT"; echo LIVE_DATABASE_WRITTEN="$LIVE_DATABASE_WRITTEN"; echo DATABASE_SCHEMA_CHANGED="$DATABASE_SCHEMA_CHANGED"; echo CLEANUP_MUTATION_PERFORMED="$CLEANUP_MUTATION_PERFORMED"; echo STEP_REPORT_URL="$STEP_REPORT_URL"; echo LATEST_ARCHIVE="$LATEST_ARCHIVE"; echo LATEST_ARCHIVE_SHA256="$LATEST_ARCHIVE_SHA256"; } >"$tmp"; chmod 600 "$tmp"; mv "$tmp" "$CHILD_RESULT"; }
archive_latest(){ local log="$EVIDENCE/archive.log"; router_step_archive_latest_python "$BASE" "$INCOMING" "$(date -u +%Y%m%d-%H%M%S)" | tee "$log"; LATEST_ARCHIVE="$(awk -F= '$1=="LATEST_ARCHIVE"{print substr($0,index($0,"=")+1)}' "$log" | tail -n1)"; LATEST_ARCHIVE_SHA256="$(awk -F= '$1=="LATEST_ARCHIVE_SHA256"{print $2}' "$log" | tail -n1)"; }
stop_handler(){ local rc="$1" line="$2" cmd="$3"; trap - ERR; [[ "$STOP_HANDLED" == false ]] || exit "$rc"; STOP_HANDLED=true; { echo RESULT=STOP_P22A_VM121_IDENTITY_CLEANUP_READONLY_INVENTORY; echo STOP_RC="$rc"; echo STOP_LINE="$line"; printf 'STOP_COMMAND=%q\n' "$cmd"; echo CORE_CHANGE_COMPLETE="$CORE_CHANGE_COMPLETE"; echo ROLLBACK_STATUS="$ROLLBACK_STATUS"; echo STOP_DETAIL="$STOP_DETAIL"; echo LIVE_DATABASE_WRITTEN="$LIVE_DATABASE_WRITTEN"; echo DATABASE_SCHEMA_CHANGED="$DATABASE_SCHEMA_CHANGED"; } | tee "$REPORT/stop.txt" "$EVIDENCE/stop.txt"; set +e; archive_latest >/dev/null 2>&1; set -e; write_child STOP_P22A_VM121_IDENTITY_CLEANUP_READONLY_INVENTORY "$rc"; exit "$rc"; }
trap 'rc=$?; stop_handler "$rc" "$LINENO" "$BASH_COMMAND"' ERR
field(){ local k="$1" f="$2"; awk -F= -v k="$k" '$1==k{print substr($0,index($0,"=")+1); exit}' "$f"; }
extract_block(){ local b="$1" e="$2" src="$3" dst="$4"; awk -v b="$b" -v e="$e" '$0==b{p=1;next}$0==e{p=0}p' "$src" >"$dst"; }
write_selftest_source(){ cat <<'S'
MODE=--check
MACHINE_ID=vm121
LOCAL_HEAD=d17c0864339bd59a444264ce9305c2a6e560a5bf
REMOTE_HEAD=d17c0864339bd59a444264ce9305c2a6e560a5bf
STAGED_CHANGE_COUNT=0
SIMULATED_TREE_SHA=d1aa54e5774f59bc7eb564bf974ea74e13862723
SIMULATED_TREE_FILE_COUNT=42
RESULT=PASS_VM121_GIT_PUBLISH_CHECK
MACHINE_ID=vm121
GIT_COMMIT=d17c0864339bd59a444264ce9305c2a6e560a5bf
GIT_TREE=d1aa54e5774f59bc7eb564bf974ea74e13862723
GIT_FILE_COUNT=42
COMMIT_CREATED=false
PUSH_PERFORMED=false
S
}
collect_source_check(){ local stage="$1" rc=0; if [[ "$SELFTEST" == true ]]; then write_selftest_source >"$EVIDENCE/${stage}-vm121-source.txt"; else if ssh -T -o BatchMode=yes -o ConnectTimeout=20 vm121 /bin/bash -s >"$EVIDENCE/${stage}-vm121-source.txt" 2>&1 <<'S'
set -euo pipefail
/usr/local/sbin/router-vm121-git-publish.sh --check
S
then rc=0; else rc=$?; fi; [[ "$rc" == 0 ]] || { STOP_DETAIL="${stage}_vm121_git_transport"; false; }; fi; if ! "$ROOT/scripts/source-check-validate.sh" vm121 "$EVIDENCE/${stage}-vm121-source.txt" d17c0864339bd59a444264ce9305c2a6e560a5bf d1aa54e5774f59bc7eb564bf974ea74e13862723 42 >"$EVIDENCE/${stage}-vm121-source-validation.txt" 2>&1; then STOP_DETAIL="${stage}_vm121_git_semantics"; false; fi; }
write_selftest_inventory(){ local bad="${1:-false}"; local missing=9; [[ "$bad" == true ]] && missing=8; cat <<EOF2
ALEMBIC_VERSION=0002_live_drift_bridge
PENDING_JOB_COUNT=0
USER_COUNT=32
EMAIL_MISSING_COUNT=$missing
NORMALIZED_EMAIL_DUPLICATE_GROUP_COUNT=1
DUPLICATE_EMAIL_USER_COUNT=2
PEER_COUNT=32
ENABLED_PEER_COUNT=0
DISABLED_PEER_COUNT=32
SUBSCRIPTION_COUNT=19
ORDER_COUNT=0
PROVISIONING_JOB_COUNT=60
CANARY_TUNNEL_CANDIDATE_PEER_COUNT=5
CANARY_TUNNEL_IDENTITY_COUNT=5
IDENTITY_NORMALIZATION_RULE=lower_btrim
LEGACY_ACTIVE_SUBSCRIPTION_ENTITLEMENT_INFERENCE=false
PRIVATE_INVENTORY_DIR=/root/router-identity-inventory/p22a-selftest
PRIVATE_INVENTORY_MODE=700
PRIVATE_USERS_MODE=600
PRIVATE_USERS_SHA256=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
PRIVATE_PEERS_SHA256=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
MAPPING_TEMPLATE_SHA256=cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
PRIVATE_EMAILS_PUBLISHED=false
PRIVATE_KEYS_PUBLISHED=false
LIVE_DATABASE_WRITTEN=false
DATABASE_SCHEMA_CHANGED=false
CLEANUP_MUTATION_PERFORMED=false
DOMAIN_V2_OBJECTS_ADDED=false
---BEGIN_PUBLIC_USERS---
identity_ref	email_state	peer_count	enabled_peer_count	disabled_peer_count	canary_tunnel_peer_count	active_subscriptions	canceled_subscriptions	expired_subscriptions	classification_candidate	entitlement_policy
EOF2
for n in $(seq 1 32); do state=unique; [[ $n -le 9 ]] && state=missing; [[ $n -eq 10 || $n -eq 11 ]] && state=duplicate; can=0; cls=legacy_peer_owner_unclassified; if [[ $n -ge 28 ]]; then can=1; cls=canary_or_infrastructure_candidate; fi; act=0; [[ $n -le 13 ]] && act=1; printf 'U%03d\t%s\t1\t0\t1\t%s\t%s\t0\t0\t%s\texplicit_only_no_legacy_subscription_inference\n' "$n" "$state" "$can" "$act" "$cls"; done
cat <<'EOF2'
---END_PUBLIC_USERS---
---BEGIN_PUBLIC_DUPLICATE_GROUPS---
duplicate_group_ref	group_size	identity_refs
D001	2	U010,U011
---END_PUBLIC_DUPLICATE_GROUPS---
RESULT=PASS_P22A_VM121_IDENTITY_CLEANUP_READONLY_INVENTORY
EOF2
}
router_step_phase_begin preflight
collect_source_check pre
PKG="$ROOT/$(python3 - "$ROOT/release-info.json" <<'PY'
import json,sys; print(json.load(open(sys.argv[1]))['target_package_path'])
PY
)"
PKG_SHA="$(awk '{print $1}' "$ROOT/reference/target-package.sha256")"
if ! "$ROOT/scripts/target-apply.sh" "$PKG_SHA" "$PKG" "$TARGET_DIR" >"$EVIDENCE/target-extract.txt" 2>&1; then STOP_DETAIL=target_package; false; fi
router_step_phase_complete preflight vm121_source_and_package_verified
router_step_phase_begin inventory
if [[ "$SELFTEST" == true ]]; then if [[ "${ROUTER_PUBLIC_ROOT:-}" == */fail/public ]]; then write_selftest_inventory true >"$EVIDENCE/identity-inventory.txt"; else write_selftest_inventory false >"$EVIDENCE/identity-inventory.txt"; fi; else inv_rc=0; if ssh -T -o BatchMode=yes -o ConnectTimeout=20 vm121 /bin/bash -s <"$TARGET_DIR/vm121-identity-inventory.sh" >"$EVIDENCE/identity-inventory.txt" 2>&1; then inv_rc=0; else inv_rc=$?; fi; if [[ "$inv_rc" != 0 ]]; then remote_stage="$(field FAILED_STAGE "$EVIDENCE/identity-inventory.txt")"; [[ -n "$remote_stage" ]] || remote_stage=unknown; STOP_DETAIL="identity_inventory_remote_${remote_stage}"; false; fi; fi
if ! "$ROOT/scripts/identity-inventory-validate.sh" "$EVIDENCE/identity-inventory.txt" >"$EVIDENCE/identity-inventory-validation.txt" 2>&1; then STOP_DETAIL=identity_inventory_contract; false; fi
PRIVATE_INVENTORY_DIR="$(field PRIVATE_INVENTORY_DIR "$EVIDENCE/identity-inventory.txt")"; EMAIL_MISSING_COUNT="$(field EMAIL_MISSING_COUNT "$EVIDENCE/identity-inventory.txt")"; NORMALIZED_EMAIL_DUPLICATE_GROUP_COUNT="$(field NORMALIZED_EMAIL_DUPLICATE_GROUP_COUNT "$EVIDENCE/identity-inventory.txt")"; CANARY_TUNNEL_CANDIDATE_PEER_COUNT="$(field CANARY_TUNNEL_CANDIDATE_PEER_COUNT "$EVIDENCE/identity-inventory.txt")"; CANARY_TUNNEL_IDENTITY_COUNT="$(field CANARY_TUNNEL_IDENTITY_COUNT "$EVIDENCE/identity-inventory.txt")"
router_step_phase_complete inventory readonly_identity_inventory_collected
router_step_phase_begin postcheck
collect_source_check post
cmp -s "$EVIDENCE/pre-vm121-source-validation.txt" "$EVIDENCE/post-vm121-source-validation.txt" || { STOP_DETAIL=vm121_source_changed; false; }
CORE_CHANGE_COMPLETE=true
router_step_phase_complete postcheck source_unchanged_and_readonly_inventory_valid
extract_block '---BEGIN_PUBLIC_USERS---' '---END_PUBLIC_USERS---' "$EVIDENCE/identity-inventory.txt" "$REPORT/identity-inventory-public.tsv"
extract_block '---BEGIN_PUBLIC_DUPLICATE_GROUPS---' '---END_PUBLIC_DUPLICATE_GROUPS---' "$EVIDENCE/identity-inventory.txt" "$REPORT/duplicate-email-groups-public.tsv"
grep -E '^(ALEMBIC_VERSION|PENDING_JOB_COUNT|USER_COUNT|EMAIL_MISSING_COUNT|NORMALIZED_EMAIL_DUPLICATE_GROUP_COUNT|DUPLICATE_EMAIL_USER_COUNT|PEER_COUNT|ENABLED_PEER_COUNT|DISABLED_PEER_COUNT|SUBSCRIPTION_COUNT|ORDER_COUNT|PROVISIONING_JOB_COUNT|CANARY_TUNNEL_CANDIDATE_PEER_COUNT|CANARY_TUNNEL_IDENTITY_COUNT|IDENTITY_NORMALIZATION_RULE|LEGACY_ACTIVE_SUBSCRIPTION_ENTITLEMENT_INFERENCE|PRIVATE_INVENTORY_DIR|PRIVATE_INVENTORY_MODE|PRIVATE_USERS_MODE|PRIVATE_USERS_SHA256|PRIVATE_PEERS_SHA256|MAPPING_TEMPLATE_SHA256|PRIVATE_EMAILS_PUBLISHED|PRIVATE_KEYS_PUBLISHED|LIVE_DATABASE_WRITTEN|DATABASE_SCHEMA_CHANGED|CLEANUP_MUTATION_PERFORMED|DOMAIN_V2_OBJECTS_ADDED)=' "$EVIDENCE/identity-inventory.txt" >"$REPORT/identity-summary.txt"
cat >"$REPORT/mapping-design.txt" <<'MAP'
P2.2A explicit mapping design
- Uxxx references are inventory-only pseudonyms and are not production IDs.
- missing email: operator must explicitly map/recover an email or mark the legacy identity non-migrating; no invented email.
- duplicate normalized email: operator must explicitly choose merge/retain semantics before unique normalized email is introduced.
- canary_or_infrastructure_candidate: structural candidate only, derived from historical 10.253.1.10-.14 peer ownership; not automatic classification.
- all current peers are historical disabled records and remain preserved in this STEP.
- legacy active subscriptions never create production entitlement automatically.
- selected_for_production_migration remains false until explicit operator mapping/invite decision.
- no destructive cleanup and no Domain V2 object creation occurs in P2.2A.
MAP
cat >"$REPORT/report.txt" <<EOF2
RESULT=PASS_P22A_VM121_IDENTITY_CLEANUP_READONLY_INVENTORY
REVISION=R01
CORE_CHANGE_COMPLETE=true
CORE_CHANGE_REPEATED=false
ROLLBACK_STATUS=not_applicable_read_only
SOURCE_CHANGED=false
MACHINE_GIT_CLOSE_RUN=false
DATABASE_SCHEMA_CHANGED=false
LIVE_DATABASE_WRITTEN=false
CLEANUP_MUTATION_PERFORMED=false
DOMAIN_V2_OBJECTS_ADDED=false
ALEMBIC_VERSION=0002_live_drift_bridge
USER_COUNT=32
EMAIL_MISSING_COUNT=$EMAIL_MISSING_COUNT
NORMALIZED_EMAIL_DUPLICATE_GROUP_COUNT=$NORMALIZED_EMAIL_DUPLICATE_GROUP_COUNT
CANARY_TUNNEL_CANDIDATE_PEER_COUNT=$CANARY_TUNNEL_CANDIDATE_PEER_COUNT
CANARY_TUNNEL_IDENTITY_COUNT=$CANARY_TUNNEL_IDENTITY_COUNT
LEGACY_ACTIVE_SUBSCRIPTION_ENTITLEMENT_INFERENCE=false
PRIVATE_INVENTORY_DIR=$PRIVATE_INVENTORY_DIR
PRIVATE_EMAILS_PUBLISHED=false
PRIVATE_KEYS_PUBLISHED=false
VM121_COMMIT=d17c0864339bd59a444264ce9305c2a6e560a5bf
VM121_TREE=d1aa54e5774f59bc7eb564bf974ea74e13862723
VM121_FILE_COUNT=42
VM121_PUBLIC_URL=https://reports.secret-studio.ru/latest/20260809-180554_vm121_git_source_d17c0864339b/
VM100_PUBLIC_URL=https://reports.secret-studio.ru/latest/20260809-142753_vm100_git_source_e865282f4837/
VM101_PUBLIC_URL=https://reports.secret-studio.ru/latest/20260801-095331_vm101_git_source_ac678d1b3164/
PROJECT_SOURCE_POST_URL=https://reports.secret-studio.ru/latest/20260802-105248_project_source_post_r20qx_vm121_multi_protocol_architecture/
GLOBAL_PLAN_URL=https://reports.secret-studio.ru/latest/20260802-105248_global_project_plan_wg_paid_after_r20qw/
ACCESS_MAP_URL=https://reports.secret-studio.ru/latest/20260715-210236_access_map_canonical_current/
RUNTIME_IMPACT=false
COPY_VERIFY=PASS
NEXT_ACTION=P2.2B explicit operator mapping decisions for missing/duplicate identities and canary/test classification; still no destructive cleanup until mapping is frozen
EOF2
cp "$REPORT/report.txt" "$REPORT/verify.txt"; cp "$ROOT/scripts/run-step.sh" "$REPORT/executed-step.sh"; cp "$ROOT/release-info.json" "$REPORT/release-info.json"; cp "$ROOT/manifest.sha256" "$REPORT/bundle-manifest.sha256"; cp "$ROOT/rollback/README.md" "$REPORT/rollback.txt"; cp "$EVIDENCE/pre-vm121-source-validation.txt" "$REPORT/"; cp "$EVIDENCE/post-vm121-source-validation.txt" "$REPORT/"; cp "$EVIDENCE/identity-inventory-validation.txt" "$REPORT/"
router_step_phase_begin report_publish
if [[ "$SELFTEST" == true ]]; then DEST="${ROUTER_PUBLIC_ROOT:?}/latest/p22a-r01-selftest"; rm -rf "$DEST"; mkdir -p "$DEST"; cp -a "$REPORT"/. "$DEST"/; STEP_REPORT_URL=selftest://p22a-r01/; else args=(); while IFS= read -r f; do args+=("$f" "$(basename "$f")"); done < <(find "$REPORT" -maxdepth 1 -type f -print | LC_ALL=C sort); router_step_publish_payload step050m07p22a_r01_vm121_identity_cleanup_readonly_inventory "$REPORT" "$PAYLOAD" "$CURRENT" "$EVENTS" "$FINAL_ENV" "${args[@]}"; STEP_REPORT_URL="$(awk -F= '$1=="PUBLIC_URL"{print substr($0,index($0,"=")+1)}' "$FINAL_ENV" | tail -n1)"; fi
router_step_phase_complete report_publish step_report_published
router_step_phase_begin archive; archive_latest; router_step_phase_complete archive latest_created; router_step_workflow_mark_complete
trap - ERR; STOP_DETAIL=none; write_child PASS_P22A_VM121_IDENTITY_CLEANUP_READONLY_INVENTORY 0; cat "$REPORT/report.txt"; echo STEP_REPORT_URL="$STEP_REPORT_URL"; echo LATEST_ARCHIVE="$LATEST_ARCHIVE"; echo LATEST_ARCHIVE_SHA256="$LATEST_ARCHIVE_SHA256"
