#!/usr/bin/env bash # Vault lock concurrency smoke (ADR-017 G1). # # Drives the lock helpers in justfiles/_secrets_lib.sh against the live ZOT # registry. Tests: # 1. First holder acquires the lock — push succeeds. # 2. Second holder attempting to acquire while lock is fresh — sees the # existing holder and refuses to overwrite (modeled in secrets-open). # 3. TTL expiry — a forged-past expires timestamp is treated as abandoned. # 4. Force-unlock writes audit + deletes — second holder can then proceed. # # Assumes: # - libre-wuji vault bootstrapped (its access.sops.yaml decryptable). # - regadm zot credentials exported in env or implicit via DOCKER_CONFIG. # # Exit 0 on green; non-zero on any failed assertion. set -euo pipefail ONTOREF_ROOT=${ONTOREF_ROOT:-/Users/Akasha/Development/ontoref} PROJECT_ROOT=${ONTOREF_PROJECT_ROOT:-/Users/Akasha/project-provisioning/workspaces/libre-wuji} NICKEL_IMPORT_PATH="${PROJECT_ROOT}:${ONTOREF_ROOT}:${ONTOREF_ROOT}/ontology:${HOME}/.config/ontoref/schemas" export NICKEL_IMPORT_PATH export ONTOREF_PROJECT_ROOT="$PROJECT_ROOT" # shellcheck disable=SC1091 source "${ONTOREF_ROOT}/justfiles/_secrets_lib.sh" PASS=0 FAIL=0 assert() { local name="$1"; shift if "$@"; then echo " ✓ $name" PASS=$((PASS + 1)) else echo " ✗ $name" FAIL=$((FAIL + 1)) fi } vault_zot_config_open || { echo "cannot open zot config — abort"; exit 1; } trap 'rm -rf "$TMPDIR_CFG"' EXIT # Cleanup any stale lock from prior runs. vault_lock_release || true # Test 1 — fresh acquire. echo "test 1: fresh lock acquire" NOW_TS=$(date -u +%Y-%m-%dT%H:%M:%SZ) NOW_EPOCH=$(date -u +%s) EXP_EPOCH=$((NOW_EPOCH + 3600)) EXP_TS=$(date -u -r "$EXP_EPOCH" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \ || date -u -d "@$EXP_EPOCH" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null) assert "push lock as alice" \ vault_lock_push "alice" "developer" "$NOW_TS" "$EXP_TS" # Test 2 — fetch returns alice's lock. echo "test 2: fetch returns existing holder" EXISTING=$(vault_lock_fetch) ALICE=$(echo "$EXISTING" | jq -r .locked_by) assert "lock fetched holder=alice" \ [ "$ALICE" = "alice" ] # Test 3 — second holder bob detects alice and refuses (logic from secrets-open). echo "test 3: second holder sees existing fresh lock" EXIST_BY=$(echo "$EXISTING" | jq -r .locked_by) EXIST_EXP=$(echo "$EXISTING" | jq -r .expires) EXIST_EXP_EPOCH=$(date -u -j -f "%Y-%m-%dT%H:%M:%SZ" "$EXIST_EXP" +%s 2>/dev/null \ || date -u -d "$EXIST_EXP" +%s 2>/dev/null || echo 0) NOW_EPOCH=$(date -u +%s) assert "fresh lock blocks bob (alice holds, not expired)" \ [ "$EXIST_BY" = "alice" ] && [ "$EXIST_EXP_EPOCH" -gt "$NOW_EPOCH" ] # Test 4 — TTL-expired lock is detectable (forge a past expiry by re-pushing). echo "test 4: TTL-expired lock" PAST_EPOCH=$((NOW_EPOCH - 60)) PAST_TS=$(date -u -r "$PAST_EPOCH" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \ || date -u -d "@$PAST_EPOCH" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null) vault_lock_push "alice" "developer" "$PAST_TS" "$PAST_TS" EXISTING=$(vault_lock_fetch) EXP=$(echo "$EXISTING" | jq -r .expires) EXP_EP=$(date -u -j -f "%Y-%m-%dT%H:%M:%SZ" "$EXP" +%s 2>/dev/null \ || date -u -d "$EXP" +%s 2>/dev/null || echo 0) assert "expired lock detected (expires < now)" \ [ "$EXP_EP" -lt "$NOW_EPOCH" ] # Test 5 — force release. echo "test 5: force release succeeds" vault_lock_release LATER=$(vault_lock_fetch) assert "lock empty after release" [ -z "$LATER" ] # Test 6 — bob can now acquire. echo "test 6: bob acquires after release" NOW_TS=$(date -u +%Y-%m-%dT%H:%M:%SZ) EXP_EPOCH=$(($(date -u +%s) + 3600)) EXP_TS=$(date -u -r "$EXP_EPOCH" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \ || date -u -d "@$EXP_EPOCH" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null) vault_lock_push "bob" "developer" "$NOW_TS" "$EXP_TS" EXISTING=$(vault_lock_fetch) BOB=$(echo "$EXISTING" | jq -r .locked_by) assert "lock now held by bob" [ "$BOB" = "bob" ] # Cleanup. vault_lock_release echo echo "── $PASS passed, $FAIL failed ──" [ "$FAIL" -eq 0 ]