cloverleaf-larry/lib/nc-create-thread.sh
Bryan Johnson a0502e2ec6 v0.4.2: operational layer — engine ctrl, tables CRUD, xlate viz, smat-diff, create-thread, tclgen
Seven new lib tools — covers the remaining Bryan-requested gaps.

lib/nc-engine.sh
  - Cloverleaf process control. Wraps shipped binaries (hcienginestop,
    hcienginerun, hcienginerestart, hciengineroutetest). Every action
    is Y/N confirmed AND journaled into engine-actions.tsv.
  - Subcommands: stop, start, bounce/restart, status, resend-ib,
    resend-ob, route-test, testxlate, tpstest.

lib/nc-status.sh
  - Runtime status, v1-modelled. Subcommands: sites, threads, not-up,
    connections, queued, raw. Auto-discovers hcienginestat / tstat /
    connstatus binaries; falls back to file-presence heuristics.

lib/nc-table.sh
  - Read+CRUD for .tbl lookup tables. Subcommands: list, show, pairs
    (→csv/tsv), lookup, reverse-lookup, add, delete, create, replace.
  - All modifications journal-backed. Composes csv-to-table /
    table-to-csv for format conversion.

lib/nc-xlate.sh
  - Visualize .xlt files. Parses the TCL nested-block ops format.
    Subcommands: list, show, ops (TSV), tree (ASCII flow), summary
    (counts + segments + tables touched), diff (cross-xlate).
  - Confirmed working against Epic_ADT_CodaMetrix.xlt: identified
    12 PATHCOPY + 1 COPY ops across MSH/EVN/PID/PV1/PV2/PD1/ZPD/ZPV/
    AL1/GT1/IN1/IN2.

lib/nc-smat-diff.sh
  - Cross-env smat content diff. Samples N msgs from each side,
    pairs by configurable HL7 field (default MSH.10 = control ID),
    hl7-diffs each pair with --ignore MSH.7. Outputs per-pair reports
    + master _summary.md with paired/A-only/B-only counts.

lib/nc-create-thread.sh
  - High-level: create a new protocol + optionally splice a route from
    an existing thread to the new one. Both writes journal-backed.
    Confirmed end-to-end: created to_metrics_test outbound + routed
    IB_ADT_muxS → to_metrics_test via journal entries 001+002.

lib/nc-tclgen.sh
  - TCL UPOC scaffolding from intent. Templates: tps-presc, tps-postsc,
    tps-iclkill, xlate-helper, trxid, ack, field-rewrite. Produces
    clean syntax-correct TCL ready to edit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 11:11:30 -07:00

200 lines
5.8 KiB
Bash
Executable File

