#!/usr/bin/env bash
# Shared hardhat decisions. Host wrappers own payload parsing and deny response emission.

# Armed site rules apply until clock-out writes ENDED. STOP is a stop-work order, not
# the ending — open boxes stay as the record and do not stand the hardhat down. Reset
# is the manual escape. A punch list that exists but will not count keeps the site armed:
# only a readable list with every box ticked takes the hardhat off.
ns_hardhat_active() {
  local _open _armed _stop
  if [ -f "$ENDED" ] && [ ! -L "$ENDED" ]; then
    return 1
  fi
  ns_layout_set _armed "$NS" armed
  ns_layout_set _stop "$NS" stop
  [ -f "$_armed" ] || return 1
  [ -f "$PUNCH" ] || return 1
  if [ -f "$_stop" ] && [ ! -L "$_stop" ]; then
    return 0
  fi
  _open="$(ns_open_boxes "$PUNCH")" || return 0
  [ "$_open" -gt 0 ]
}

# ns_hardhat_state_name <key> — a state file as a message names it, `.nightshift/<path>` in this
# workspace's layout.
ns_hardhat_state_name() {
  ns_layout_name "${NS:-}" "$1"
}

ns_hardhat_is_command_tool() {
  case "$1" in Bash | PowerShell | Shell) return 0 ;; esac
  return 1
}

ns_hardhat_binding_probe() { # $1 = canonical tool name, $2 = command
  case "$1" in
    Bash | Shell) [ "$2" = ": nightshift-binding-probe" ] ;;
    PowerShell) [ "$2" = "\$null = 'nightshift-binding-probe'" ] ;;
    *) return 1 ;;
  esac
}

ns_hardhat_rules_targeted() {
  local normalized
  normalized="$(printf '%s' "$1" | sed 's#\\/#/#g')"
  printf '%s' "$normalized" | grep -qE '\.nightshift/rules\.json|nightshift-rules\.json' \
    || {
      printf '%s' "$normalized" | grep -q '\.nightshift' \
        && printf '%s' "$normalized" | grep -q 'rules\.json'
    }
}

