diff --git a/MANIFEST b/MANIFEST index 020180b..a2adbe3 100644 --- a/MANIFEST +++ b/MANIFEST @@ -23,7 +23,7 @@ # scripts/make-manifest.sh and bump VERSION. # Top-level scripts -larry.sh 25d81d239b268635bff6704b55f473dc795af844b7a77cf6b24ba7e214c25786 +larry.sh bd3bd27898afce693b44f75eb4fa3fab44b1a963d9b43f5559f5d9d0a5516e40 larry-tunnel.sh 6b050e4eeab15669f4858eaf3b807f168f211ced07815db9521bc40a093f6aaa larry-auth.sh a220cdf7878569dc3028951ee57fc8d5e706a8ca5c6aa45347b58facb386f831 larry-rollback.sh 91b5e9aa6c79266bf306dcfba4ca791c07971bd6924d67a779037531648aa6d0 @@ -31,7 +31,7 @@ install-larry.sh 072a036ad5bbf80e866cfd2dd74de50f8defd69a3f835032579b0cb9d421ad5 uninstall-larry.sh c53ad2d8354c7adeb243b541f027f3f481e4a8661eecfd7af14d7ca53cfcaad9 # Metadata -VERSION 179a5390966e85c2071a87c1b31de13df67460665196f6529f8c4986842f81e5 +VERSION f34248c2449a022d41c918d1e995ad85859a1e9f0e6f89d0af23ae4a55519f71 MANUAL.md 5ff54d6d5fae826f8b3da1eb3be6476076bb15f9b1417a4de285e59ea37e1b1f CHANGELOG.md 934007dc1b08b6c90120f009e3cc7870815e7b251fdf8f6629aa4c004c866017 @@ -72,9 +72,9 @@ lib/lessons.sh 225e899ed72ce20906cc454c5f5db87d605859e5e17431731a2ce481623f4e16 lib/journal.sh 11c62a2d47b6b67a2f423fd8b86c454126df18d2dc3e150233bbd08293e39fe7 # HL7 utilities -lib/hl7-sanitize.sh c0ea35d28c32dcbb1476835a6e58c2ecdbd04f0a479b889675724fc564f4205f +lib/hl7-sanitize.sh 5bb409b3e5eae545e362e1313cd47c6835d56177dfe2efafd519e4ceedb2a82b lib/hl7-desanitize.sh 2e5462a61ab1e8bd3fefb956bace8ca1ae33397a09024cbe766fa55c37a5aad6 -lib/hl7-diff.sh 66985afb3073340f1c12b0d7b39f41a5d8df68dfebc89c55190d6915f6077e86 +lib/hl7-diff.sh d2cc179bf25dd8e808d46d4211d1926f36645cec8443d0ea910675093eb89d72 lib/hl7-field.sh a640f7cbd9521dc96171ee1dbdf909170262101a1d7a433f6f0ce2bea8d42b02 lib/hl7-schema.sh 2ba4057a214867ff4950f10057ee4ffd7149e1a82ba94b07b6857d77bf10d75f @@ -107,9 +107,9 @@ lib/nc-tclgen.sh 5b8e73d7f6950a2b84f563132562ea82f62f4acac907257e233c7e68d85506c lib/nc-parse.sh 52fef42d7a4b361534ab0d921deef74586dfeb6c199c941cebb55abcc2c39d4f lib/nc-paths.sh 388d2f4560736587a01218cadc1de612cd59e392819d16db2f56f19174c1111b lib/nc-inbound.sh 52d28c5f8d97bdf96f0fc7b5300d35b106b8e1226578f4cda430deb2a8b4a91b -lib/nc-make-jump.sh 08a0bc58a299c95c60a59a5202792daf0ada3a8a0be7dc1b4cccc5724f5c9c79 +lib/nc-make-jump.sh 237d320d78d36e050dd4b2237c11d8452f55fa3a61428bea45643217ad5c6ab7 lib/nc-provision-jumps.sh cf80abe572a4eb241b351363ffa85406829f0c458882dc8d14f1628c458432f8 -lib/nc-msgs.sh 20517922d1153ec7827c833987497fb305d087b579911d1b9067d65ae156a19f +lib/nc-msgs.sh 7e37bf3f9f1e1f09dab95914c9d45e605198e7111534e816bbac3298eb812918 lib/nc-document.sh 47211e99089c0446d25a1e84545a734894720a1c9ad8f59b920332035e4ea880 lib/nc-revisions.sh c27856f7decfc4c2e2c990f59eb20136fdff9cf0a52b9d9fbd9370613666a802 lib/nc-diff-interface.sh c922d10323f06346efa53ada68b44d32d9568ff0bd848c59af3404135f29d1ad diff --git a/VERSION b/VERSION index f374f66..2003b63 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.9.1 +0.9.2 diff --git a/larry.sh b/larry.sh index 7a97d25..4521c04 100755 --- a/larry.sh +++ b/larry.sh @@ -99,7 +99,7 @@ set -o pipefail # ───────────────────────────────────────────────────────────────────────────── # Config # ───────────────────────────────────────────────────────────────────────────── -LARRY_VERSION="0.9.1" +LARRY_VERSION="0.9.2" LARRY_HOME="${LARRY_HOME:-$HOME/.larry}" # ───────────────────────────────────────────────────────────────────────────── diff --git a/lib/hl7-diff.sh b/lib/hl7-diff.sh index 6781da8..3c44127 100755 --- a/lib/hl7-diff.sh +++ b/lib/hl7-diff.sh @@ -184,8 +184,9 @@ awk -v IGNORE="$IGNORE" -v INCLUDE="$INCLUDE" -v FMT="$FORMAT" \ } function emit(msg_idx, path, lv, rv) { - if (FMT == "tsv") printf "%d\t%s\t%s\t%s\n", msg_idx, path, lv, rv - else printf " %-20s %-30s %s\n", path, lv, rv + # count mode: accumulate only — no per-diff output (final print is in END). + if (FMT == "tsv") printf "%d\t%s\t%s\t%s\n", msg_idx, path, lv, rv + else if (FMT != "count") printf " %-20s %-30s %s\n", path, lv, rv DIFF_COUNT++ } @@ -234,7 +235,9 @@ awk -v IGNORE="$IGNORE" -v INCLUDE="$INCLUDE" -v FMT="$FORMAT" \ { R_MSGS[++n_r] = $0 } END { - if (FMT == "count") { print DIFF_COUNT; exit } + # F-1 fix (2026-06-08): the early-exit `if (FMT=="count")` that used to sit + # here fired BEFORE the diff loop ran, so DIFF_COUNT was always 0. The loop + # is now unconditional; count output is emitted AFTER the loop at the bottom. nm = (n_l > n_r) ? n_l : n_r if (FMT == "text") { printf "HL7 diff:\n left: %s (%d messages)\n right: %s (%d messages)\n ignore: %s\n", LFILE, n_l, RFILE, n_r, IGNORE @@ -243,7 +246,7 @@ awk -v IGNORE="$IGNORE" -v INCLUDE="$INCLUDE" -v FMT="$FORMAT" \ } if (n_l != n_r) { if (FMT == "tsv") printf "0\tMESSAGE_COUNT\t%d\t%d\n", n_l, n_r - else printf " MESSAGE COUNT mismatch: %d vs %d\n", n_l, n_r + else if (FMT != "count") printf " MESSAGE COUNT mismatch: %d vs %d\n", n_l, n_r DIFF_COUNT++ } for (i=1; i<=nm; i++) { @@ -256,6 +259,7 @@ awk -v IGNORE="$IGNORE" -v INCLUDE="$INCLUDE" -v FMT="$FORMAT" \ if (FMT == "text" && (i == 1 || DIFF_COUNT > 0)) printf "----- message %d -----\n", i diff_message(lm, rm, i) } + if (FMT == "count") { print DIFF_COUNT; exit (DIFF_COUNT > 0 ? 1 : 0) } if (FMT == "text") printf "\n%d total field difference(s)\n", DIFF_COUNT exit (DIFF_COUNT > 0 ? 1 : 0) } diff --git a/lib/hl7-sanitize.sh b/lib/hl7-sanitize.sh index d6ca6c8..874b285 100755 --- a/lib/hl7-sanitize.sh +++ b/lib/hl7-sanitize.sh @@ -453,14 +453,50 @@ END { AWK_END ) + # F-5 fix (2026-06-08): awk uses RS="\r" (CR segment separator — the real + # Cloverleaf wire format). LF-only or CRLF input never splits into segments, + # so every PHI field passes through as cleartext. Normalise all three line + # endings (LF, CRLF, bare CR) to CR before handing off to awk so that the + # existing tokenisation logic works regardless of how the file was created. + # `tr` is POSIX and available on every platform the tool targets. + # Detection: if the file contains no CR but does contain LF-separated MSH + # lines, it needs normalisation. We normalise whenever no bare CR is present, + # which is a safe no-op on already-CR wire data (the first tr removes nothing). + _normalise_to_cr() { + # CRLF → CR first, then remaining bare LF → CR. + tr -d '\r' | tr '\n' '\r' + } + if [ -n "$input_file" ]; then - awk -v RULES_FILE="$rules_tmp" -v TABLE="$table" -v STRICT="$strict" \ - -v UPDATE_TABLE="$update_table" \ - "$awk_script" "$input_file" + # Detect whether file already uses CR segment separators. + if grep -qP '\r' "$input_file" 2>/dev/null || \ + python3 -c "import sys; d=open(sys.argv[1],'rb').read(); sys.exit(0 if b'\r' in d else 1)" "$input_file" 2>/dev/null; then + # Already CR-delimited — feed directly (original path, zero overhead). + awk -v RULES_FILE="$rules_tmp" -v TABLE="$table" -v STRICT="$strict" \ + -v UPDATE_TABLE="$update_table" \ + "$awk_script" "$input_file" + else + # LF or CRLF — normalise to CR on the fly then pipe into awk. + _normalise_to_cr < "$input_file" | \ + awk -v RULES_FILE="$rules_tmp" -v TABLE="$table" -v STRICT="$strict" \ + -v UPDATE_TABLE="$update_table" \ + "$awk_script" /dev/stdin + fi else - awk -v RULES_FILE="$rules_tmp" -v TABLE="$table" -v STRICT="$strict" \ - -v UPDATE_TABLE="$update_table" \ - "$awk_script" /dev/stdin + # stdin — buffer to a temp file so we can inspect for CR presence. + local _norm_tmp; _norm_tmp=$(mktemp) + trap 'rm -f "$_norm_tmp"' RETURN + cat /dev/stdin > "$_norm_tmp" + if python3 -c "import sys; d=open(sys.argv[1],'rb').read(); sys.exit(0 if b'\r' in d else 1)" "$_norm_tmp" 2>/dev/null; then + awk -v RULES_FILE="$rules_tmp" -v TABLE="$table" -v STRICT="$strict" \ + -v UPDATE_TABLE="$update_table" \ + "$awk_script" "$_norm_tmp" + else + _normalise_to_cr < "$_norm_tmp" | \ + awk -v RULES_FILE="$rules_tmp" -v TABLE="$table" -v STRICT="$strict" \ + -v UPDATE_TABLE="$update_table" \ + "$awk_script" /dev/stdin + fi fi } diff --git a/lib/nc-make-jump.sh b/lib/nc-make-jump.sh index b564913..0efd646 100755 --- a/lib/nc-make-jump.sh +++ b/lib/nc-make-jump.sh @@ -85,7 +85,15 @@ T_ENC=$("$NCP" protocol-field "$NC" "$INBOUND" ENCODING 2>/dev/null | head -1) ENC="${ENC_OVERRIDE:-$T_ENC}" ORIG_PORT=$("$NCP" protocol-nested "$NC" "$INBOUND" PROTOCOL.PORT 2>/dev/null | head -1) -[ -n "$ORIG_PORT" ] || die "could not read PROTOCOL.PORT of inbound $INBOUND (is it a TCP listener? if it's a file/ICL inbound, this pattern may not apply directly)" +# F-2 fix (2026-06-08): protocol-nested returns the literal string "{}" for +# file/ICL inbounds whose PORT block is empty ({ PORT {} }). The old guard +# only tested for empty string, so "{}" (non-empty) slipped through and the +# generated thread carried "{ PORT {} }" — a broken TCP client with no port. +# Treat empty, "{}", and any non-numeric value as "no port" and die clearly. +case "$ORIG_PORT" in + ''|'{}'|*[!0-9]*) + die "could not read a numeric PROTOCOL.PORT for inbound '$INBOUND' (got: '${ORIG_PORT:-}'). Is it a TCP listener? File/ICL inbounds do not use this jump pattern." ;; +esac # tag = the inbound name itself (Bryan's "auto-derived" preference) TAG="$INBOUND" diff --git a/lib/nc-msgs.sh b/lib/nc-msgs.sh index 7263c06..cd781ea 100755 --- a/lib/nc-msgs.sh +++ b/lib/nc-msgs.sh @@ -287,8 +287,19 @@ field_matches() { done <<< "$actual" return 1 fi + # F-3 fix (2026-06-08): exact-match on a bare value (no ^ in expected) + # must also match when the stored field carries HL7 components, e.g. + # mrn=5720501458 should match 5720501458^^^MRN + # The manual's marquee example uses this form. Without the fix, operators + # searching by bare MRN get 0 results on every real Epic site. + # Rule: if expected has no ^ and the repetition does, compare only + # component-1 (the part before the first ^). while IFS= read -r rep; do [ "$rep" = "$expected" ] && return 0 + if [[ "$rep" == *"^"* ]] && [[ "$expected" != *"^"* ]]; then + local _comp1="${rep%%^*}" + [ "$_comp1" = "$expected" ] && return 0 + fi done <<< "$actual" return 1 ;;