#!/usr/bin/env bash
# nc-create-thread.sh — high-level: create a new thread in a NetConfig and
# (optionally) wire a route from another thread to it.
#
# Combines nc-make-jump's emit helpers + nc-insert-protocol for a single
# user-facing operation. Goes through the journal.
#
# Usage:
# nc-create-thread.sh --name NEW_THREAD --site SITE --netconfig PATH
# --type tcpip|file
# --direction inbound|outbound
# --port PORT [--host HOST]
# [--process PROC]
# [--encoding ASCII]
# [--connect-from EXISTING_THREAD] # add a route on EXISTING → NEW
# [--route-type raw|xlate|generate] # default raw
# [--xlate XLATENAME] # if route-type=xlate
# [--trxid REGEX] # default .*
#
# Example: create a new outbound thread `to_metrics` in process `metrics` on
# 10.0.0.50:51999, raw-route from existing `IB_ADT_muxS` to it.
set -o pipefail
NC_SELF="$0"
LIB_DIR="$(cd "$(dirname "$NC_SELF")" && pwd)"
NCP="$LIB_DIR/nc-parse.sh"
NCI="$LIB_DIR/nc-insert-protocol.sh"
die() { printf 'nc-create-thread: %s\n' "$*" >&2; exit 1; }
NAME=""; SITE="${HCISITE:-}"; NC=""
TYPE="tcpip"; DIRECTION="outbound"; PORT=""; HOST=""
PROCESS=""; ENCODING="ASCII"
CONNECT_FROM=""; ROUTE_TYPE="raw"; XLATE=""; TRXID=".*"
while [ $# -gt 0 ]; do
case "$1" in
--name) shift; NAME="$1" ;;
--site) shift; SITE="$1" ;;
--netconfig) shift; NC="$1" ;;
--type) shift; TYPE="$1" ;;
--direction) shift; DIRECTION="$1" ;;
--port) shift; PORT="$1" ;;
--host) shift; HOST="$1" ;;
--process) shift; PROCESS="$1" ;;
--encoding) shift; ENCODING="$1" ;;
--connect-from) shift; CONNECT_FROM="$1" ;;
--route-type) shift; ROUTE_TYPE="$1" ;;
--xlate) shift; XLATE="$1" ;;
--trxid) shift; TRXID="$1" ;;
-h|--help) sed -n '2,20p' "$NC_SELF"; exit 0 ;;
*) die "unknown arg: $1" ;;
esac
shift
done
[ -n "$NAME" ] || die "missing --name"
[ -n "$PORT" ] || die "missing --port"
[ -n "$NC" ] || NC="${HCIROOT:-}/${SITE}/NetConfig"
[ -f "$NC" ] || die "no such netconfig: $NC"
[ -z "$PROCESS" ] && PROCESS="$SITE"
is_server=0
outonly=1
obib=0
case "$DIRECTION" in
inbound) is_server=1; outonly=0; obib=1 ;;
outbound) is_server=0; outonly=1; obib=0 ;;
*) die "bad --direction" ;;
esac
# Build the protocol block
build_block() {
cat <<EOF
protocol ${NAME} {
{ AUTOSTART 1 }
{ BITMAP {} }
{ COORDS {0 0} }
{ DATAFORMAT {
{ FRLTYPE offlen }
{ OFFLEN { { LEN 0 } { OFF 0 } } }
{ TYPE frl }
} }
{ DATAXLATE {
} }
{ EDIBATCH {
{ IN_DATA { { TYPE {} } { VERSION {} } } }
{ OUT_DATA { { HEADER {} } { TRIGGER { { COUNT {} } { SCHEDULER {} } { TIMER {} } } } { TYPE {} } { VERSION {} } } }
} }
{ ENCODING ${ENCODING} }
{ ENCODING_BOM_IB 0 }
{ ENCODING_BOM_OB 0 }
{ ENCODING_HL7 0 }
{ ENCODING_XML 0 }
{ EOCONFIG {} }
{ ERRDBTPS {
{ ERRTPSPROCS { { ARGS {} } { PROCS {} } { PROCSCONTROL {} } } }
{ RETRIES -1 }
} }
{ GROUPS {larry_created} }
{ HOSTDOWN 0 }
{ ICLSERVERPORT {} }
{ KEEPMSGONDISK 0 }
{ META {} }
{ OBWORKASIB ${obib} }
{ OUTBOUNDONLY ${outonly} }
{ PROCESSNAME ${PROCESS} }
{ PROTOCOL {
{ CA_FILE {} }
{ CA_PATH {} }
{ CERT_FILE {} }
{ CIPHERSUITES {} }
{ CLOSE 0 }
{ CONTROLMSGS 0 }
{ COPYCLIENTIPP 0 }
{ DELAYCONNECT 0 }
{ ENCODE_FILL {} }
{ ENCODE_INCLUSIVE 1 }
{ ENCODE_ISNATIVE 0 }
{ ENCODE_JUST r }
{ ENCODE_LEN 4 }
{ ENCODE_TYPE encapsulated }
{ HOST ${HOST:-{}} }
{ IPV4_V6_DUAL 0 }
{ IS_SSL 0 }
{ ISMULTI 0 }
{ ISSERVER ${is_server} }
{ LOCAL_IP {} }
{ MAXCLIENT 0 }
{ MAXOBQD 0 }
{ MAXPREXLTQD 0 }
{ MLP_ERROR RESET }
{ MLP_MODE MLP }
{ MLP_TIMEOUT 30 }
{ MODE {} }
{ PASSWORD {} }
{ PORT ${PORT} }
{ PRIVATE_KEY {} }
{ RECONNECT 1 }
{ REOPEN 5 }
{ SSL_PROTOCOL All }
{ TCP_CONNECTION_TIMEOUT {} }
{ TYPE ${TYPE} }
{ WRITEZERO 0 }
} }
{ RECVCONTROL { { ACKCONTROL { { ARGS {} } { PROCS {} } { PROCSCONTROL {} } } } { EOMSG {} } { HOLDMSGS 0 } { MSGPRIO 5120 } } }
{ SAVECONTROL { { ARGS {} } { PROCS {} } { PROCSCONTROL {} } } }
{ TPS_INBOUND { { ARGS {} } { PROCS {} } { PROCSCONTROL {} } } }
{ TPS_OUTBOUND { { ARGS {} } { PROCS {} } { PROCSCONTROL {} } } }
{ TRACING 0 }
}
EOF
}
build_route_entry() {
local xlate_block="{ PROCS { { ARGS {} } { PROCS {} } { PROCSCONTROL {} } } }"
local extra=""
if [ "$ROUTE_TYPE" = "xlate" ] && [ -n "$XLATE" ]; then
extra="{ XLATE ${XLATE} }"
fi
cat <<EOF
{
{ CACHEMSG 0 }
{ DEL_ON_ERR_ROUTE 0 }
{ ROUTE_DETAILS {
{
{ DEST ${NAME} }
${xlate_block}
${extra}
{ TYPE ${ROUTE_TYPE} }
}
} }
{ ROUTE_ENABLED 1 }
{ TRXID ${TRXID} }
{ WILDCARD ON }
}
EOF
}
# Generate block
BLOCK_FILE=$(mktemp); build_block > "$BLOCK_FILE"
printf '\n=== Generated protocol block for %s ===\n\n' "$NAME"
cat "$BLOCK_FILE"
printf '\n'
# Insert it
"$NCI" insert "$NC" "$BLOCK_FILE"
# If --connect-from, also splice a route into the source thread's DATAXLATE
if [ -n "$CONNECT_FROM" ]; then
ROUTE_FILE=$(mktemp); build_route_entry > "$ROUTE_FILE"
printf '\n=== Route to splice into %s DATAXLATE ===\n\n' "$CONNECT_FROM"
cat "$ROUTE_FILE"
printf '\n'
"$NCI" add-route "$NC" "$CONNECT_FROM" "$ROUTE_FILE"
rm -f "$ROUTE_FILE"
fi
rm -f "$BLOCK_FILE"