# True when the command is already inside .nightshift — by path, by cd/pushd, or by payload cwd.
ns_hardhat_nightshift_dir_context() {
  local normalized="$1" cwd_norm ns_norm
  # `.nightshift && unlink .shift-armed` has no slash after the directory name.
  if printf '%s' "$normalized" | grep -qE '\.nightshift([/[:space:];&]|$)'; then
    return 0
  fi
  if printf '%s' "$normalized" |
      grep -qE '(^|[;&|()[:space:]])(cd|pushd)[[:space:]]+([^;&|()[:space:]]*/)?\.nightshift/?([;&|()[:space:]]|$)'; then
    return 0
  fi
  if [ -n "${CWD:-}" ] && [ -n "${NS:-}" ]; then
    cwd_norm="${CWD%/}"
    ns_norm="${NS%/}"
    case "$cwd_norm" in
      "$ns_norm" | "$ns_norm"/*) return 0 ;;
    esac
  fi
  return 1
}

ns_hardhat_lease_targeted() {
  local normalized nightshift_context=0 run_rel
  normalized="$(printf '%s' "$1" | sed "s#\\\\/#/#g; s#[\"']##g")"
  if printf '%s' "$normalized" |
      grep -qE '(^|/)(\.shift-lease|\.mutex-scope)($|[^[:alnum:]_-])|(^|/)\.lease-lock\.d($|/)'; then
    return 0
  fi
  if ns_hardhat_nightshift_dir_context "$normalized"; then
    nightshift_context=1
  fi
  if [ "$nightshift_context" -eq 1 ]; then
    case "$normalized" in
      *'.shift-*'* | *'.shift-?'* | *'.shift-['* | *'.shift-{'* | *'.shift-$'* | *'.shift-`'* \
        | *'.lease-*'* | *'.lease-?'* | *'.lease-['* | *'.lease-{'* | *'.lease-$'* | *'.lease-`'* \
        | *'.mutex-*'* | *'.mutex-?'* | *'.mutex-['* | *'.mutex-{'* | *'.mutex-$'* | *'.mutex-`'* \
        | *'.nightshift/*'* | *'.nightshift/.*'* | *'.nightshift/.?'* \
        | *'{'*'shift-lease'* | *'{'*'lease-lock'* | *'{'*'mutex-scope'* ) return 0 ;;
    esac
    # A glob across the runtime directory reaches the lease as surely as one across the root.
    if ns_hardhat_control_dir_rel run_rel; then
      case "$normalized" in
        *"$run_rel/"'*'* | *"$run_rel/.*"* | *"$run_rel/.?"*) return 0 ;;
      esac
    fi
  fi
  # The delete verb must target .nightshift itself. `cd .nightshift && unlink .shift-armed`
  # is a control-file write, not `rm .nightshift`.
  if printf '%s' "$normalized" |
      grep -qE '(^|[;&|()[:space:]])(rm|rmdir|unlink|mv)[[:space:]]+([^;&|\n]*[[:space:]]+)?(\./)?\.nightshift/?([;&|()[:space:]]|$)'; then
    return 0
  fi
  if printf '%s' "$normalized" | grep -qE '(^|[;&|[:space:]])find([[:space:]]|$)' \
    && printf '%s' "$normalized" | grep -qE '(^|[[:space:]])(\./)?\.nightshift/?([[:space:]]|$)' \
    && printf '%s' "$normalized" | grep -qE '(^|[[:space:]])(-delete|-exec)([[:space:]]|$)'; then
    return 0
  fi
  return 1
}

ns_hardhat_payload_targets() { # $1 = tool, $2 = raw payload, $3 = command/patch, $4 = predicate
  local targets="" predicate="$4" decoder="" encoded record_type
  case "$1" in
    Bash | PowerShell | Shell)
      "$predicate" "$3"
      return
      ;;
    apply_patch)
      targets="$(printf '%s\n' "$3" \
        | grep -E '^\*\*\* (Add|Update|Delete) File:|^\*\*\* Move to:' 2>/dev/null || true)"
      ;;
    *)
      if [ "${NS_HARDHAT_TARGETS_FOR:-}" = "$2" ]; then
        targets="${NS_HARDHAT_TARGETS:-}"
        decoder="${NS_HARDHAT_TARGETS_DECODER:-}"
      elif command -v jq >/dev/null 2>&1; then
        targets="$(printf '%s' "$2" | jq -r '
          def string_values: .. | strings;
          .tool_input
          | ..
          | objects
          | . as $object
          | (
              (
                $object
                | to_entries[]
                | select(.key | test("((^|_)(path|filepath|file|filename|directory|dir|uri|name)$|^(target|destination|dest|source|src)$)"; "i"))
                | .value
                | string_values
                | if contains("\n") then @base64 | "P\t\(.)" else "p\t\(.)" end
              ),
              (
                $object
                | to_entries[]
                | select(.key | test("(^|_)(command|cmd|script)$"; "i"))
                | .value
                | string_values
                | if contains("\n") then @base64 | "C\t\(.)" else "c\t\(.)" end
              ),
              (
                [$object | to_entries[] | select(.key | test("^(directory|dir)$"; "i")) | .value | string_values] as $dirs
                | [$object | to_entries[] | select(.key | test("^(name|filename|file)$"; "i")) | .value | string_values] as $names
                | $dirs[] as $dir
                | $names[] as $name
                | "\($dir)/\($name)"
                | if contains("\n") then @base64 | "P\t\(.)" else "p\t\(.)" end
              )
            )
        ' 2>/dev/null)" || return 2
        decoder=jq
      else
        # No general Bash JSON parser. Missing jq fails closed.
        return 2
      fi
      NS_HARDHAT_TARGETS_FOR="$2"
      NS_HARDHAT_TARGETS="$targets"
      NS_HARDHAT_TARGETS_DECODER="$decoder"
      ;;
  esac
  while IFS= read -r target; do
    [ -n "$target" ] || continue
    if [ -n "$decoder" ]; then
      case "$target" in
        p$'\t'*) target="${target#*$'\t'}" ;;
        c$'\t'*) target="$(ns_hardhat_scrub "${target#*$'\t'}")" ;;
        P$'\t'* | C$'\t'*)
          record_type="${target%%$'\t'*}"
          encoded="${target#*$'\t'}"
          if [ "$decoder" = "jq" ]; then
            target="$(
              printf '%s' "$encoded" | jq -jRr '@base64d' 2>/dev/null || exit
              printf '\034'
            )" || return 2
          else
            return 2
          fi
          target="${target%$'\034'}"
          if [ "$record_type" = "C" ]; then
            target="$(
              ns_hardhat_scrub "$target"
              printf '\034'
            )"
            target="${target%$'\034'}"
          fi
          ;;
        *) return 2 ;;
      esac
    fi
    if "$predicate" "$target"; then return 0; fi
  done <<<"$targets"
  return 1
}

# Bound-worker control plane: forge/delete of the files the gate keys off. The shift policy, the
# remembered defaults and the derived deadline join them: tonight's authority is written before
# arming, so an armed agent that could rewrite it could widen its own permissions.
# punch-list.md may be edited; only a delete/rename of that file is denied, and so is a delete or
# move of the directory the layout keeps the runtime's control files in, which takes them all.
# Regex is a pre-filter. A write is a hit only when the target's canonical absolute path is the
# control file's path in this layout, so // /./ /../ backslashes and absolute twins cannot slip past.
ns_hardhat_control_prefilter() {
  printf '%s' "$1" | grep -qE \
    '(STOP|\.shift-armed|\.ended|\.shift-session|\.shift-worker|work-target|work-mode|shift-policy\.json|shift-defaults\.json|deadline|punch-list\.md)' \
    && return 0
  ns_hardhat_control_dir_word_in "$1" && ns_hardhat_control_delete_verb "$1"
}

# ns_hardhat_control_key <file name> — the layout key of a control file, printed.
ns_hardhat_control_key() {
  case "$1" in
    STOP) printf 'stop' ;;
    .shift-armed) printf 'armed' ;;
    .ended) printf 'ended' ;;
    .shift-session) printf 'session' ;;
    .shift-worker) printf 'worker' ;;
    work-target | work-mode | deadline) printf '%s' "$1" ;;
    shift-policy.json) printf 'shift-policy' ;;
    shift-defaults.json) printf 'shift-defaults' ;;
    punch-list.md) printf 'punch-list' ;;
    *) return 1 ;;
  esac
}

# The runtime directory of this layout (run/ in layout 2), as a path relative to the state
# directory; status 1 in a layout that keeps its control files at the top.
ns_hardhat_control_dir_rel() {
  [ -n "${NS:-}" ] || return 1
  ns_layout_rel_set "$1" "$NS" run
}

# True when the command names the runtime directory as a word of its own, bare or under a path.
ns_hardhat_control_dir_word_in() {
  local rel
  ns_hardhat_control_dir_rel rel || return 1
  printf '%s' "$1" | grep -qE "(^|[/[:space:];&|()])$rel/?([[:space:];&|()]|\$)"
}

# True when <candidate> is the runtime directory's bare name, as a command run inside the state
# directory would spell it.
ns_hardhat_control_dir_bare() {
  local rel cand="${1#./}"
  ns_hardhat_control_dir_rel rel || return 1
  [ "${cand%/}" = "$rel" ]
}

# True when <canonical> is the runtime directory itself.
ns_hardhat_control_dir_hit() {
  local rel exp
  ns_hardhat_control_dir_rel rel || return 1
  exp="$(ns_hardhat_canon_write_target "$NS/$rel")" || return 1
  [ "$1" = "$exp" ]
}

ns_hardhat_control_delete_verb() {
  printf '%s' "$1" | grep -qE '(^|[;&|()[:space:]])(rm|rmdir|unlink|mv|Remove-Item|Move-Item|Rename-Item)([[:space:]]|$)'
}

ns_hardhat_control_bare_name() {
  case "$1" in
    STOP | .shift-armed | .ended | .shift-session | .shift-worker | work-target | work-mode \
      | shift-policy.json | shift-defaults.json | deadline | punch-list.md \
      | ./STOP | ./.shift-armed | ./.ended | ./.shift-session | ./.shift-worker \
      | ./work-target | ./work-mode | ./shift-policy.json | ./shift-defaults.json \
      | ./deadline | ./punch-list.md)
      return 0
      ;;
  esac
  return 1
}

# Lexically collapse // /./ /../ on an absolute slash path. Directory need not exist.
ns_hardhat_lex_abs() {
  local p="$1" out="" part rest
  case "$p" in /*) ;; *) return 1 ;; esac
  rest="${p#/}"
  while [ -n "$rest" ]; do
    case "$rest" in
      */*)
        part="${rest%%/*}"
        rest="${rest#*/}"
        ;;
      *)
        part="$rest"
        rest=""
        ;;
    esac
    case "$part" in
      '' | .) continue ;;
      ..) out="${out%/*}" ;;
      *) out="$out/$part" ;;
    esac
  done
  [ -n "$out" ] || out=/
  printf '%s' "$out"
}

