← Files NightshiftARCHIVED FILE

lib/evidence-lifecycle.sh

4.15 KB · Oct 3, 2026 · 06:31 UTC

↓ Download file

#!/usr/bin/env bash
# evidence-lifecycle.sh — read-only evidence ledger and shift-progress helpers.

# ns_evidence_jsonl <workspace> — absolute path to the live findings ledger.
ns_evidence_jsonl() {
  local dir
  ns_layout_set dir "${1%/}/.nightshift" evidence
  printf '%s/findings.jsonl' "$dir"
}

# ns_gate_checkpoint_token <workspace> — latest checkpoint id for stall fingerprints.
ns_gate_checkpoint_token() {
  local ws="${1:?}" jsonl line id=""
  jsonl="$(ns_evidence_jsonl "$ws")"
  if [ ! -f "$jsonl" ] || [ -L "$jsonl" ]; then
    printf 'none'
    return 0
  fi
  if command -v jq >/dev/null 2>&1; then
    id="$(jq -r 'select(.domain == "checkpoint") | .id' "$jsonl" 2>/dev/null | tail -n1)"
  elif command -v python3 >/dev/null 2>&1; then
    id="$(python3 - "$jsonl" <<'PY'
import json, sys
last = ""
with open(sys.argv[1], encoding="utf-8") as fh:
    for line in fh:
        line = line.strip()
        if not line:
            continue
        try:
            rec = json.loads(line)
        except json.JSONDecodeError:
            continue
        if rec.get("domain") == "checkpoint" and rec.get("id"):
            last = rec["id"]
print(last)
PY
)"
  fi
  [ -n "$id" ] || id=none
  printf '%s' "$id"
}

# ns_evidence_counts <workspace> — one line: findings=N open=O baseline=B checkpoint=C
ns_evidence_counts() {
  local ws="${1:?}" jsonl counts f=0 o=0 b=0 c=0
  jsonl="$(ns_evidence_jsonl "$ws")"
  if [ ! -f "$jsonl" ] || [ -L "$jsonl" ]; then
    printf 'findings=0 open=0 baseline=0 checkpoint=0'
    return 0
  fi
  if command -v jq >/dev/null 2>&1; then
    while IFS= read -r line; do
      [ -n "$line" ] || continue
      f=$((f + 1))
      case "$line" in
        *'"domain":"baseline"'* | *'"domain": "baseline"'*) b=$((b + 1)) ;;
        *'"domain":"checkpoint"'* | *'"domain": "checkpoint"'*) c=$((c + 1)) ;;
      esac
      case "$line" in
        *'"status":"open"'* | *'"status": "open"'*) o=$((o + 1)) ;;
      esac
    done <"$jsonl"
  elif command -v python3 >/dev/null 2>&1; then
    counts="$(python3 - "$jsonl" <<'PY'
import json, sys
f = o = b = c = 0
with open(sys.argv[1], encoding="utf-8") as fh:
    for line in fh:
        line = line.strip()
        if not line:
            continue
        try:
            rec = json.loads(line)
        except json.JSONDecodeError:
            continue
        f += 1
        if rec.get("domain") == "baseline":
            b += 1
        elif rec.get("domain") == "checkpoint":
            c += 1
        if rec.get("status") == "open":
            o += 1
print(f"{f} {o} {b} {c}")
PY
)"
    read -r f o b c <<COUNTS
$counts
COUNTS
    f="${f:-0}" o="${o:-0}" b="${b:-0}" c="${c:-0}"
  fi
  printf 'findings=%s open=%s baseline=%s checkpoint=%s' "$f" "$o" "$b" "$c"
}

# ns_status_liveness <ns> <watch_minutes> — fresh|stale|absent
ns_status_liveness() {
  local ns="${1:?}" watch="${2:-0}"
  if ns_pulse_fresh "$ns" "$watch"; then
    printf 'fresh'
  elif [ -n "$(ns_pulse_epoch "$ns")" ]; then
    printf 'stale'
  else
    printf 'absent'
  fi
}

# ns_status_last_activity <ns> — epoch seconds or empty
ns_status_last_activity() {
  ns_pulse_epoch "${1:?}"
}

# ns_status_last_checkpoint <workspace> — checkpoint id or none
ns_status_last_checkpoint() {
  local id
  id="$(ns_gate_checkpoint_token "$1")"
  [ "$id" = none ] && id=""
  printf '%s' "${id:-none}"
}

# ns_status_stall_attempts <ns> — integer from .stall line 2
ns_status_stall_attempts() {
  local ns="${1:?}" n="" stall
  ns_layout_set stall "$ns" stall
  if [ ! -f "$stall" ] || [ -L "$stall" ]; then
    printf '0'
    return 0
  fi
  n="$(sed -n '2p' "$stall" 2>/dev/null | tr -d '[:space:]')"
  case "$n" in '' | *[!0-9]*) n=0 ;; esac
  printf '%s' "$n"
}

# ns_long_unit_warn_due <workspace> <minutes> — 0 ok · 1 warn
ns_long_unit_warn_due() {
  local ws="${1:?}" mins="${2:-0}" ns armed start now
  case "$mins" in '' | 0 | *[!0-9]*) return 1 ;; esac
  ns="$ws/.nightshift"
  ns_layout_set armed "$ns" armed
  [ -f "$armed" ] && [ ! -L "$armed" ] || return 1
  start="$(ns_mtime "$armed")"
  [ -n "$start" ] || return 1
  now="$(date +%s)"
  [ $((now - start)) -lt $((mins * 60)) ] && return 1
  [ "$(ns_gate_checkpoint_token "$ws")" = none ] || return 1
  return 0
}

SHA-256: d0091c524c9c1e6d42acd058bdd021f0fc8924d69d6cd01712c297712f7d62c8