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

SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
FIXTURE_DIR="$SCRIPT_DIR/fixtures/lot2"
MARKER='.soria-hardening-privileged-access-secrets'
EXPECTED_DECISION='PRIVILEGED_ACCESS_WITH_ACTIONS'

fail() { echo "ERROR: $*" >&2; exit 1; }
info() { printf '%s\n' "$*"; }

usage() {
  cat <<'USAGE'
Usage: module2-privileged-access-secrets.sh <setup|status|run|validate|reset> <root>
The root must match /tmp/soria-hardening-* or $HOME/soria-hardening-*.
USAGE
}

safe_root() {
  local root=${1:-}
  [[ -n "$root" ]] || fail 'missing lab root'
  [[ "$root" = /* ]] || fail 'lab root must be absolute'
  [[ "$root" != '/' && "$root" != '/tmp' && "$root" != "$HOME" ]] || fail 'unsafe lab root'
  case "$root" in
    /tmp/soria-hardening-*|"$HOME"/soria-hardening-*) ;;
    *) fail "lab root outside allowed patterns: $root" ;;
  esac
}

require_marker() {
  local root=$1
  [[ -f "$root/$MARKER" ]] || fail "missing marker: $root/$MARKER"
}

csv_unique() {
  local file=$1 col=$2
  awk -F, -v c="$col" 'NR==1{next} {if($c=="" || seen[$c]++) exit 1}' "$file"
}

csv_nonempty() {
  local file=$1; shift
  local cols="$*"
  awk -F, -v cols="$cols" '
    BEGIN{n=split(cols,a," ")}
    NR==1{next}
    {for(i=1;i<=n;i++) if($(a[i])=="") exit 1}
  ' "$file"
}

validate_service_accounts_file() {
  local accounts=$1 services=$2
  awk -F, -v accounts="$accounts" '
    BEGIN{while((getline line < accounts)>0){if(++n==1)continue; split(line,a,","); ids[a[1]]=1}}
    NR==1{next}
    !ids[$1]{print "orphan service account: "$1 > "/dev/stderr"; bad=1}
    $2==""{print "service account without owner: "$1 > "/dev/stderr"; bad=1}
    $4==""{print "service account without scope: "$1 > "/dev/stderr"; bad=1}
    $5!="no"{print "interactive service account: "$1 > "/dev/stderr"; bad=1}
    END{exit bad}
  ' "$services"
}

validate_privileges_file() {
  local accounts=$1 privileges=$2
  awk -F, -v accounts="$accounts" '
    BEGIN{
      while((getline line < accounts)>0){if(++n==1)continue; split(line,a,","); type[a[1]]=a[2]}
    }
    NR==1{next}
    $6=="privileged" && ($7=="" || $8=="" || $9=="") {print "privilege without justification approval or owner: "$1 > "/dev/stderr"; bad=1}
    $6=="privileged" && $2=="account" && type[$3]=="daily-user" && $10=="active" {print "daily identity with privileged entitlement: "$3 > "/dev/stderr"; bad=1}
    END{exit bad}
  ' "$privileges"
}

validate_secrets_file() {
  local file=$1
  awk -F, '
    NR==1{next}
    $3==""{print "secret without responsible owner: "$1 > "/dev/stderr"; bad=1}
    $5=="" || $5+0<=0 {print "secret without rotation policy: "$1 > "/dev/stderr"; bad=1}
    $10!=""{print "secret value must remain empty: "$1 > "/dev/stderr"; bad=1}
    END{exit bad}
  ' "$file"
}

validate_admin_paths_file() {
  local file=$1
  awk -F, '
    NR==1{next}
    $2=="user-zone" && $4 ~ /management|directory|network|app-prod/ && $6=="ALLOW" {print "administrative path bypasses approved management route: "$1 > "/dev/stderr"; bad=1}
    $6=="ALLOW" && $3!="admin-bastion" {print "allowed administrative path missing approved relay: "$1 > "/dev/stderr"; bad=1}
    END{exit bad}
  ' "$file"
}

validate_emergency_file() {
  local file=$1
  awk -F, '
    NR==1{next}
    {n=split($2,g,"|")} n<2 {print "emergency access without separated guardians: "$1 > "/dev/stderr"; bad=1}
    $5!="yes"{print "emergency access without approval: "$1 > "/dev/stderr"; bad=1}
    $6!="yes"{print "emergency access without expiration: "$1 > "/dev/stderr"; bad=1}
    $10==""{print "emergency access without revocation evidence: "$1 > "/dev/stderr"; bad=1}
    $11!="no"{print "emergency account used daily: "$1 > "/dev/stderr"; bad=1}
    END{exit bad}
  ' "$file"
}

setup_lab() {
  local root=$1
  safe_root "$root"
  if [[ -e "$root" ]]; then require_marker "$root"; chmod -R u+w "$root"; rm -rf -- "$root"; fi
  mkdir -p "$root"/{source/{identities,privileges,authentication,administration,services,secrets,emergency},state,work,output,negative,rollback}
  printf 'SORIA hardening Lot 2 disposable lab\n' > "$root/$MARKER"
  [[ -d "$FIXTURE_DIR" ]] || fail "missing fixture directory: $FIXTURE_DIR"
  cp -a "$FIXTURE_DIR/." "$root/source/"
  find "$root/source" -type f -print0 | sort -z | xargs -0 sha256sum > "$root/state/source.sha256"
  chmod -R a-w "$root/source"
  info "Lot 2 setup complete: $root"
}

status_lab() {
  local root=$1
  safe_root "$root"; require_marker "$root"
  printf 'root=%s\n' "$root"
  printf 'source_files=%s\n' "$(find "$root/source" -type f | wc -l | tr -d ' ')"
  printf 'outputs=%s\n' "$(find "$root/output" -type f 2>/dev/null | wc -l | tr -d ' ')"
  [[ -f "$root/output/final-decision.txt" ]] && cat "$root/output/final-decision.txt" || true
}

run_lab() {
  local root=$1
  safe_root "$root"; require_marker "$root"
  rm -rf "$root/work" "$root/output" "$root/rollback"
  mkdir -p "$root/work"/{before,target} "$root/output" "$root/rollback"
  cp -a "$root/source/." "$root/work/before/"
  cp -a "$root/source/." "$root/work/target/"
  chmod -R u+w "$root/work"

  awk -F, 'BEGIN{OFS=","} NR==1{print;next} $1=="ENT-007"{$10="removed"} {print}' \
    "$root/work/before/privileges/entitlements.csv" > "$root/work/target/privileges/entitlements.csv"
  awk -F, 'BEGIN{OFS=","} NR==1{print;next} $1=="DEL-003"{$3="app-prod";$4="restart-erp";$6="role-change"} {print}' \
    "$root/work/before/privileges/delegations.csv" > "$root/work/target/privileges/delegations.csv"
  awk -F, 'BEGIN{OFS=","} NR==1{print;next} $2=="user-zone" && $3=="direct" {$6="DENY";$7="direct management bypass prohibited"} {print}' \
    "$root/work/before/administration/admin-paths.csv" > "$root/work/target/administration/admin-paths.csv"
  awk -F, 'BEGIN{OFS=","} NR==1{print;next} {$5="no"} {print}' \
    "$root/work/before/services/service-accounts.csv" > "$root/work/target/services/service-accounts.csv"

  cp "$root/work/target/identities/accounts.csv" "$root/output/identity-catalog.csv"
  cp "$root/output/identity-catalog.csv" "$root/output/identity-inventory.csv"
  {
    echo 'owner_id,daily_account,admin_account,result,evidence,limit'
    for owner in person-ops-01 person-id-01 person-net-01; do
      daily=$(awk -F, -v o="$owner" 'NR>1 && $2=="daily-user" && $3==o{print $1}' "$root/work/target/identities/accounts.csv")
      admin=$(awk -F, -v o="$owner" 'NR>1 && $2=="privileged-admin" && $3==o{print $1}' "$root/work/target/identities/accounts.csv")
      [[ -n "$daily" && -n "$admin" ]] && result=PASS || result=BLOCKED
      echo "$owner,$daily,$admin,$result,fixture relation,no real directory checked"
    done
  } > "$root/output/account-separation-review.csv"

  cp "$root/work/target/privileges/entitlements.csv" "$root/output/privilege-matrix.csv"
  {
    echo 'entitlement_id,subject_id,resource,before_status,target_status,decision,reason,evidence,limit'
    awk -F, 'BEGIN{OFS=","} NR==1{next} {decision=($1=="ENT-007"?"REMOVE":"KEEP"); reason=($1=="ENT-007"?"daily account held privileged shell":"required by access requirement"); print $1,$3,$4,"active",$10,decision,reason,"fixture comparison","no real authorization changed"}' "$root/work/target/privileges/entitlements.csv"
  } > "$root/output/least-privilege-review.csv"
  {
    echo 'delegation_id,delegate,before_scope,target_scope,before_permission,target_permission,approval,revocation,decision,limit'
    awk -F, 'BEGIN{OFS=","} NR==1{next} {before_scope=($1=="DEL-003"?"all-servers":$3); before_perm=($1=="DEL-003"?"full-admin":$4); decision=($1=="DEL-003"?"RESTRICT":"KEEP"); print $1,$2,before_scope,$3,before_perm,$4,$7,$8,decision,"fixture only"}' "$root/work/target/privileges/delegations.csv"
  } > "$root/output/delegation-review.csv"
  cp "$root/output/delegation-review.csv" "$root/output/delegation-plan.csv"
  {
    echo 'combination,subject,target_state,result,reason'
    echo 'daily-plus-privileged,acct-user-ops01,removed,PASS,daily account no longer has privileged entitlement'
    echo 'service-plus-interactive,svc-monitor,non-interactive,PASS,service account target disables interactive use'
    echo 'user-zone-direct-management,PATH-001|PATH-003,denied,PASS,direct paths are denied'
    echo 'emergency-plus-daily,bg-directory|bg-network,prohibited,PASS,emergency accounts absent from daily flows'
  } > "$root/output/prohibited-combinations.csv"
  {
    echo 'account_type,authentication_requirement,interactive_allowed,exception_id,proof_state,action_required,decision,limit'
    awk -F, 'BEGIN{OFS=","} NR==1{next} {decision=($5=="declared"||$5=="partial"?"WITH_ACTIONS":"PASS"); print $1,$2,$3,$4,$5,$6,decision,"no authentication attempted"}' "$root/source/authentication/privileged-auth-policy.csv"
  } > "$root/output/authentication-review.csv"
  {
    echo 'path_id,origin,relay,destination,protocol,before_decision,target_decision,justification,functional_test,rollback,limit'
    awk -F, 'BEGIN{OFS=","} NR==1{next} {before=($2=="user-zone"&&$3=="direct"?"ALLOW":$6); print $1,$2,$3,$4,$5,before,$6,$7,$8,$9,"synthetic path only"}' "$root/work/target/administration/admin-paths.csv"
  } > "$root/output/admin-path-review.csv"
  cp "$root/output/admin-path-review.csv" "$root/output/administrative-paths.csv"
  cp "$root/work/target/services/service-accounts.csv" "$root/output/service-account-register.csv"
  {
    echo 'secret_id,secret_type,owner,storage_ref,rotation_days,last_rotation,due_state,dependencies,status,proof_available,action_required,limit'
    awk -F, 'BEGIN{OFS=","} NR==1{next} {proof=($7=="overdue"?"missing-current-rotation":"metadata-only"); action=($7=="overdue"?"rotate and verify dependencies":"verify real storage and enforcement"); print $1,$2,$3,$4,$5,$6,$7,$8,$9,proof,action,"no secret value or vault checked"}' "$root/source/secrets/secrets-register.csv"
  } > "$root/output/secret-governance.csv"
  cp "$root/output/secret-governance.csv" "$root/output/secret-register.csv"
  cp "$root/source/secrets/rotation-policy.csv" "$root/output/rotation-plan.csv"
  {
    echo 'account_id,event,account_action,group_action,delegation_action,direct_right_action,dependent_secrets,review_evidence,limit'
    echo 'acct-user-former01,departure,suspend,remove-all,remove-all,remove-all,review related service accounts and secrets,evidence://departure/003,no session or token revoked'
    echo 'acct-admin-ops01,compromise,suspend,remove-admin-groups,remove-delegations,remove-direct-rights,SEC-001,evidence://simulated/compromise,no real account changed'
    echo 'svc-backup,service-retirement,disable,not-applicable,remove-workload-grants,remove-repository-grant,SEC-002,evidence://simulated/service-retirement,no real workload changed'
  } > "$root/output/revocation-plan.csv"
  cp "$root/output/revocation-plan.csv" "$root/output/revocation-report.csv"
  {
    echo 'account_id,guardians,activation_condition,max_minutes,approval,expiration,logging,post_review,periodic_test,revocation_evidence,daily_use,decision,action,limit'
    awk -F, 'BEGIN{OFS=","} NR==1{next} {decision=($9=="missing"?"WITH_ACTIONS":"PASS"); action=($9=="missing"?"perform controlled periodic test":"retain evidence and review"); print $1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,decision,action,"no real emergency account tested"}' "$root/source/emergency/emergency-accounts.csv"
  } > "$root/output/emergency-access-review.csv"
  cp "$root/output/emergency-access-review.csv" "$root/output/break-glass-register.csv"
  {
    echo 'exception_id,subject,justification,owner,expiration,compensating_control,residual_risk,decision'
    echo 'EXC-MFA-001,privileged authentication,real enforcement not observed,security-manager,2026-09-30,review declared policy and collect MFA evidence,credential compromise remains possible,WITH_ACTIONS'
    echo 'EXC-ROT-001,SEC-002,rotation overdue,team-platform,2026-08-10,restrict workload and rotate in controlled window,credential age remains excessive,WITH_ACTIONS'
    echo 'EXC-BG-001,bg-directory,periodic test missing,security-guardians,2026-08-31,dual guardians and sealed reference,recovery procedure may fail,WITH_ACTIONS'
  } > "$root/output/exceptions-register.csv"
  cp "$root/output/exceptions-register.csv" "$root/output/exceptions.csv"
  {
    echo 'artifact,before_path,target_path,rollback_method,functional_test,proof_limit'
    echo 'entitlements,work/before/privileges/entitlements.csv,work/target/privileges/entitlements.csv,restore before CSV,required permissions remain represented,no real rights changed'
    echo 'delegations,work/before/privileges/delegations.csv,work/target/privileges/delegations.csv,restore before CSV,required delegations remain represented,no real delegation changed'
    echo 'admin-paths,work/before/administration/admin-paths.csv,work/target/administration/admin-paths.csv,restore before CSV,approved paths remain represented,no firewall rule changed'
    echo 'service-accounts,work/before/services/service-accounts.csv,work/target/services/service-accounts.csv,restore before CSV,workload access remains represented,no account changed'
  } > "$root/output/rollback-manifest.csv"
  cp -a "$root/work/target/." "$root/rollback/target-copy/"
  rm -rf "$root/rollback/restored-before"
  mkdir -p "$root/rollback/restored-before"
  cp -a "$root/work/before/." "$root/rollback/restored-before/"
  diff -ru "$root/work/before" "$root/rollback/restored-before" > "$root/output/rollback-report.txt" || fail 'rollback simulation mismatch'
  printf 'ROLLBACK_FIXTURE_MATCH\n' >> "$root/output/rollback-report.txt"
  cat > "$root/output/proof-boundaries.md" <<'MD'
# Proof boundaries

The lab proves deterministic relationships and transformations of synthetic CSV fixtures.
It does not prove a real directory change, MFA enforcement, PAM or SSO control, network isolation,
secret-vault enforcement, credential rotation, session revocation, emergency-account operation or regulatory compliance.
MD
  printf '%s\n' "$EXPECTED_DECISION" > "$root/output/final-decision.txt"
  cat > "$root/output/validation-summary.txt" <<EOF_SUM
reference_decision=$EXPECTED_DECISION
account_separation=represented
least_privilege=represented
management_route=represented
service_accounts=non-interactive-target
secret_values=absent
rotation_overdue=SEC-002
emergency_test_action=bg-directory
proof_scope=synthetic-fixtures-only
EOF_SUM
  info "Lot 2 run complete: $EXPECTED_DECISION"
}

negative_tests() {
  local root=$1 neg="$root/negative"
  rm -rf "$neg"; mkdir -p "$neg"
  mkdir -p "$neg/daily-priv"; cp "$root/work/target/identities/accounts.csv" "$neg/daily-priv/accounts.csv"; cp "$root/work/target/privileges/entitlements.csv" "$neg/daily-priv/entitlements.csv"; chmod u+w "$neg/daily-priv/entitlements.csv"
  echo 'NEG-001,account,acct-user-sales01,directory-management,full-admin,privileged,negative fixture,security-manager,team-identity,active' >> "$neg/daily-priv/entitlements.csv"
  if validate_privileges_file "$neg/daily-priv/accounts.csv" "$neg/daily-priv/entitlements.csv" >/dev/null 2>&1; then fail 'negative variant passed: incompatible daily and privileged roles'; fi
  info 'Negative test blocked: daily identity with privileged entitlement'
  mkdir -p "$neg/orphan-service"; cp "$root/work/target/identities/accounts.csv" "$neg/orphan-service/accounts.csv"; cp "$root/work/target/services/service-accounts.csv" "$neg/orphan-service/services.csv"; chmod u+w "$neg/orphan-service/services.csv"
  echo 'svc-orphan,,unknown-workload,unknown-resource,no,2026-08-15,disable binding,secret-ref://vault/soria/orphan,negative fixture' >> "$neg/orphan-service/services.csv"
  if validate_service_accounts_file "$neg/orphan-service/accounts.csv" "$neg/orphan-service/services.csv" >/dev/null 2>&1; then fail 'negative variant passed: orphan service account'; fi
  info 'Negative test blocked: orphan service account'
  mkdir -p "$neg/privilege-owner"; cp "$root/work/target/identities/accounts.csv" "$neg/privilege-owner/accounts.csv"; cp "$root/work/target/privileges/entitlements.csv" "$neg/privilege-owner/entitlements.csv"; chmod u+w "$neg/privilege-owner/entitlements.csv"
  echo 'NEG-003,account,acct-admin-id01,directory-management,full-admin,privileged,,,,active' >> "$neg/privilege-owner/entitlements.csv"
  if validate_privileges_file "$neg/privilege-owner/accounts.csv" "$neg/privilege-owner/entitlements.csv" >/dev/null 2>&1; then fail 'negative variant passed: privilege without owner or justification'; fi
  info 'Negative test blocked: privilege without owner or justification'
  mkdir -p "$neg/secret-governance"; cp "$root/source/secrets/secrets-register.csv" "$neg/secret-governance/secrets.csv"; chmod u+w "$neg/secret-governance/secrets.csv"
  echo 'NEG-SEC,api-reference,,secret-ref://vault/soria/negative,0,2026-01-01,overdue,svc-erp,active,' >> "$neg/secret-governance/secrets.csv"
  if validate_secrets_file "$neg/secret-governance/secrets.csv" >/dev/null 2>&1; then fail 'negative variant passed: secret without rotation or owner'; fi
  info 'Negative test blocked: secret without rotation or responsible owner'
  mkdir -p "$neg/admin-bypass"; cp "$root/work/target/administration/admin-paths.csv" "$neg/admin-bypass/paths.csv"; chmod u+w "$neg/admin-bypass/paths.csv"
  echo 'NEG-PATH,user-zone,direct,network-management,https,ALLOW,bypass fixture,network admin can reach edge,restore before path' >> "$neg/admin-bypass/paths.csv"
  if validate_admin_paths_file "$neg/admin-bypass/paths.csv" >/dev/null 2>&1; then fail 'negative variant passed: management route bypass'; fi
  info 'Negative test blocked: administrative path bypasses approved management route'
  mkdir -p "$neg/emergency"; cp "$root/source/emergency/emergency-accounts.csv" "$neg/emergency/emergency.csv"; chmod u+w "$neg/emergency/emergency.csv"
  echo 'NEG-BG,guardian-z,negative emergency,0,no,no,none,none,missing,,yes' >> "$neg/emergency/emergency.csv"
  if validate_emergency_file "$neg/emergency/emergency.csv" >/dev/null 2>&1; then fail 'negative variant passed: emergency access controls absent'; fi
  info 'Negative test blocked: emergency access lacks expiration approval or revocation evidence'
}

validate_lab() {
  local root=$1
  safe_root "$root"; require_marker "$root"
  [[ -f "$root/output/final-decision.txt" ]] || fail 'run must complete before validate'
  local required=(
    identity-catalog.csv account-separation-review.csv privilege-matrix.csv least-privilege-review.csv
    delegation-review.csv authentication-review.csv admin-path-review.csv service-account-register.csv
    secret-governance.csv rotation-plan.csv revocation-plan.csv emergency-access-review.csv
    exceptions-register.csv rollback-manifest.csv final-decision.txt proof-boundaries.md
    identity-inventory.csv delegation-plan.csv prohibited-combinations.csv administrative-paths.csv
    secret-register.csv revocation-report.csv break-glass-register.csv exceptions.csv
    rollback-report.txt validation-summary.txt
  )
  for f in "${required[@]}"; do [[ -s "$root/output/$f" ]] || fail "missing output: $f"; done
  (cd / && sha256sum -c "$root/state/source.sha256" >/dev/null) || fail 'source fixtures changed'
  csv_unique "$root/source/identities/people.csv" 1 || fail 'duplicate person id'
  csv_unique "$root/source/identities/accounts.csv" 1 || fail 'duplicate account id'
  csv_unique "$root/source/identities/groups.csv" 1 || fail 'duplicate group id'
  csv_unique "$root/source/privileges/entitlements.csv" 1 || fail 'duplicate entitlement id'
  csv_unique "$root/source/privileges/delegations.csv" 1 || fail 'duplicate delegation id'
  csv_unique "$root/source/administration/management-assets.csv" 1 || fail 'duplicate asset id'
  csv_unique "$root/source/secrets/secrets-register.csv" 1 || fail 'duplicate secret id'
  csv_nonempty "$root/source/identities/accounts.csv" 1 2 3 4 5 6 7 8 || fail 'account metadata incomplete'
  for t in daily-user privileged-admin service emergency; do awk -F, -v t="$t" 'NR>1&&$2==t{f=1} END{exit !f}' "$root/source/identities/accounts.csv" || fail "missing account type: $t"; done
  awk -F, 'NR>1&&$2=="privileged-admin"{if(owner[$3]++)exit 1} END{exit 0}' "$root/source/identities/accounts.csv" || fail 'shared privileged human owner detected'
  awk -F, '
    NR==FNR{if(FNR>1){if($2=="daily-user") daily[$3]=1} next}
    FNR>1 && $2=="privileged-admin" && !daily[$3]{bad=1}
    END{exit bad}
  ' "$root/source/identities/accounts.csv" "$root/source/identities/accounts.csv" || fail 'privileged person missing separate daily account'
  validate_privileges_file "$root/work/target/identities/accounts.csv" "$root/work/target/privileges/entitlements.csv" || fail 'target privilege validation failed'
  awk -F, 'NR>1&&$1=="DEL-003"&&$3=="app-prod"&&$4=="restart-erp"&&$6=="role-change"{f=1} END{exit !f}' "$root/work/target/privileges/delegations.csv" || fail 'broad delegation was not restricted'
  awk -F, 'NR>1&&$1=="privileged-admin"&&($2 ~ /mfa/ || $4!=""){f=1} END{exit !f}' "$root/output/authentication-review.csv" || fail 'privileged authentication requirement missing'
  validate_admin_paths_file "$root/work/target/administration/admin-paths.csv" || fail 'administrative path validation failed'
  for p in PATH-002 PATH-004 PATH-005; do awk -F, -v p="$p" 'NR>1&&$1==p&&$3=="admin-bastion"&&$6=="ALLOW"{f=1} END{exit !f}' "$root/work/target/administration/admin-paths.csv" || fail "approved management path missing: $p"; done
  validate_service_accounts_file "$root/work/target/identities/accounts.csv" "$root/work/target/services/service-accounts.csv" || fail 'service account validation failed'
  validate_secrets_file "$root/source/secrets/secrets-register.csv" || fail 'secret register validation failed'
  awk -F, 'NR>1&&$7=="overdue"{f=1} END{exit !f}' "$root/source/secrets/secrets-register.csv" || fail 'reference overdue secret missing'
  awk -F, 'NR>1&&$7=="overdue"&&$11 ~ /rotate/{f=1} END{exit !f}' "$root/output/secret-governance.csv" || fail 'overdue rotation action missing'
  validate_emergency_file "$root/source/emergency/emergency-accounts.csv" || fail 'emergency access validation failed'
  awk -F, 'NR>1&&$9=="missing"&&$12=="WITH_ACTIONS"{f=1} END{exit !f}' "$root/output/emergency-access-review.csv" || fail 'missing emergency test action not reported'
  [[ $(awk 'END{print NR-1}' "$root/output/rollback-manifest.csv") -eq 4 ]] || fail 'rollback manifest incomplete'
  grep -Fxq 'ROLLBACK_FIXTURE_MATCH' "$root/output/rollback-report.txt" || fail 'rollback fixture mismatch'
  grep -Fxq "$EXPECTED_DECISION" "$root/output/final-decision.txt" || fail 'unexpected final decision'
  for req in REQ-201 REQ-202 REQ-203 REQ-204 REQ-205; do grep -Fq "$req" "$root/source/privileges/access-requirements.csv" || fail "missing functional requirement: $req"; done
  for svc in svc-erp svc-backup svc-monitor; do awk -F, -v svc="$svc" 'NR>1&&$1==svc&&$5=="no"{f=1} END{exit !f}' "$root/work/target/services/service-accounts.csv" || fail "service account target invalid: $svc"; done
  negative_tests "$root"
  info "Lot 2 validation passed: $EXPECTED_DECISION"
}

reset_lab() {
  local root=$1
  safe_root "$root"; require_marker "$root"
  chmod -R u+w "$root"
  rm -rf -- "$root"
  info "Lot 2 reset complete: $root"
}

main() {
  [[ $# -eq 2 ]] || { usage; exit 2; }
  local action=$1 root=$2
  case "$action" in
    setup) setup_lab "$root" ;;
    status) status_lab "$root" ;;
    run) run_lab "$root" ;;
    validate) validate_lab "$root" ;;
    reset) reset_lab "$root" ;;
    *) usage; exit 2 ;;
  esac
}

main "$@"