# Canonical absolute path of a write target. The leaf need not exist; directory
# symlinks are followed. Relative paths resolve against CWD, then PROJECT_DIR.
ns_hardhat_canon_write_target() {
  local p="$1" base dir leaf
  [ -n "$p" ] || return 1
  p="$(printf '%s' "$p" | sed "s#\\\\#/#g; s#[\"']##g")"
  [ -n "$p" ] || return 1
  case "$p" in
    /*) ;;
    *)
      if [ -n "${CWD:-}" ]; then
        base="${CWD%/}"
      elif [ -n "${PROJECT_DIR:-}" ]; then
        base="${PROJECT_DIR%/}"
      else
        return 1
      fi
      case "$base" in
        /*) ;;
        *)
          [ -n "${PROJECT_DIR:-}" ] || return 1
          base="${PROJECT_DIR%/}/$base"
          ;;
      esac
      p="$base/$p"
      ;;
  esac
  p="$(ns_hardhat_lex_abs "$p")" || return 1
  if [ "$p" = / ]; then
    printf '/'
    return 0
  fi
  leaf="${p##*/}"
  dir="${p%/*}"
  [ -n "$dir" ] || dir=/
  dir="$(cd -P "$dir" >/dev/null 2>&1 && pwd -P)" || return 1
  if [ "$dir" = / ]; then
    printf '/%s' "$leaf"
  else
    printf '%s/%s' "$dir" "$leaf"
  fi
}

# Absolute lexical path for walking components. Does not follow symlinks.
ns_hardhat_lex_write_target() {
  local p="$1" base
  [ -n "$p" ] || return 1
  p="$(printf '%s' "$p" | sed "s#\\\\#/#g; s#[\"']##g")"
  [ -n "$p" ] || return 1
  case "$p" in
    /*) ;;
    *)
      if [ -n "${CWD:-}" ]; then
        base="${CWD%/}"
      elif [ -n "${PROJECT_DIR:-}" ]; then
        base="${PROJECT_DIR%/}"
      else
        return 1
      fi
      case "$base" in
        /*) ;;
        *)
          [ -n "${PROJECT_DIR:-}" ] || return 1
          base="${PROJECT_DIR%/}/$base"
          ;;
      esac
      p="$base/$p"
      ;;
  esac
  ns_hardhat_lex_abs "$p"
}

ns_hardhat_rules_expected() {
  [ -n "${NS:-}" ] || return 1
  ns_hardhat_canon_write_target "$(ns_layout_path "$NS" rules)"
}

ns_hardhat_parking_expected() {
  [ -n "${NS:-}" ] || return 1
  ns_hardhat_canon_write_target "$(ns_layout_path "$NS" parking-lot)"
}

# Follow one symlink hop to a canonical write target. Missing or non-link: empty.
ns_hardhat_follow_symlink() {
  local p="$1" dest dir
  [ -n "$p" ] && [ -L "$p" ] || return 1
  dest="$(readlink "$p")" || return 1
  [ -n "$dest" ] || return 1
  case "$dest" in
    /*) ns_hardhat_canon_write_target "$dest" ;;
    *)
      dir="${p%/*}"
      [ -n "$dir" ] || dir=/
      ns_hardhat_canon_write_target "$dir/$dest"
      ;;
  esac
}

# True when this write target, or any symlink/junction on the way to it, is the rules file.
ns_hardhat_write_target_reaches_rules() {
  local raw="$1" rules lex acc rest part hop canon
  rules="$(ns_hardhat_rules_expected)" || return 1
  canon="$(ns_hardhat_canon_write_target "$raw")" || return 1
  [ "$canon" = "$rules" ] && return 0
  hop="$(ns_hardhat_follow_symlink "$canon")" && [ "$hop" = "$rules" ] && return 0
  lex="$(ns_hardhat_lex_write_target "$raw")" || return 1
  rest="${lex#/}"
  acc=""
  while [ -n "$rest" ]; do
    case "$rest" in
      */*)
        part="${rest%%/*}"
        rest="${rest#*/}"
        ;;
      *)
        part="$rest"
        rest=""
        ;;
    esac
    case "$part" in
      '' | .) continue ;;
      ..) acc="${acc%/*}" ;;
      *) acc="$acc/$part" ;;
    esac
    [ -n "$acc" ] || acc=/
    hop="$(ns_hardhat_follow_symlink "$acc")" || continue
    [ "$hop" = "$rules" ] && return 0
  done
  return 1
}

# Sole target is the live parking lot, and no intermediate reaches the rules file.
ns_hardhat_is_real_parking_lot() {
  local raw="$1" canon parking
  ns_hardhat_write_target_reaches_rules "$raw" && return 1
  parking="$(ns_hardhat_parking_expected)" || return 1
  canon="$(ns_hardhat_canon_write_target "$raw")" || return 1
  [ "$canon" = "$parking" ]
}

