#!/usr/bin/env bash # Prove ACK instant is before journal credit (regulatory ordering: ack before credit). # Usage: verify-ack-before-credit.sh # ack.json: include "timestamp" or "ack_timestamp" as full ISO-8601 (UTC recommended). # Fineract often returns transactionDate as YYYY-MM-DD only; we treat credit as end of that UTC day # (conservative: ACK must be strictly before 23:59:59.999Z on that date unless you extend this script). # # Exit: 0 pass, 1 fail ordering, 2 usage/API/parse error. set -eo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" # shellcheck source=scripts/lib/load-project-env.sh set +u source "${REPO_ROOT}/scripts/lib/load-project-env.sh" set -euo pipefail ACK_FILE="${1:-}" JE_ID="${2:-}" if [[ -z "$ACK_FILE" || -z "$JE_ID" || ! -f "$ACK_FILE" ]]; then echo "Usage: $0 " >&2 exit 2 fi BASE_URL="${OMNL_FINERACT_BASE_URL:-}" PASS="${OMNL_FINERACT_PASSWORD:-}" USER="${OMNL_FINERACT_USER:-app.omnl}" TENANT="${OMNL_FINERACT_TENANT:-omnl}" if [[ -z "$BASE_URL" || -z "$PASS" ]]; then echo "error: OMNL_FINERACT_BASE_URL and OMNL_FINERACT_PASSWORD required" >&2 exit 2 fi ACK_TS="$(jq -r '.timestamp // .ack_timestamp // empty' "$ACK_FILE")" [[ -z "$ACK_TS" ]] && echo "error: ack file missing timestamp / ack_timestamp" >&2 && exit 2 JE_JSON="$(curl -sS -H "Fineract-Platform-TenantId: ${TENANT}" -u "${USER}:${PASS}" "${BASE_URL}/journalentries/${JE_ID}")" CREDIT_DATE="$(echo "$JE_JSON" | jq -r '.transactionDate // empty')" [[ -z "$CREDIT_DATE" ]] && echo "error: journalentries/${JE_ID} missing transactionDate" >&2 && exit 2 ACK_TS="$ACK_TS" CREDIT_DATE="$CREDIT_DATE" python3 <<'PY' import os, sys from datetime import datetime, timezone ack_s = os.environ["ACK_TS"].strip().replace("Z", "+00:00") try: ack = datetime.fromisoformat(ack_s) except ValueError: print("error: cannot parse ACK timestamp as ISO-8601", file=sys.stderr) sys.exit(2) if ack.tzinfo is None: ack = ack.replace(tzinfo=timezone.utc) d = os.environ["CREDIT_DATE"].strip()[:10] try: y, m, day = (int(d[0:4]), int(d[5:7]), int(d[8:10])) credit_end = datetime(y, m, day, 23, 59, 59, 999000, tzinfo=timezone.utc) except Exception: print("error: bad transactionDate", file=sys.stderr) sys.exit(2) if ack < credit_end: print(f"OK: ack {ack.isoformat()} is before credit value-date end {credit_end.isoformat()}") sys.exit(0) print(f"FAIL: ack {ack.isoformat()} is not before credit window end {credit_end.isoformat()}", file=sys.stderr) sys.exit(1) PY