# Print the >> target of a sole literal append. Anything else fails.
# No general shell parser: substitutions, lists, pipes, extra redirects, and
# globs keep today's refusal.
ns_hardhat_literal_append_target() {
  local s="$1"
  local i=0 n q="" c next
  local appends=0 extra_redir=0 extras=0
  local target="" collecting=0 word="" first=""
  n=${#s}
  while [ "$i" -lt "$n" ]; do
    c="${s:$i:1}"
    next="${s:$((i + 1)):1}"
    if [ -n "$q" ]; then
      if [ "$c" = $'\\' ] && [ "$q" = '"' ]; then
        i=$((i + 2))
        continue
      fi
      if [ "$c" = "$q" ]; then
        q=""
        i=$((i + 1))
        continue
      fi
      if [ "$q" = '"' ]; then
        case "$c" in
          '$' | '`') return 1 ;;
        esac
      fi
      i=$((i + 1))
      continue
    fi
    case "$c" in
      "'" | '"')
        if [ "$collecting" -eq 1 ]; then
          q="$c"
          i=$((i + 1))
          while [ "$i" -lt "$n" ] && [ "${s:$i:1}" != "$q" ]; do
            if [ "$q" = '"' ]; then
              case "${s:$i:1}" in
                '$' | '`') return 1 ;;
              esac
            fi
            word="$word${s:$i:1}"
            i=$((i + 1))
          done
          [ "$i" -ge "$n" ] && return 1
          target="$word"
          collecting=0
          word=""
          q=""
          i=$((i + 1))
          continue
        fi
        q="$c"
        i=$((i + 1))
        continue
        ;;
      '$' | '`' | '(') return 1 ;;
    esac
    if [ "$c" = '>' ] && [ "$next" = '>' ]; then
      appends=$((appends + 1))
      collecting=1
      word=""
      i=$((i + 2))
      while [ "$i" -lt "$n" ]; do
        case "${s:$i:1}" in
          ' ' | $'\t') i=$((i + 1)) ;;
          *) break ;;
        esac
      done
      continue
    fi
    if [ "$c" = '>' ]; then
      extra_redir=$((extra_redir + 1))
      i=$((i + 1))
      continue
    fi
    if [ "$c" = '<' ] && [ "$next" = '<' ]; then
      collecting=0
      i=$((i + 2))
      continue
    fi
    if [ "$c" = '<' ]; then
      extra_redir=$((extra_redir + 1))
      i=$((i + 1))
      continue
    fi
    case "$c" in
      '|' | ';')
        extras=$((extras + 1))
        collecting=0
        i=$((i + 1))
        continue
        ;;
      '&')
        extras=$((extras + 1))
        collecting=0
        if [ "$next" = '&' ]; then
          i=$((i + 2))
        else
          i=$((i + 1))
        fi
        continue
        ;;
    esac
    if [ "$collecting" -eq 1 ]; then
      case "$c" in
        ' ' | $'\t' | $'\n')
          if [ -n "$word" ]; then
            target="$word"
            collecting=0
            word=""
          fi
          i=$((i + 1))
          continue
          ;;
      esac
      word="$word$c"
      i=$((i + 1))
      continue
    fi
    i=$((i + 1))
  done
  [ -n "$q" ] && return 1
  [ "$appends" -eq 1 ] || return 1
  [ "$extra_redir" -eq 0 ] || return 1
  [ "$extras" -eq 0 ] || return 1
  [ -n "$target" ] || target="$word"
  [ -n "$target" ] || return 1
  case "$target" in
    *'*'* | *'?'* | *'['*) return 1 ;;
  esac
  first="${s#"${s%%[![:space:]]*}"}"
  first="${first%%[[:space:]]*}"
  case "$first" in
    echo | printf | cat) ;;
    \' | \'*) ;;
    \" | \"*) ;;
    *) return 1 ;;
  esac
  printf '%s' "$target"
}

ns_hardhat_command_is_inert_parking_append() {
  local target
  target="$(ns_hardhat_literal_append_target "$1")" || return 1
  ns_hardhat_is_real_parking_lot "$target"
}

ns_hardhat_collect_one() {
  NS_HARDHAT_COLLECTED="${NS_HARDHAT_COLLECTED}$1$NS_HARDHAT_NL"
  return 1
}

ns_hardhat_file_tool_is_inert_parking() {
  local t count=0
  NS_HARDHAT_COLLECTED=""
  ns_hardhat_payload_targets "$1" "$2" "$3" ns_hardhat_collect_one
  while IFS= read -r t; do
    [ -n "$t" ] || continue
    count=$((count + 1))
    ns_hardhat_is_real_parking_lot "$t" || return 1
  done <<EOF
$NS_HARDHAT_COLLECTED
EOF
  [ "$count" -eq 1 ]
}

ns_hardhat_is_inert_parking_lot_write() {
  case "$1" in
    Bash | PowerShell | Shell)
      ns_hardhat_command_is_inert_parking_append "$3"
      ;;
    *)
      ns_hardhat_file_tool_is_inert_parking "$1" "$2" "$3"
      ;;
  esac
}

ns_hardhat_control_expected() { # <key>
  local path
  [ -n "${NS:-}" ] || return 1
  ns_layout_set path "$NS" "$1" || return 1
  ns_hardhat_canon_write_target "$path"
}

ns_hardhat_control_rewrite_hit() {
  local key exp
  while IFS= read -r key; do
    [ -n "$key" ] || continue
    exp="$(ns_hardhat_control_expected "$key")" || continue
    [ "$1" = "$exp" ] && return 0
  done <<'EOF'
stop
armed
ended
session
worker
work-target
work-mode
shift-policy
shift-defaults
deadline
EOF
  return 1
}

ns_hardhat_control_list_hit() {
  local exp
  exp="$(ns_hardhat_control_expected punch-list)" || return 1
  [ "$1" = "$exp" ]
}

ns_hardhat_control_candidates() {
  printf '%s\n' "$1" | tr ';|&()<>' '\n' | tr -s '[:space:]' '\n'
}

ns_hardhat_control_candidate_hits() {
  local cand="$1" full="$2" canon leaf rel
  leaf="${cand##*/}"
  leaf="${leaf#./}"
  if ns_hardhat_control_bare_name "$cand" && ns_hardhat_nightshift_dir_context "$full"; then
    canon="$(ns_hardhat_control_expected "$(ns_hardhat_control_key "$leaf")")" || return 1
  elif ns_hardhat_control_dir_bare "$cand" && ns_hardhat_nightshift_dir_context "$full"; then
    ns_hardhat_control_dir_rel rel || return 1
    canon="$(ns_hardhat_canon_write_target "$NS/$rel")" || return 1
  else
    canon="$(ns_hardhat_canon_write_target "$cand")" || return 1
  fi
  ns_hardhat_control_rewrite_hit "$canon" && return 0
  { ns_hardhat_control_list_hit "$canon" || ns_hardhat_control_dir_hit "$canon"; } \
    && ns_hardhat_control_delete_verb "$full"
}

ns_hardhat_control_targeted() {
  local normalized candidate
  normalized="$(printf '%s' "$1" | sed "s#\\\\#/#g; s#[\"']##g")"
  ns_hardhat_control_prefilter "$normalized" || return 1
  [ -n "${NS:-}" ] || return 1
  if ! printf '%s' "$normalized" | grep -qE '[[:space:];&|<>()]'; then
    ns_hardhat_control_candidate_hits "$normalized" "$normalized"
    return
  fi
  while IFS= read -r candidate; do
    [ -n "$candidate" ] || continue
    ns_hardhat_control_candidate_hits "$candidate" "$normalized" && return 0
  done <<EOF
$(ns_hardhat_control_candidates "$normalized")
EOF
  return 1
}

ns_hardhat_payload_targets_control() {
  case "$1" in
    Read | Grep | Glob | LS | WebFetch | WebSearch | Task | TodoWrite | AskQuestion | AskUserQuestion | request_user_input | NotebookRead)
      return 1
      ;;
    *read* | *Read*)
      return 1
      ;;
  esac
  ns_hardhat_payload_targets "$1" "$2" "$3" ns_hardhat_control_targeted
}

# True when a collected file-tool path, or a sole literal append, reaches the rules file.
ns_hardhat_payload_write_reaches_rules() {
  local t
  case "$1" in
    Bash | PowerShell | Shell)
      t="$(ns_hardhat_literal_append_target "$3")" || return 1
      ns_hardhat_write_target_reaches_rules "$t"
      ;;
    *)
      NS_HARDHAT_COLLECTED=""
      ns_hardhat_payload_targets "$1" "$2" "$3" ns_hardhat_collect_one
      while IFS= read -r t; do
        [ -n "$t" ] || continue
        ns_hardhat_write_target_reaches_rules "$t" && return 0
      done <<EOF
$NS_HARDHAT_COLLECTED
EOF
      return 1
      ;;
  esac
}

ns_hardhat_payload_targets_rules() {
  if ns_hardhat_is_inert_parking_lot_write "$1" "$2" "$3"; then
    return 1
  fi
  ns_hardhat_payload_write_reaches_rules "$1" "$2" "$3" && return 0
  ns_hardhat_payload_targets "$1" "$2" "$3" ns_hardhat_rules_targeted
}

ns_hardhat_payload_targets_lease() {
  local rc
  ns_hardhat_payload_targets "$1" "$2" "$3" ns_hardhat_lease_targeted
  rc=$?
  [ "$rc" -eq 0 ] && return 0
  [ "$rc" -eq 2 ] || return 1
  # If toolDeny cannot be read exactly, unknown local tools fail closed rather than letting a
  # helper conversation address the lease through an opaque payload.
  case "$1" in
    AskQuestion | AskUserQuestion | request_user_input | WebFetch | WebSearch | Task | TodoWrite) return 1 ;;
    *) return 0 ;;
  esac
}

NS_HARDHAT_NL='
'
NS_HARDHAT_STRIPPED=""

# ns_hardhat_heredoc_is_data_write <opening-line> — true when the here-document this line opens
# is written to a file rather than handed to something that runs it.
#
# Quoting a delimiter stops the outer shell from expanding the body. It does not stop `bash` from
# executing what it reads, so the exemption has to know the consumer, not just the quoting. Only
# `cat` and `tee` qualify, and only on a line that is one plain command with one here-document:
# no pipeline, no command list, no background, no substitution, and no `$` at all, so the file
# being written is literally on the line the guards already inspect.
ns_hardhat_heredoc_is_data_write() {
  local line="$1" trimmed word
  case "$line" in
    *'<<'*'<<'*) return 1 ;;
  esac
  case "$line" in
    *'|'* | *';'* | *'&'* | *'`'* | *'$'* | *'('*) return 1 ;;
  esac
  trimmed="${line#"${line%%[! 	]*}"}"
  word="${trimmed%%[ 	]*}"
  case "$word" in
    cat | tee) return 0 ;;
    *) return 1 ;;
  esac
}

# ns_hardhat_strip_quoted_heredocs <command> — the same command with the body of every quoted
# here-document that is written to a file replaced by a placeholder.
#
# A quoted delimiter on a `cat` or `tee` write means the words inside are the file being written
# rather than a command. Reading that body as code makes the product unwritable from inside a
# shift: a page explaining the elevation categories reads as a request for one, and a note naming
# a control file reads as an attempt to rewrite it.
#
# Every other consumer keeps its body under inspection. `bash <<'EOF'`, a producer piped into an
# interpreter, `eval`, `source` and anything the classifier does not recognise are all commands
# that run their input, and a quoted delimiter changes nothing about that.
#
# The line that opens the heredoc is kept either way, so the redirection target is inspected
# whatever the body says — writing a protected file is caught by where it writes, not by what it
# says. An unquoted delimiter is left alone, because that body is expanded and a command
# substitution in it really does run. So is an unterminated one: a body with no visible end is
# not skipped.
ns_hardhat_strip_quoted_heredocs() {
  local input="$1" out="" line delim="" body_open=0 strip_body=0 found stripped
  NS_HARDHAT_STRIPPED="$input"
  case "$input" in
    *'<<'*) ;;
    *) return 0 ;;
  esac
  while IFS= read -r line || [ -n "$line" ]; do
    if [ "$body_open" -eq 1 ]; then
      stripped="${line#"${line%%[! 	]*}"}"
      if [ "$stripped" = "$delim" ] || [ "$line" = "$delim" ]; then
        body_open=0
        out="$out$line$NS_HARDHAT_NL"
        continue
      fi
      [ "$strip_body" -eq 1 ] || out="$out$line$NS_HARDHAT_NL"
      continue
    fi
    out="$out$line$NS_HARDHAT_NL"
    found="$(printf '%s' "$line" | sed -n \
      -e "s/.*<<-\{0,1\}[[:space:]]*'\([^']*\)'.*/\1/p" \
      -e 's/.*<<-\{0,1\}[[:space:]]*"\([^"]*\)".*/\1/p' | head -n1)"
    if [ -n "$found" ]; then
      delim="$found"
      body_open=1
      if ns_hardhat_heredoc_is_data_write "$line"; then
        strip_body=1
        out="${out}NIGHTSHIFT_HEREDOC_BODY$NS_HARDHAT_NL"
      else
        strip_body=0
      fi
    fi
  done <<NS_HEREDOC_SCAN
$input
NS_HEREDOC_SCAN
  if [ "$body_open" -eq 1 ]; then
    return 0
  fi
  case "$input" in
    *"$NS_HARDHAT_NL") NS_HARDHAT_STRIPPED="$out" ;;
    *) NS_HARDHAT_STRIPPED="${out%"$NS_HARDHAT_NL"}" ;;
  esac
}

ns_hardhat_scrub() {
  ns_hardhat_strip_quoted_heredocs "$1"
  ns_hardhat_scrub_options "$NS_HARDHAT_STRIPPED"
}

ns_hardhat_scrub_options() {
  local input="$1" output="" length i=0 j k option_length quote char previous next dynamic closed start
  length="${#input}"
  while [ "$i" -lt "$length" ]; do
    start=$i
    option_length=0
    while [ "$i" -lt "$length" ]; do
      if [ "$i" -eq 0 ]; then previous=""; else previous="${input:$((i - 1)):1}"; fi
      option_length=0
      case "$previous" in
        '' | ' ' | $'\t' | $'\n')
          if [ "${input:$i:9}" = "--message" ]; then
            next="${input:$((i + 9)):1}"
            case "$next" in '' | '=' | ' ' | $'\t' | $'\n' | "'" | '"') option_length=9 ;; esac
          elif [ "${input:$i:2}" = "-m" ]; then
            next="${input:$((i + 2)):1}"
            case "$next" in '' | '=' | ' ' | $'\t' | $'\n' | "'" | '"') option_length=2 ;; esac
          fi
          ;;
      esac
      [ "$option_length" -gt 0 ] && break
      i=$((i + 1))
    done
    if [ "$i" -gt "$start" ]; then
      output="${output}${input:$start:$((i - start))}"
    fi
    [ "$i" -ge "$length" ] && break
    [ "$option_length" -gt 0 ] || continue

    j=$((i + option_length))
    [ "${input:$j:1}" = "=" ] && j=$((j + 1))
    while [ "$j" -lt "$length" ]; do
      case "${input:$j:1}" in ' ' | $'\t' | $'\n') j=$((j + 1)) ;; *) break ;; esac
    done
    quote="${input:$j:1}"
    case "$quote" in
      "'")
        k=$((j + 1))
        while [ "$k" -lt "$length" ] && [ "${input:$k:1}" != "'" ]; do k=$((k + 1)); done
        if [ "$k" -lt "$length" ]; then
          output="${output}${input:$i:$((j - i))}MSG"
          i=$((k + 1))
          continue
        fi
        ;;
      '"')
        k=$((j + 1))
        dynamic=0
        closed=0
        while [ "$k" -lt "$length" ]; do
          char="${input:$k:1}"
          if [ "$char" = "\\" ]; then
            k=$((k + 2))
            continue
          fi
          if [ "$char" = '"' ]; then closed=1; break; fi
          if [ "$char" = '`' ] \
            || { [ "$char" = '$' ] && [ "${input:$((k + 1)):1}" = '(' ]; }; then
            dynamic=1
          fi
          k=$((k + 1))
        done
        if [ "$closed" -eq 1 ]; then
          if [ "$dynamic" -eq 1 ]; then
            output="${output}${input:$i:$((k - i + 1))}"
          else
            output="${output}${input:$i:$((j - i))}MSG"
          fi
          i=$((k + 1))
          continue
        fi
        ;;
    esac
    output="${output}${input:$i:1}"
    i=$((i + 1))
  done
  printf '%s' "$output"
}

ns_hardhat_rules_has() {
  [ -n "${TOOL_RULES:-}" ] || return 1
  ns_rules_map_has "$TOOL_RULES" "$1"
}

ns_hardhat_rules_msg() {
  ns_rules_map_msg "$TOOL_RULES" "$1"
}

ns_hardhat_tool_deny_broken() {
  [ -n "${TOOL_RULES:-}" ] || return 1
  case "$TOOL_RULES" in
    __nightshift_invalid_tool_rules__ | __nightshift_tool_rules_parser_missing__) return 0 ;;
  esac
  ! ns_rules_map_parse "$TOOL_RULES"
}

ns_hardhat_tool_deny_reason() {
  local m
  [ -n "$1" ] && ns_hardhat_rules_has "$1" || return 1
  m="$(ns_hardhat_rules_msg "$1")"
  [ -n "$m" ] || return 1
  printf '%s' "$m"
}

ns_hardhat_required_tool_deny_reason() {
  if ! ns_hardhat_rules_has "$1"; then
    printf '%s' "BLOCKED: toolDeny is missing the required '$1' entry. Add that exact host tool name to $(ns_hardhat_state_name rules) with a denial message, or use an empty string to allow it; run Setup again (/nightshift:setup on Claude Code; ask Nightshift to set up on Codex) to review the current template."
    return 0
  fi
  ns_hardhat_tool_deny_reason "$1"
}

ns_hardhat_git_verb() {
  printf '%s' "$1" | grep -qE "(^|[^[:alnum:]_-])git([^[:alnum:]]|$)" \
    && printf '%s' "$1" | grep -qE "(^|[^[:alnum:]_-])$2([^[:alnum:]]|$)"
}

ns_hardhat_is_git_write() {
  ns_hardhat_git_verb "$1" add || ns_hardhat_git_verb "$1" commit \
    || ns_hardhat_git_verb "$1" tag || ns_hardhat_git_verb "$1" remote
}

ns_hardhat_is_commit() {
  ns_hardhat_git_verb "$1" commit
}

# Print a simple quoted payload after eval or a shell -c, or nothing.
# Hardening only: one pair of quotes, no nested parser, not a sandbox.
ns_hardhat_elevation_inner() {
  local s="$1" inner
  printf '%s' "$s" | grep -qE '(^|[;&|()[:space:]])(eval|[A-Za-z0-9./_-]*sh[[:space:]]+-[a-zA-Z]*c)[[:space:]]' || return 1
  inner="$(printf '%s' "$s" | sed -n "s/.*eval[[:space:]]\{1,\}'\([^']*\)'.*/\1/p")"
  [ -n "$inner" ] && { printf '%s' "$inner"; return 0; }
  inner="$(printf '%s' "$s" | sed -n 's/.*eval[[:space:]]\{1,\}"\([^"]*\)".*/\1/p')"
  [ -n "$inner" ] && { printf '%s' "$inner"; return 0; }
  inner="$(printf '%s' "$s" | sed -n "s/.*[A-Za-z0-9./_-]*sh[[:space:]]\{1,\}-[a-zA-Z]*c[[:space:]]\{1,\}'\([^']*\)'.*/\1/p")"
  [ -n "$inner" ] && { printf '%s' "$inner"; return 0; }
  inner="$(printf '%s' "$s" | sed -n 's/.*[A-Za-z0-9./_-]*sh[[:space:]]\{1,\}-[a-zA-Z]*c[[:space:]]\{1,\}"\([^"]*\)".*/\1/p')"
  [ -n "$inner" ] && { printf '%s' "$inner"; return 0; }
  return 1
}

# Print a deny reason when the command needs an elevation category this shift does not allow,
# or return 1 to allow. Uses globals: SCRUBBED PROJECT_DIR
#
# Elevation gates creating system state, never using what already runs: the category patterns come
# from rules.elevation (or the shipped defaults) through ns_policy_elevation_pattern, which the
# permission preflight reads too, so the guard and the preflight can never disagree about what a
# command needs. Whether tonight lifts a deny is the resolver's answer alone — ns_policy_allowed
# carries the whole precedence table, including the exact-plan binding — so this reads a status
# and writes the sentence the agent acts on. A pattern the owner broke denies rather than lapses.
# A simple eval / sh -c quoted payload is matched as well as the outer text; that is hardening,
# not isolation — a determined rewrite still gets through.
ns_hardhat_elevation_reason() {
  local _cat _pat _rc _reason _patterns _subject _inner
  _subject="$SCRUBBED"
  _inner="$(ns_hardhat_elevation_inner "$SCRUBBED" || true)"
  [ -n "$_inner" ] && _subject="$SCRUBBED $_inner"
  # One parse of the rules for all five categories; each line is category<TAB>pattern.
  _patterns="$(ns_policy_elevation_patterns "$PROJECT_DIR")" || return 1
  while IFS="$(printf '\t')" read -r _cat _pat; do
    if [ -z "$_cat" ] || [ -z "$_pat" ]; then
      continue
    fi
    valid_ere "$_pat" || {
      printf '%s' "BLOCKED: elevation.$_cat.pattern is not a valid extended regular expression, so the guard it configures cannot run. Fix the pattern in $(ns_hardhat_state_name rules)."
      return 0
    }
    printf '%s' "$_subject" | grep -qE "$_pat" || continue
    ns_policy_allowed "$PROJECT_DIR" "$_cat" "$SCRUBBED"
    _rc=$?
    [ "$_rc" -eq 0 ] && continue
    _reason="BLOCKED: this command needs the '$_cat' elevation category, which is denied for this shift."
    if [ "$_rc" -eq 2 ]; then
      printf '%s' "$_reason An exact-plan allowance exists but this command is not one of its approved commands."
    else
      printf '%s' "$_reason The owner allows it in $(ns_hardhat_state_name rules) (elevation.$_cat.policy) or for one shift in shift-policy.json before arming. Park the item in $(ns_hardhat_state_name parking-lot) as \"needs allowance: $_cat\" and keep working."
    fi
    return 0
  done <<EOF
$_patterns
EOF
  return 1
}

# Print a deny reason for a Bash-like command, or return 1 to allow.
# Uses globals: SCRUBBED CMD CWD PROJECT_DIR PROTECTED_DIRS EXPECTED_EMAIL
# NEVER_COMMIT_PATTERNS FORBIDDEN_COMMANDS
ns_hardhat_command_reason() {
  local _p _name _pat d _tok _dirs _toks REPO email _scope _diff _verb _pathfile _pathrc _hit
  for _p in "FORBIDDEN_COMMANDS:$FORBIDDEN_COMMANDS" "NEVER_COMMIT_PATTERNS:$NEVER_COMMIT_PATTERNS"; do
    _name="${_p%%:*}"
    _pat="${_p#*:}"
    [ -n "$_pat" ] || continue
    valid_ere "$_pat" || {
      printf '%s' "BLOCKED: NIGHTSHIFT_$_name is not a valid extended regular expression, so the guard it configures cannot run. Fix the pattern in your session settings."
      return 0
    }
  done

  if [ -n "$PROTECTED_DIRS" ] && ns_hardhat_is_git_write "$SCRUBBED"; then
    _verb=""
    ns_hardhat_git_verb "$SCRUBBED" add && _verb=add
    ns_hardhat_git_verb "$SCRUBBED" commit && _verb=commit
    if [ -n "$_verb" ]; then
      if printf '%s' "$SCRUBBED" | grep -qE -- '--git-dir|--work-tree'; then
        printf '%s' "BLOCKED: --git-dir/--work-tree point this $_verb somewhere the protected-directory guard cannot verify. Run it from inside the repository instead."
        return 0
      fi
      REPO="$(target_repo "$CMD" "${CWD:-$PROJECT_DIR}")"
      case "$?" in
        1) printf '%s' "BLOCKED: this $_verb names a directory that is not a git repository, so the protected-directory guard cannot inspect it. Do not retry a rephrased form."
           return 0 ;;
        2) REPO="$(repo_root "$PROJECT_DIR" "$CWD")" || REPO="$(ns_work_target "$PROJECT_DIR")" || {
             printf '%s' "BLOCKED: cannot tell which git repository this $_verb targets, so the protected-directory guard cannot run. Run it from inside the repository."
             return 0
           } ;;
      esac
      _pathfile="$(mktemp "${TMPDIR:-/tmp}/ns-pd.XXXXXX")" || {
        printf '%s' "BLOCKED: cannot tell which paths this $_verb would write, so the protected-directory guard cannot run."
        return 0
      }
      ns_git_prospective_paths "$REPO" "$SCRUBBED" "$_verb" >"$_pathfile"
      _pathrc=$?
      if [ "$_pathrc" -eq 2 ]; then
        rm -f "$_pathfile"
        printf '%s' "BLOCKED: this $_verb uses a form the protected-directory guard cannot verify. Do not retry a rephrased form."
        return 0
      fi
      _hit=""
      while IFS= read -r -d '' _p; do
        [ -n "$_p" ] || continue
        if ns_path_under_protected "$_p" "$PROTECTED_DIRS"; then
          _hit="$_p"
          break
        fi
      done <"$_pathfile"
      rm -f "$_pathfile"
      if [ -n "$_hit" ]; then
        IFS=' |' read -ra _dirs <<<"$PROTECTED_DIRS"
        for d in "${_dirs[@]}"; do
          ns_path_under_protected "$_hit" "$d" && break
        done
        printf '%s' "BLOCKED: never git add/commit/tag/remote inside '$d' (a protected directory). Do not retry a rephrased form."
        return 0
      fi
    else
      IFS=' |' read -ra _dirs <<<"$PROTECTED_DIRS"
      read -ra _toks <<<"$SCRUBBED"
      for d in "${_dirs[@]}"; do
        [ -n "$d" ] || continue
        for _tok in "${_toks[@]}"; do
          case "$_tok" in
            "$d" | "$d"/* | */"$d" | */"$d"/* | *="$d" | *="$d"/*)
              printf '%s' "BLOCKED: never git add/commit/tag/remote inside '$d' (a protected directory). Do not retry a rephrased form."
              return 0
              ;;
          esac
        done
      done
    fi
  fi

  if ns_hardhat_is_commit "$SCRUBBED" && { [ -n "$EXPECTED_EMAIL" ] || [ -n "$NEVER_COMMIT_PATTERNS" ]; }; then
    if printf '%s' "$SCRUBBED" | grep -qE -- '--git-dir|--work-tree'; then
      printf '%s' "BLOCKED: --git-dir/--work-tree point this commit somewhere the configured commit guards cannot verify. Run the commit from inside the repository instead."
      return 0
    fi
    if [ -n "$EXPECTED_EMAIL" ] && printf '%s' "$SCRUBBED" |
      grep -qE -- '-c[[:space:]]*user\.email=|--author|GIT_(AUTHOR|COMMITTER)_EMAIL='; then
      printf '%s' "BLOCKED: this commit overrides the author identity on the command line, which the expected-identity guard cannot verify. Commit with the repository's configured identity."
      return 0
    fi
    REPO="$(target_repo "$CMD" "${CWD:-$PROJECT_DIR}")"
    case "$?" in
      1) printf '%s' "BLOCKED: this commit names a directory that is not a git repository, so the configured commit guards cannot inspect it. Do not retry a rephrased form."
         return 0 ;;
      2) REPO="$(repo_root "$PROJECT_DIR" "$CWD")" || REPO="$(ns_work_target "$PROJECT_DIR")" || {
           printf '%s' "BLOCKED: cannot tell which git repository this commit targets, so the configured commit guards cannot run. Run the commit from inside the repository."
           return 0
         } ;;
    esac
    if [ -n "$EXPECTED_EMAIL" ]; then
      email="$(git -C "$REPO" config user.email 2>/dev/null || true)"
      if [ "$email" != "$EXPECTED_EMAIL" ]; then
        printf '%s' "BLOCKED: committer identity ('$email') is not the expected '$EXPECTED_EMAIL'. Fix git config user.email, then retry."
        return 0
      fi
    fi
    if [ -n "$NEVER_COMMIT_PATTERNS" ]; then
      _scope="the diff this commit would write"
      _diff="$(ns_git_prospective_diff "$REPO" "$SCRUBBED")"
      case "$?" in
        2) printf '%s' "BLOCKED: this commit uses a form the never-commit guard cannot verify. Do not retry a rephrased form."
           return 0 ;;
      esac
      if printf '%s' "$_diff" | grep -qiE "$NEVER_COMMIT_PATTERNS"; then
        printf '%s' "BLOCKED: $_scope matches a never-commit pattern. Remove it, restage, retry. Do not weaken the pattern list."
        return 0
      fi
    fi
  fi

  if [ -n "$FORBIDDEN_COMMANDS" ] && printf '%s' "$SCRUBBED" | grep -qE "$FORBIDDEN_COMMANDS"; then
    printf '%s' "BLOCKED: the command matches the owner's forbidden list for this shift. Find another way, or park the task with a note in $(ns_hardhat_state_name parking-lot) and keep working. Do not retry a rephrased form."
    return 0
  fi

  # forbiddenCommands is the owner's own list and stays independent of the categories: a command
  # can clear it and still need an allowance the shift does not hold.
  ns_hardhat_elevation_reason && return 0
  return 1
}

# Split a simple command into tokens. Rejects unmatched quotes. No eval.
ns_hardhat_split_tokens() { # <cmd> → fills __ns_hh_tok
  local s="$1" tok="" quote="" c
  __ns_hh_tok=()
  while [ -n "$s" ]; do
    c="${s%"${s#?}"}"
    s="${s#?}"
    if [ -n "$quote" ]; then
      if [ "$c" = "$quote" ]; then
        quote=""
      else
        tok="${tok}${c}"
      fi
      continue
    fi
    case "$c" in
      \' | \") quote="$c" ;;
      [[:space:]])
        if [ -n "$tok" ]; then
          __ns_hh_tok+=("$tok")
          tok=""
        fi
        ;;
      *) tok="${tok}${c}" ;;
    esac
  done
  [ -z "$quote" ] || return 1
  [ -n "$tok" ] && __ns_hh_tok+=("$tok")
  return 0
}

ns_hardhat_canon_regular_file() { # <path>
  local p="$1" dir
  [ -n "$p" ] || return 1
  [ -f "$p" ] && [ ! -L "$p" ] || return 1
  case "$p" in
    */*) dir="$(cd -P "${p%/*}" >/dev/null 2>&1 && pwd -P)" || return 1 ;;
    *) return 1 ;;
  esac
  printf '%s/%s' "$dir" "${p##*/}"
}

# True when the command is exactly a plugin Stop/Reset/Purge helper for this workspace.
# Invoked after lease targeting and before unbound, so a fenced conversation can recover.
ns_hardhat_trusted_shift_control() { # <cmd> <plugin_root> <workspace>
  local cmd="$1" plugin="$2" workspace="$3"
  local script project="" confirm="" i tok helper expected resolved
  [ -n "$cmd" ] && [ -n "$plugin" ] && [ -n "$workspace" ] || return 1
  case "$cmd" in
    *$'\n'* | *$'\r'*) return 1 ;;
  esac
  printf '%s' "$cmd" | grep -qE '[;&|`$<>]' && return 1
  printf '%s' "$cmd" | grep -q '\$' && return 1
  ns_hardhat_split_tokens "$cmd" || return 1
  [ "${#__ns_hh_tok[@]}" -ge 3 ] || return 1
  i=0
  case "${__ns_hh_tok[0]}" in
    bash | sh | /bin/bash | /usr/bin/bash)
      i=1
      ;;
  esac
  script="${__ns_hh_tok[$i]}"
  script="$(ns_hardhat_canon_regular_file "$script")" || return 1
  plugin="$(cd -P "$plugin" >/dev/null 2>&1 && pwd -P)" || return 1
  expected=""
  for helper in stop-shift.sh reset-shift.sh purge-workspace.sh; do
    tok="$(ns_hardhat_canon_regular_file "$plugin/runtime/$helper")" || continue
    if [ "$script" = "$tok" ]; then
      expected="$helper"
      break
    fi
  done
  [ -n "$expected" ] || return 1
  i=$((i + 1))
  while [ "$i" -lt "${#__ns_hh_tok[@]}" ]; do
    tok="${__ns_hh_tok[$i]}"
    i=$((i + 1))
    case "$tok" in
      --project)
        [ "$i" -lt "${#__ns_hh_tok[@]}" ] || return 1
        project="${__ns_hh_tok[$i]}"
        i=$((i + 1))
        ;;
      --reason)
        [ "$i" -lt "${#__ns_hh_tok[@]}" ] || return 1
        i=$((i + 1))
        ;;
      --confirm-path)
        [ "$i" -lt "${#__ns_hh_tok[@]}" ] || return 1
        confirm="${__ns_hh_tok[$i]}"
        i=$((i + 1))
        ;;
      *) return 1 ;;
    esac
  done
  [ -n "$project" ] || return 1
  case "$project" in /*) ;; *) return 1 ;; esac
  resolved="$(cd -P "$project" 2>/dev/null && pwd)" || return 1
  if [ -e "$resolved/.nightshift-link" ] || [ -L "$resolved/.nightshift-link" ]; then
    resolved="$(ns_workspace_root "$resolved" 2>/dev/null)" || return 1
  fi
  workspace="$(cd -P "$workspace" 2>/dev/null && pwd)" || return 1
  [ "$resolved" = "$workspace" ] || return 1
  if [ "$expected" = purge-workspace.sh ]; then
    [ -n "$confirm" ] || return 1
  fi
  return 0
}
