v0.8.21: interface document tool — <thread>/<system> document. Legacy ADT-Messages template (flow via nc_paths, Platform|Action|Description|From|To, per-delivery breakdown); deterministic API-free UPOC-bits extraction (comments/HL7 fields/event matches/table/disposition) + raw-TCL appendix; LLM polishes to prose only when API present. Verified on the real 24-site integrator (ADTto_CodaMetrix, codametrix system, PeriWatch UPOC proof).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
9364c7edeb
commit
474a0710a4
78
CHANGELOG.md
78
CHANGELOG.md
@ -4,6 +4,84 @@ All notable changes to `cloverleaf-larry` / `larry-anywhere` are recorded here.
|
|||||||
Versioning is loose-semver; bumps trigger the in-process self-update on every
|
Versioning is loose-semver; bumps trigger the in-process self-update on every
|
||||||
running client via `LARRY_BASE_URL` + `MANIFEST`.
|
running client via `LARRY_BASE_URL` + `MANIFEST`.
|
||||||
|
|
||||||
|
## v0.8.21 — 2026-05-28
|
||||||
|
|
||||||
|
Interface **`document`** tool rebuilt (`lib/nc-document.sh`) — documents a
|
||||||
|
Cloverleaf interface end-to-end in Bryan's confirmed Legacy "ADT Messages"
|
||||||
|
template. Deterministic, pure bash+awk, **API-FREE** (the whole tool runs
|
||||||
|
identically on an API-blocked host like Gundersen — no python, no `.pyz`, no
|
||||||
|
network).
|
||||||
|
|
||||||
|
**Two modes.**
|
||||||
|
- SINGLE INTERFACE: `nc-document.sh <thread> [site]` (or `<site>/<thread>`),
|
||||||
|
e.g. `ADTto_CodaMetrix ancout` — one fully-detailed interface section.
|
||||||
|
- SYSTEM/PATTERN: `nc-document.sh --name <pattern>`, e.g. `--name codametrix` —
|
||||||
|
one section per matching DELIVERY (outbound) thread across all sites. A delivery
|
||||||
|
is any thread that is NOT an inbound listener (`ISSERVER=1`) and NOT an ICL/file
|
||||||
|
inbound router (`OBWORKASIB=1`), so it catches both `OUTBOUNDONLY=1` threads and
|
||||||
|
bidirectional `Xto_*` deliveries (e.g. `DFTto_codaMetrix`, `OUTBOUNDONLY=0`).
|
||||||
|
|
||||||
|
**Per-interface output (Legacy template):**
|
||||||
|
- **Title** = the interface / message type.
|
||||||
|
- **Description** prose — what the messages are, the TRXID filter that selects the
|
||||||
|
delivery, where translation happens (xlate vs raw), seeded from the surfaced
|
||||||
|
UPOC bits.
|
||||||
|
- **Message Flow** table `Platform | Action | Description | From | To` — Epic feed
|
||||||
|
→ Cloverleaf routing → Final Delivery, one row per hop. The routing row uses
|
||||||
|
`nc-paths.sh` and adapts its wording to whether the chain crosses a site
|
||||||
|
boundary (cross-site `destination`-block hop `==>` vs intra-site DATAXLATE route
|
||||||
|
`-->`).
|
||||||
|
- **Delivery breakdown** — Flow chain; how-received (inbound `PROTOCOL`
|
||||||
|
TYPE/HOST/PORT/ISSERVER + `ICLSERVERPORT`); inbound TRXID/TPS proc
|
||||||
|
(`DATAFORMAT.PROC`); the route's TRXID filter + WILDCARD; route TYPE;
|
||||||
|
PREPROCS/POSTPROCS; XLATE; destination host:port / process / type.
|
||||||
|
|
||||||
|
**★ Deterministic UPOC-bits extraction (the key feature).** For every referenced
|
||||||
|
UPOC proc (inbound TRXID/TPS proc + each route's PRE/POST/PROCS), locate its
|
||||||
|
`.tcl` under `$HCIROOT/<site>/tclprocs/` (home site first, then any site) and
|
||||||
|
surface — with NO API — into the Description: (1) the proc's **comments** (header +
|
||||||
|
inline `#` filter notes), (2) **HL7 fields** referenced (dotted `PID.8` + the
|
||||||
|
underscore `PV1_3_3` form normalized to dotted), (3) literal **event-code matches**
|
||||||
|
(`A01 A02 A03 …`, boundary-checked), (4) **table lookups** (`tbllookup` / `.tbl`,
|
||||||
|
e.g. `PeriCalm_Loc`), (5) **disposition** (CONTINUE/KILL/return → "pass matching /
|
||||||
|
kill non-matching"). Rendered compactly, e.g.:
|
||||||
|
`UPOC Epic_PeriCalm_ADT_pass — … · fields: PV1.45 PID.8 · matches: A02 A03 · table: PeriCalm_Loc · disposition: pass matching / kill non-matching`.
|
||||||
|
The **raw proc TCL** is included verbatim in a plainly-labelled `## Referenced
|
||||||
|
proc source` appendix for audit — with **no "summarize by hand / on an API box"
|
||||||
|
marker** (the surfaced bits ARE the content).
|
||||||
|
|
||||||
|
**LLM polish (enrichment, NOT in the bash tool).** The bash tool calls no API. The
|
||||||
|
`nc_document` tool schema now instructs the model, when run WITH the API, to
|
||||||
|
transparently polish the surfaced UPOC bits into smoother filter prose in the
|
||||||
|
Description (no marker, no special mechanism). On API-blocked hosts the
|
||||||
|
deterministic bits + appendix ARE the deliverable.
|
||||||
|
|
||||||
|
**Portability fixes baked in:**
|
||||||
|
- All extraction awk is `\b`-free (BSD/BWK awk on macOS + mawk on Windows Git-Bash
|
||||||
|
silently match nothing on `\b`); token boundaries use explicit char-class scans.
|
||||||
|
- Internal records are `\037`(US)-delimited, not TAB — bash `read` with a TAB IFS
|
||||||
|
collapses CONSECUTIVE empty fields and was silently shifting columns when an
|
||||||
|
ICL/file inbound has empty HOST/PORT/ISSERVER. Inbound facts are read into named
|
||||||
|
globals for the same reason.
|
||||||
|
- Route parser walks the real DATAXLATE depth map (route sub-blocks at depth 3,
|
||||||
|
DEST/TYPE/XLATE at depth 6, inner `{ PROCS <name> }` at depth 8), so per-route
|
||||||
|
TRXID/TYPE/XLATE/PREPROCS extraction is exact.
|
||||||
|
|
||||||
|
**Wiring.** `tool_nc_document` (larry.sh) now takes `thread`/`name`/`site`; the
|
||||||
|
`nc_document` tool schema documents single-thread + system modes and the
|
||||||
|
UPOC-bit-polish instruction. `larry tools nc-document` drives the same script
|
||||||
|
standalone (no API).
|
||||||
|
|
||||||
|
**Verified on the REAL integrator** (`HCIROOT=/tmp/clvf_realtest/integrator`, the
|
||||||
|
24-site QA env): generated `ADTto_CodaMetrix ancout` (matches Larry's verified
|
||||||
|
prototype — flow `mux/ADTfr_epic_964700 --> mux/OB_ADT_ancS ==> ancout/IB_ADT_muxS
|
||||||
|
--> ancout/ADTto_CodaMetrix`, inbound proc `trxId_IB_ADT_muxS`, TYPE xlate, XLATE
|
||||||
|
`Epic_ADT_CodaMetrix.xlt`, dest `172.31.23.2:39500` process ADT); the `codametrix`
|
||||||
|
system doc (2 deliveries: the ADT feed + the intra-site `DFTto_codaMetrix` DFT
|
||||||
|
feed); and the PeriWatch route, proving the UPOC-bits extraction surfaces real
|
||||||
|
content from `Epic_PeriCalm_ADT_pass` (comments, fields, `A02`/`A03` matches,
|
||||||
|
`PeriCalm_Loc` table, pass/kill disposition). `bash -n` clean; TOOLS_JSON valid.
|
||||||
|
|
||||||
## v0.8.20 — 2026-05-28
|
## v0.8.20 — 2026-05-28
|
||||||
|
|
||||||
Route-chain tracer (`lib/nc-paths.sh`) REARCHITECTED for the real integrator:
|
Route-chain tracer (`lib/nc-paths.sh`) REARCHITECTED for the real integrator:
|
||||||
|
|||||||
10
MANIFEST
10
MANIFEST
@ -23,21 +23,21 @@
|
|||||||
# scripts/make-manifest.sh and bump VERSION.
|
# scripts/make-manifest.sh and bump VERSION.
|
||||||
|
|
||||||
# Top-level scripts
|
# Top-level scripts
|
||||||
larry.sh 20b68e650ff9a94a15f7745334fe0dc0f913da2c6d4c2b92388202c951d0d171
|
larry.sh ebbe42c5b4236737d8e3b02b4a19fd58e7877b67362c9ac3a729aac89cce0cd7
|
||||||
larry-tunnel.sh 6b050e4eeab15669f4858eaf3b807f168f211ced07815db9521bc40a093f6aaa
|
larry-tunnel.sh 6b050e4eeab15669f4858eaf3b807f168f211ced07815db9521bc40a093f6aaa
|
||||||
larry-auth.sh a220cdf7878569dc3028951ee57fc8d5e706a8ca5c6aa45347b58facb386f831
|
larry-auth.sh a220cdf7878569dc3028951ee57fc8d5e706a8ca5c6aa45347b58facb386f831
|
||||||
larry-rollback.sh 91b5e9aa6c79266bf306dcfba4ca791c07971bd6924d67a779037531648aa6d0
|
larry-rollback.sh 91b5e9aa6c79266bf306dcfba4ca791c07971bd6924d67a779037531648aa6d0
|
||||||
install-larry.sh e97da4e12a0d8863ca18d79b12f6c4294c72fa6d4b11dffeab66504236bb4eb1
|
install-larry.sh e97da4e12a0d8863ca18d79b12f6c4294c72fa6d4b11dffeab66504236bb4eb1
|
||||||
|
|
||||||
# Metadata
|
# Metadata
|
||||||
VERSION 9bb2e455df78105b99303d11d1de0401d94142ff3fadc8e37bcba6c0c4d59914
|
VERSION 14f2df7b94315d4dcd8adba946a2421fe03b0e18f69cdc48fa45d527e13a5536
|
||||||
MANUAL.md c64bd0251a51ad150508b4e1185355bc4826a64071d4de339f92ed550dbfacde
|
MANUAL.md c64bd0251a51ad150508b4e1185355bc4826a64071d4de339f92ed550dbfacde
|
||||||
CHANGELOG.md 73f32366662b55ddc16cb937f0e6a4d0f4cd99181e8717ab9938d80b60984db6
|
CHANGELOG.md e1078bf774ea4137f1b4810bc8d875572059d854ffc04e559d9e57b2450b76bc
|
||||||
|
|
||||||
# Agent personas (system-prompt overlays)
|
# Agent personas (system-prompt overlays)
|
||||||
agents/larry.md 0a1ef737e7fc133ab35be09f79c3a4df33de814e0404b69b950932d0c8a01be1
|
agents/larry.md 0a1ef737e7fc133ab35be09f79c3a4df33de814e0404b69b950932d0c8a01be1
|
||||||
agents/clover.md d1bbfd6cc4642c2bff6e15dcbdf051d71b063b3fe29e0be97d17b3180d3c7ac5
|
agents/clover.md d1bbfd6cc4642c2bff6e15dcbdf051d71b063b3fe29e0be97d17b3180d3c7ac5
|
||||||
agents/cloverleaf-cheatsheet.md 95c3bc52eaae92dff548702b0a0461ccba6ac6d8b410196c45ca59f28d0b3477
|
agents/cloverleaf-cheatsheet.md 35801c8d6b2ea67ac3ea828a11f611d1a716dee05f1db096a19d7c86b69c1734
|
||||||
agents/regress.md bb05ed1439b1e35d6e9799e32d683bfab166472c72115c1f02757e227c74e42f
|
agents/regress.md bb05ed1439b1e35d6e9799e32d683bfab166472c72115c1f02757e227c74e42f
|
||||||
|
|
||||||
# Cygwin/MobaXterm CR-taint defense primitives (sourced by every tool)
|
# Cygwin/MobaXterm CR-taint defense primitives (sourced by every tool)
|
||||||
@ -102,7 +102,7 @@ lib/nc-paths.sh 388d2f4560736587a01218cadc1de612cd59e392819d16db2f56f19174c1111b
|
|||||||
lib/nc-inbound.sh 52d28c5f8d97bdf96f0fc7b5300d35b106b8e1226578f4cda430deb2a8b4a91b
|
lib/nc-inbound.sh 52d28c5f8d97bdf96f0fc7b5300d35b106b8e1226578f4cda430deb2a8b4a91b
|
||||||
lib/nc-make-jump.sh 08a0bc58a299c95c60a59a5202792daf0ada3a8a0be7dc1b4cccc5724f5c9c79
|
lib/nc-make-jump.sh 08a0bc58a299c95c60a59a5202792daf0ada3a8a0be7dc1b4cccc5724f5c9c79
|
||||||
lib/nc-msgs.sh 729e2d6c9159e83fa177fc6b982e48ed8453a9743477cc90afdd3cd4ec7e620c
|
lib/nc-msgs.sh 729e2d6c9159e83fa177fc6b982e48ed8453a9743477cc90afdd3cd4ec7e620c
|
||||||
lib/nc-document.sh 1f95082df3a88086868e5c159dddd4fd4019b706dbe1e48f0d7500eb9cd6c063
|
lib/nc-document.sh a643fddd1c71f0c8871c2bedd393c7ba3a5dceaa6d34e43d5f37cd9dd3985f5d
|
||||||
lib/nc-diff-interface.sh 6b64ec3070a3d75d1d79632c9aeb357177fdbcc77c474aa78e6f6929fda1a324
|
lib/nc-diff-interface.sh 6b64ec3070a3d75d1d79632c9aeb357177fdbcc77c474aa78e6f6929fda1a324
|
||||||
lib/nc-find.sh 8c79e0acad7de56e4e1f12d61e071a4b98c4e2310a1f7fb183697df521215e3f
|
lib/nc-find.sh 8c79e0acad7de56e4e1f12d61e071a4b98c4e2310a1f7fb183697df521215e3f
|
||||||
lib/nc-insert-protocol.sh ad1fa0bafbf4fdfb12bad20f9c22c3eed519f8846774331e26aa9becd6f8898a
|
lib/nc-insert-protocol.sh ad1fa0bafbf4fdfb12bad20f9c22c3eed519f8846774331e26aa9becd6f8898a
|
||||||
|
|||||||
@ -24,6 +24,7 @@ Two kinds of capability:
|
|||||||
| `nc_paths(thread, site, [all], [site_only])` | **"trace the FULL route chain / what feeds X / the whole path / downstream + upstream"** — deterministic DFS path enumerator, output `SITE THREAD HOPS PATH`. Intra-site hops follow DATAXLATE DEST; **cross-site links are via named `destination` blocks** (a `DEST` naming a destination block resolves to its `{ SITE } { THREAD }`; the `PORT` corroborates). The whole route graph is parsed once into memory and walked with O(1) lookups. For `--up`, THREAD = feeder ROOT and the queried thread is the terminus. **Use this instead of repeated `nc_destinations`/`nc_sources`, grep, or read_file** for ANY path / chain / route-tracing question. |
|
| `nc_paths(thread, site, [all], [site_only])` | **"trace the FULL route chain / what feeds X / the whole path / downstream + upstream"** — deterministic DFS path enumerator, output `SITE THREAD HOPS PATH`. Intra-site hops follow DATAXLATE DEST; **cross-site links are via named `destination` blocks** (a `DEST` naming a destination block resolves to its `{ SITE } { THREAD }`; the `PORT` corroborates). The whole route graph is parsed once into memory and walked with O(1) lookups. For `--up`, THREAD = feeder ROOT and the queried thread is the terminus. **Use this instead of repeated `nc_destinations`/`nc_sources`, grep, or read_file** for ANY path / chain / route-tracing question. |
|
||||||
| `nc_xlate_refs(netconfig, [name])` | "what .xlt files are referenced?" — all or scoped to one protocol |
|
| `nc_xlate_refs(netconfig, [name])` | "what .xlt files are referenced?" — all or scoped to one protocol |
|
||||||
| `nc_find_inbound(netconfig, mode, format)` | "which threads are inbound?" — modes: `tcp-listen` (real upstream-client listeners, ISSERVER=1), `icl-or-file` (OBWORKASIB=1 internal mux/file inbounds), `all`. formats: tsv, jsonl, table |
|
| `nc_find_inbound(netconfig, mode, format)` | "which threads are inbound?" — modes: `tcp-listen` (real upstream-client listeners, ISSERVER=1), `icl-or-file` (OBWORKASIB=1 internal mux/file inbounds), `all`. formats: tsv, jsonl, table |
|
||||||
|
| `nc_document(thread \| name, [site], [out])` | **"document this interface / system end-to-end"** — emits the Legacy "ADT Messages" markdown template (Title, Description, Message Flow table `Platform\|Action\|Description\|From\|To`, per-delivery breakdown) for ONE interface (`thread`, e.g. `ADTto_CodaMetrix`) or a whole SYSTEM (`name` pattern, e.g. `codametrix` → one section per matching delivery thread across sites). Deterministic, pure bash, **API-FREE** (runs on API-blocked boxes). ★ For every referenced UPOC proc it surfaces the proc's **comments, HL7 fields, matched event codes, table lookups, and disposition** into the Description, and includes the **raw proc TCL** in a `## Referenced proc source` appendix. **When you get this output WITH the API, polish those surfaced UPOC bits into smooth filter prose in the Description — do NOT invent facts, just smooth what's surfaced.** On an API-blocked host the deterministic bits + appendix ARE the deliverable (no "summarize by hand" marker). |
|
||||||
|
|
||||||
### NetConfig modification — generate, then write via `write_file` (Y/N gated)
|
### NetConfig modification — generate, then write via `write_file` (Y/N gated)
|
||||||
|
|
||||||
|
|||||||
25
larry.sh
25
larry.sh
@ -78,7 +78,7 @@ set -o pipefail
|
|||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
# Config
|
# Config
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
LARRY_VERSION="0.8.20"
|
LARRY_VERSION="0.8.21"
|
||||||
LARRY_HOME="${LARRY_HOME:-$HOME/.larry}"
|
LARRY_HOME="${LARRY_HOME:-$HOME/.larry}"
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
@ -4083,10 +4083,22 @@ tool_larry_rollback_list() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tool_nc_document() {
|
tool_nc_document() {
|
||||||
local pattern="$1" out_path="${2:-}" hciroot="${3:-${HCIROOT:-}}"
|
# SINGLE-THREAD mode: pass `thread` (+ optional `site`) — documents ONE interface
|
||||||
local title="${4:-}" status="${5:-}" poc_internal="${6:-}" poc_vendor="${7:-}" escalation="${8:-}" open_items="${9:-}" notes="${10:-}"
|
# in the Legacy "ADT Messages" template with the full per-delivery breakdown +
|
||||||
|
# deterministic UPOC-bits extraction. SYSTEM mode: pass `name` (a pattern) — one
|
||||||
|
# section per matching delivery thread across sites. Exactly one of thread/name.
|
||||||
|
local thread="$1" name="$2" site="$3" out_path="${4:-}" hciroot="${5:-${HCIROOT:-}}"
|
||||||
|
local title="${6:-}" status="${7:-}" poc_internal="${8:-}" poc_vendor="${9:-}" escalation="${10:-}" open_items="${11:-}" notes="${12:-}"
|
||||||
_lib_err_if_missing || return
|
_lib_err_if_missing || return
|
||||||
local args=(--name "$pattern")
|
[ -n "$thread" ] || [ -n "$name" ] \
|
||||||
|
|| { echo "ERROR: nc_document needs either thread (single interface) or name (system pattern)"; return 1; }
|
||||||
|
local args=()
|
||||||
|
if [ -n "$thread" ]; then
|
||||||
|
args+=(--thread "$thread")
|
||||||
|
[ -n "$site" ] && args+=(--site "$site")
|
||||||
|
else
|
||||||
|
args+=(--name "$name")
|
||||||
|
fi
|
||||||
[ -n "$hciroot" ] && args+=(--hciroot "$hciroot")
|
[ -n "$hciroot" ] && args+=(--hciroot "$hciroot")
|
||||||
[ -n "$out_path" ] && args+=(--out "$out_path")
|
[ -n "$out_path" ] && args+=(--out "$out_path")
|
||||||
[ -n "$title" ] && args+=(--title "$title")
|
[ -n "$title" ] && args+=(--title "$title")
|
||||||
@ -4165,7 +4177,8 @@ execute_tool() {
|
|||||||
"$(J '.field // ""')" "$(J '.value // ""')" \
|
"$(J '.field // ""')" "$(J '.value // ""')" \
|
||||||
"$(J '.limit // 10')" "$(J '.format // "text"')" \
|
"$(J '.limit // 10')" "$(J '.format // "text"')" \
|
||||||
"$(J '.sitedir // ""')" "$(J '.db // ""')" ;;
|
"$(J '.sitedir // ""')" "$(J '.db // ""')" ;;
|
||||||
nc_document) tool_nc_document "$(J '.name')" "$(J '.out // ""')" "$(J '.hciroot // ""')" \
|
nc_document) tool_nc_document "$(J '.thread // ""')" "$(J '.name // ""')" "$(J '.site // ""')" \
|
||||||
|
"$(J '.out // ""')" "$(J '.hciroot // ""')" \
|
||||||
"$(J '.title // ""')" "$(J '.status // ""')" \
|
"$(J '.title // ""')" "$(J '.status // ""')" \
|
||||||
"$(J '.poc_internal // ""')" "$(J '.poc_vendor // ""')" \
|
"$(J '.poc_internal // ""')" "$(J '.poc_vendor // ""')" \
|
||||||
"$(J '.escalation // ""')" "$(J '.open_items // ""')" \
|
"$(J '.escalation // ""')" "$(J '.open_items // ""')" \
|
||||||
@ -4224,7 +4237,7 @@ TOOLS_JSON=$(cat <<'TOOLS_END'
|
|||||||
{"name":"nc_tclproc_refs","description":"List every TCL proc name referenced from a protocol block (or from the whole NetConfig if name is omitted). Pulls from DATAFORMAT.PROC, PREPROCS.PROCS, POSTPROCS.PROCS, etc. Unique sorted.","input_schema":{"type":"object","properties":{"netconfig":{"type":"string"},"name":{"type":"string","description":"Optional. Scope to one protocol."}},"required":["netconfig"]}},
|
{"name":"nc_tclproc_refs","description":"List every TCL proc name referenced from a protocol block (or from the whole NetConfig if name is omitted). Pulls from DATAFORMAT.PROC, PREPROCS.PROCS, POSTPROCS.PROCS, etc. Unique sorted.","input_schema":{"type":"object","properties":{"netconfig":{"type":"string"},"name":{"type":"string","description":"Optional. Scope to one protocol."}},"required":["netconfig"]}},
|
||||||
{"name":"hl7_field","description":"Extract a specific HL7 v2 field from a message. field_path = SEG[.FIELD[.COMPONENT[.SUBCOMPONENT]]]. Examples: PID.3 (MRN), PID.18 (account number), MSH.7 (timestamp), MSH.9.2 (event code, like A08), PID.5 (patient name with components). Multiple repetitions are returned one per line. Native v3, no v1/v2 dependency.","input_schema":{"type":"object","properties":{"message":{"type":"string","description":"Raw HL7 message text. Segments separated by \\r."},"field_path":{"type":"string","description":"Field path like PID.3 or MSH.9.2"}},"required":["message","field_path"]}},
|
{"name":"hl7_field","description":"Extract a specific HL7 v2 field from a message. field_path = SEG[.FIELD[.COMPONENT[.SUBCOMPONENT]]]. Examples: PID.3 (MRN), PID.18 (account number), MSH.7 (timestamp), MSH.9.2 (event code, like A08), PID.5 (patient name with components). Multiple repetitions are returned one per line. Native v3, no v1/v2 dependency.","input_schema":{"type":"object","properties":{"message":{"type":"string","description":"Raw HL7 message text. Segments separated by \\r."},"field_path":{"type":"string","description":"Field path like PID.3 or MSH.9.2"}},"required":["message","field_path"]}},
|
||||||
{"name":"nc_msgs","description":"Query Cloverleaf smat (SQLite!) databases for messages from a thread. Filters: time range, exact HL7 field match. Native v3 — reads smatdb directly with sqlite3 -ascii, no hcidbdump/dbExtract needed. Format text shows messages line-by-line with metadata; count returns just the count; json returns structured data. Operates on LOCAL smatdbs; for a remote env's smatdb, use ssh_pull_smat first (sampled mode is cheaper than pulling the whole DB).","input_schema":{"type":"object","properties":{"thread":{"type":"string","description":"Thread name. The .smatdb file under $HCISITEDIR/exec/processes/*/<thread>.smatdb is auto-located unless db is given."},"after":{"type":"string","description":"Time-after filter. Accepts \"3 days ago\", \"2026-05-20 14:30:00\", \"2026-05-20\", or a unix timestamp."},"before":{"type":"string","description":"Time-before filter, same formats as after."},"field":{"type":"string","description":"HL7 field path for exact-match filter, e.g. PID.18 or MSH.10."},"value":{"type":"string","description":"Value the field must equal. Use with field. Repeatable filters not supported via this single tool call — chain calls if you need multi-field AND."},"limit":{"type":"integer","description":"Max messages to return. Default 10."},"format":{"type":"string","enum":["text","json","count","raw"],"description":"text = human-readable with metadata; count = just the number; json = structured; raw = raw bytes separated by 0x1c."},"sitedir":{"type":"string","description":"Override $HCISITEDIR for thread-to-db location."},"db":{"type":"string","description":"Explicit .smatdb path; overrides auto-locate."}},"required":["thread"]}},
|
{"name":"nc_msgs","description":"Query Cloverleaf smat (SQLite!) databases for messages from a thread. Filters: time range, exact HL7 field match. Native v3 — reads smatdb directly with sqlite3 -ascii, no hcidbdump/dbExtract needed. Format text shows messages line-by-line with metadata; count returns just the count; json returns structured data. Operates on LOCAL smatdbs; for a remote env's smatdb, use ssh_pull_smat first (sampled mode is cheaper than pulling the whole DB).","input_schema":{"type":"object","properties":{"thread":{"type":"string","description":"Thread name. The .smatdb file under $HCISITEDIR/exec/processes/*/<thread>.smatdb is auto-located unless db is given."},"after":{"type":"string","description":"Time-after filter. Accepts \"3 days ago\", \"2026-05-20 14:30:00\", \"2026-05-20\", or a unix timestamp."},"before":{"type":"string","description":"Time-before filter, same formats as after."},"field":{"type":"string","description":"HL7 field path for exact-match filter, e.g. PID.18 or MSH.10."},"value":{"type":"string","description":"Value the field must equal. Use with field. Repeatable filters not supported via this single tool call — chain calls if you need multi-field AND."},"limit":{"type":"integer","description":"Max messages to return. Default 10."},"format":{"type":"string","enum":["text","json","count","raw"],"description":"text = human-readable with metadata; count = just the number; json = structured; raw = raw bytes separated by 0x1c."},"sitedir":{"type":"string","description":"Override $HCISITEDIR for thread-to-db location."},"db":{"type":"string","description":"Explicit .smatdb path; overrides auto-locate."}},"required":["thread"]}},
|
||||||
{"name":"nc_document","description":"Generate a complete markdown knowledge entry for a Cloverleaf subsystem identified by a name pattern. Walks every NetConfig under $HCIROOT, gathers config + sources + destinations + xlates + tclprocs for every matching thread, composes a markdown doc with placeholder context sections (Vendor POC, Internal Owner, Status, Escalation, Open items, Notes). Returns the doc text and (if out is given) writes it to that path.","input_schema":{"type":"object","properties":{"name":{"type":"string","description":"Case-insensitive substring/regex to match protocol names. e.g. 'codametrix', 'epic_adt', '3M'."},"out":{"type":"string","description":"Optional output file path. Convention: $LARRY_HOME/knowledge/<system>.md."},"hciroot":{"type":"string","description":"Override $HCIROOT for the NetConfig scan."},"title":{"type":"string","description":"Doc title. Default derived from name."},"status":{"type":"string","description":"System status fill-in (production/test/decommissioning/...)."},"poc_internal":{"type":"string","description":"Internal owner fill-in."},"poc_vendor":{"type":"string","description":"Vendor POC fill-in."},"escalation":{"type":"string","description":"Escalation path fill-in."},"open_items":{"type":"string","description":"Open items / known issues fill-in. Can be multi-line, will be inserted as-is."},"notes":{"type":"string","description":"Freeform notes fill-in."}},"required":["name"]}},
|
{"name":"nc_document","description":"Document a Cloverleaf INTERFACE end-to-end as a native markdown knowledge entry in Bryan's confirmed Legacy 'ADT Messages' template (Title; Description prose; Message Flow table Platform|Action|Description|From|To with one row per hop Epic-feed → Cloverleaf-routing → Final-Delivery built from the nc_paths route chain; per-delivery breakdown: inbound PROTOCOL TYPE/HOST/PORT/ISSERVER + inbound TRXID/TPS proc, route TRXID filter + TYPE + PREPROCS/POSTPROCS, XLATE, destination host:port/process). EVERYTHING THIS TOOL EMITS IS DETERMINISTIC, PURE BASH, AND API-FREE — it runs identically on an API-blocked host and never calls a model. ★ KEY FEATURE — for every referenced UPOC proc it locates the .tcl under $HCIROOT/<site>/tclprocs/ and DETERMINISTICALLY surfaces, into the Description, a compact bit-line: the proc's COMMENTS (the author's own filter notes), the HL7 FIELDS it references (PID.8, PV1.45, EVN.1…), the MATCHED literal event codes (A01 A02 A03…), TABLE lookups (e.g. PeriCalm_Loc), and the DISPOSITION (pass vs kill). The raw proc TCL is included verbatim in a '## Referenced proc source' appendix for audit (NO 'summarize by hand' marker — the surfaced bits ARE the content). ★ WHEN YOU (the model, running WITH the API) GET THIS TOOL'S OUTPUT: transparently POLISH those surfaced UPOC bits into smoother, human-readable filter descriptions inside the Description prose (e.g. turn 'fields: PV1.45 PID.8 · matches: A01 A02 A03 · table: Pericalm_Loc · disposition: kill non-matching' into 'passes only A01/A02/A03 admit/transfer events for female patients whose location is in the Pericalm_Loc table, killing everything else'). Do NOT invent facts not present in the surfaced bits; just smooth them. On an API-blocked host the deterministic bit-lines + appendix ARE the deliverable. MODES: (a) SINGLE INTERFACE — set thread (the delivery/outbound thread, e.g. 'ADTto_CodaMetrix'; optionally site to disambiguate) → one fully-detailed interface section. (b) SYSTEM/PATTERN — set name (case-insensitive substring/regex, e.g. 'codametrix') → one section per matching delivery thread across ALL sites. Give EXACTLY ONE of thread or name. Returns the doc text and (if out is given) writes it there.","input_schema":{"type":"object","properties":{"thread":{"type":"string","description":"SINGLE-INTERFACE mode: the delivery (outbound) thread to document, e.g. 'ADTto_CodaMetrix' or 'ADTto_periwatch'. Accepts a bare thread name or a 'site/thread' node. Give this OR name, not both."},"name":{"type":"string","description":"SYSTEM mode: case-insensitive substring/regex matching delivery thread names across all sites, e.g. 'codametrix', 'periwatch', 'epic_adt'. One section per match. Give this OR thread, not both."},"site":{"type":"string","description":"Home site of the thread (the NetConfig's parent dir). Optional — disambiguates a thread name present in multiple sites."},"out":{"type":"string","description":"Optional output file path. Convention: $LARRY_HOME/knowledge/<system>.md."},"hciroot":{"type":"string","description":"Override $HCIROOT for the NetConfig scan."},"title":{"type":"string","description":"Doc title. Default derived from thread/name."},"status":{"type":"string","description":"System status fill-in (production/test/decommissioning/...)."},"poc_internal":{"type":"string","description":"Internal owner fill-in."},"poc_vendor":{"type":"string","description":"Vendor POC fill-in."},"escalation":{"type":"string","description":"Escalation path fill-in."},"open_items":{"type":"string","description":"Open items / known issues fill-in. Can be multi-line, will be inserted as-is."},"notes":{"type":"string","description":"Freeform notes fill-in."}},"required":[]}},
|
||||||
|
|
||||||
{"name":"nc_find","description":"Cross-site thread search. Native v3 replacement for v1 tbn/tbp/tbh/tbpr/where. Walks every NetConfig under $HCIROOT and returns matching threads with site, port, host, process, direction, file, line. Modes: name=partial name match (like tbn); port=exact port (like tbp); host=substring on host (like tbh); process=substring on PROCESSNAME (like tbpr); where=exact name match across all sites (like the v1 `<thread> where`); xlate=threads referencing a specific .xlt; tclproc=threads referencing a specific TCL proc.","input_schema":{"type":"object","properties":{"mode":{"type":"string","enum":["name","port","host","process","where","xlate","tclproc"],"description":"Search mode."},"query":{"type":"string","description":"Query value: partial name, port number, host substring, process name, exact thread name, xlate filename, or tclproc name."},"format":{"type":"string","enum":["table","tsv","jsonl"],"description":"Output format. Default table."},"hciroot":{"type":"string","description":"Override $HCIROOT."}},"required":["mode","query"]}},
|
{"name":"nc_find","description":"Cross-site thread search. Native v3 replacement for v1 tbn/tbp/tbh/tbpr/where. Walks every NetConfig under $HCIROOT and returns matching threads with site, port, host, process, direction, file, line. Modes: name=partial name match (like tbn); port=exact port (like tbp); host=substring on host (like tbh); process=substring on PROCESSNAME (like tbpr); where=exact name match across all sites (like the v1 `<thread> where`); xlate=threads referencing a specific .xlt; tclproc=threads referencing a specific TCL proc.","input_schema":{"type":"object","properties":{"mode":{"type":"string","enum":["name","port","host","process","where","xlate","tclproc"],"description":"Search mode."},"query":{"type":"string","description":"Query value: partial name, port number, host substring, process name, exact thread name, xlate filename, or tclproc name."},"format":{"type":"string","enum":["table","tsv","jsonl"],"description":"Output format. Default table."},"hciroot":{"type":"string","description":"Override $HCIROOT."}},"required":["mode","query"]}},
|
||||||
{"name":"nc_insert_protocol","description":"Insert a new protocol block into a NetConfig file. ALL WRITES GO THROUGH THE JOURNAL — original is snapshotted, diff is saved, the file is atomically replaced. Use larry_rollback_list to view, larry-rollback.sh CLI to undo. mode=end appends; mode=after needs anchor=existing-protocol-name; mode=before needs anchor.","input_schema":{"type":"object","properties":{"netconfig":{"type":"string","description":"Target NetConfig file path."},"block":{"type":"string","description":"The full protocol block text (starting with 'protocol NAME {' and ending with '}'). Get this from nc_make_jump output."},"mode":{"type":"string","enum":["end","after","before"],"description":"Insertion position. Default end."},"anchor":{"type":"string","description":"For mode=after|before: existing protocol name to position relative to."}},"required":["netconfig","block"]}},
|
{"name":"nc_insert_protocol","description":"Insert a new protocol block into a NetConfig file. ALL WRITES GO THROUGH THE JOURNAL — original is snapshotted, diff is saved, the file is atomically replaced. Use larry_rollback_list to view, larry-rollback.sh CLI to undo. mode=end appends; mode=after needs anchor=existing-protocol-name; mode=before needs anchor.","input_schema":{"type":"object","properties":{"netconfig":{"type":"string","description":"Target NetConfig file path."},"block":{"type":"string","description":"The full protocol block text (starting with 'protocol NAME {' and ending with '}'). Get this from nc_make_jump output."},"mode":{"type":"string","enum":["end","after","before"],"description":"Insertion position. Default end."},"anchor":{"type":"string","description":"For mode=after|before: existing protocol name to position relative to."}},"required":["netconfig","block"]}},
|
||||||
|
|||||||
@ -1,39 +1,81 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# nc-document.sh — generate a v3 native markdown knowledge entry for a Cloverleaf
|
# nc-document.sh — document a Cloverleaf INTERFACE end-to-end as a native markdown
|
||||||
# subsystem identified by a name pattern. Walks every NetConfig under $HCIROOT
|
# knowledge entry in Bryan's confirmed Legacy "ADT Messages" template.
|
||||||
# (or a passed-in list), gathers config + flow + xlates + tclprocs, composes a
|
#
|
||||||
# markdown doc with placeholder context sections for humans to fill.
|
# Two modes:
|
||||||
|
# SINGLE THREAD nc-document.sh <thread> [site] (e.g. ADTto_CodaMetrix ancout)
|
||||||
|
# nc-document.sh <site>/<thread> (v1 node form)
|
||||||
|
# SYSTEM/PATTERN nc-document.sh --name <pattern> (e.g. --name codametrix)
|
||||||
|
# → one section per matching destination thread, across sites.
|
||||||
|
#
|
||||||
|
# Everything emitted by THIS tool is DETERMINISTIC, PURE BASH+AWK, and API-FREE.
|
||||||
|
# It runs identically on an API-blocked host (e.g. Gundersen). It never calls an
|
||||||
|
# LLM and never reaches the network. The deterministic UPOC-bits + raw proc TCL
|
||||||
|
# appendix ARE the deliverable; when larry runs WITH the API the model transparently
|
||||||
|
# polishes those surfaced bits into smoother prose in the Description — that is
|
||||||
|
# normal agent behavior, NOT a mechanism in this script.
|
||||||
|
#
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# WHAT GETS DOCUMENTED, per interface (one delivery = one outbound thread):
|
||||||
|
# - Title = the interface / message type.
|
||||||
|
# - Description = prose: what the messages are, what the filters key on, where
|
||||||
|
# translation happens, how it's fed — seeded from the surfaced
|
||||||
|
# UPOC bits.
|
||||||
|
# - Message Flow = a table (Platform | Action | Description | From | To), one row
|
||||||
|
# per hop: Epic feed → Cloverleaf cross-site routing → Final
|
||||||
|
# Delivery. Built from nc-paths.sh (the route-chain enumerator).
|
||||||
|
# - Per-delivery breakdown:
|
||||||
|
# inbound PROTOCOL TYPE/HOST/PORT/ISSERVER + inbound TRXID/TPS proc,
|
||||||
|
# the route's TRXID filter + TYPE + PREPROCS/POSTPROCS + XLATE,
|
||||||
|
# destination host:port / process.
|
||||||
|
# - ★ DETERMINISTIC UPOC-BITS — for each referenced proc, locate its .tcl under
|
||||||
|
# $HCIROOT/<site>/tclprocs/ and extract (no API):
|
||||||
|
# 1. comments (header + inline `#` lines — the author's own filter notes)
|
||||||
|
# 2. HL7 fields referenced (PID.8, PV1.45, EVN.1, …)
|
||||||
|
# 3. conditions + literal values (matched event-code lists A01/A02/…, etc.)
|
||||||
|
# 4. table lookups (.tbl / table names, e.g. PeriCalm_Loc)
|
||||||
|
# 5. disposition (CONTINUE / KILL / return — pass vs kill)
|
||||||
|
# Rendered compactly into the Description.
|
||||||
|
# - Raw proc TCL in a plain appendix (`## Referenced proc source`). NO "summarize
|
||||||
|
# by hand / on an API box" marker — the extracted bits are the content.
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
|
# nc-document.sh <thread> [site] [options]
|
||||||
|
# nc-document.sh <site>/<thread> [options]
|
||||||
# nc-document.sh --name <pattern> [options]
|
# nc-document.sh --name <pattern> [options]
|
||||||
#
|
#
|
||||||
# --name PATTERN case-insensitive substring/regex to match protocol names
|
# --name PATTERN SYSTEM mode: case-insensitive substring/regex over thread
|
||||||
|
# names; one interface section per matching OUTBOUND thread.
|
||||||
|
# --thread NAME single-thread mode (alternative to the positional form)
|
||||||
|
# --site NAME home site of the thread (disambiguates a multi-site name)
|
||||||
# --hciroot DIR defaults to $HCIROOT
|
# --hciroot DIR defaults to $HCIROOT
|
||||||
# --netconfigs PATHS colon-separated explicit NetConfig list (overrides --hciroot scan)
|
|
||||||
# --out PATH output markdown path (default: stdout)
|
# --out PATH output markdown path (default: stdout)
|
||||||
# --title TITLE doc title (default: derived from --name)
|
# --title TITLE doc title (default: derived from thread/name)
|
||||||
# --poc-vendor TXT Vendor POC content
|
# --poc-vendor TXT Vendor POC content
|
||||||
# --poc-internal TXT Internal Owner content
|
# --poc-internal TXT Internal Owner content
|
||||||
# --status TXT e.g. production / test / decommissioning
|
# --status TXT e.g. production / test / decommissioning
|
||||||
# --escalation TXT Escalation path text
|
# --escalation TXT Escalation path text
|
||||||
# --open-items TXT Open items text (bulleted by you if multi-line)
|
# --open-items TXT Open items text
|
||||||
# --notes TXT freeform additional notes
|
# --notes TXT freeform additional notes
|
||||||
#
|
# --no-appendix omit the raw proc-source appendix
|
||||||
# Any --poc/-status/--escalation/--open-items/--notes that you OMIT becomes an
|
# -h | --help this help
|
||||||
# empty placeholder section in the doc, ready for someone to fill.
|
|
||||||
set -u
|
set -u
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
|
|
||||||
NC_SELF="$0"
|
NC_SELF="$0"
|
||||||
LIB_DIR="$(cd "$(dirname "$NC_SELF")" && pwd)"
|
LIB_DIR="$(cd "$(dirname "$NC_SELF")" && pwd)"
|
||||||
NCP="$LIB_DIR/nc-parse.sh"
|
NCP="$LIB_DIR/nc-parse.sh"
|
||||||
NCI="$LIB_DIR/nc-inbound.sh"
|
NCPATHS="$LIB_DIR/nc-paths.sh"
|
||||||
|
|
||||||
die() { printf 'nc-document: %s\n' "$*" >&2; exit 1; }
|
die() { printf 'nc-document: %s\n' "$*" >&2; exit 1; }
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Arg parsing
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
PATTERN=""
|
PATTERN=""
|
||||||
|
THREAD_ARG=""
|
||||||
|
SITE_ARG=""
|
||||||
HCIROOT_OVERRIDE=""
|
HCIROOT_OVERRIDE=""
|
||||||
NETCONFIGS_OVERRIDE=""
|
|
||||||
OUT=""
|
OUT=""
|
||||||
TITLE=""
|
TITLE=""
|
||||||
POC_VENDOR=""
|
POC_VENDOR=""
|
||||||
@ -42,45 +84,84 @@ STATUS=""
|
|||||||
ESCALATION=""
|
ESCALATION=""
|
||||||
OPEN_ITEMS=""
|
OPEN_ITEMS=""
|
||||||
NOTES=""
|
NOTES=""
|
||||||
|
WANT_APPENDIX=1
|
||||||
|
POSITIONAL=()
|
||||||
|
|
||||||
while [ $# -gt 0 ]; do
|
while [ $# -gt 0 ]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--name) shift; PATTERN="$1" ;;
|
--name) shift; PATTERN="${1:-}" ;;
|
||||||
--hciroot) shift; HCIROOT_OVERRIDE="$1" ;;
|
--thread) shift; THREAD_ARG="${1:-}" ;;
|
||||||
--netconfigs) shift; NETCONFIGS_OVERRIDE="$1" ;;
|
--site) shift; SITE_ARG="${1:-}" ;;
|
||||||
--out) shift; OUT="$1" ;;
|
--hciroot) shift; HCIROOT_OVERRIDE="${1:-}" ;;
|
||||||
--title) shift; TITLE="$1" ;;
|
--out) shift; OUT="${1:-}" ;;
|
||||||
--poc-vendor) shift; POC_VENDOR="$1" ;;
|
--title) shift; TITLE="${1:-}" ;;
|
||||||
--poc-internal) shift; POC_INTERNAL="$1" ;;
|
--poc-vendor) shift; POC_VENDOR="${1:-}" ;;
|
||||||
--status) shift; STATUS="$1" ;;
|
--poc-internal) shift; POC_INTERNAL="${1:-}" ;;
|
||||||
--escalation) shift; ESCALATION="$1" ;;
|
--status) shift; STATUS="${1:-}" ;;
|
||||||
--open-items) shift; OPEN_ITEMS="$1" ;;
|
--escalation) shift; ESCALATION="${1:-}" ;;
|
||||||
--notes) shift; NOTES="$1" ;;
|
--open-items) shift; OPEN_ITEMS="${1:-}" ;;
|
||||||
-h|--help) sed -n '2,25p' "$NC_SELF"; exit 0 ;;
|
--notes) shift; NOTES="${1:-}" ;;
|
||||||
-*) die "unknown flag: $1" ;;
|
--no-appendix) WANT_APPENDIX=0 ;;
|
||||||
*) die "extra arg: $1" ;;
|
-h|--help) sed -n '2,72p' "$NC_SELF" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
||||||
|
--*) die "unknown flag: $1" ;;
|
||||||
|
*) POSITIONAL+=("$1") ;;
|
||||||
esac
|
esac
|
||||||
shift
|
shift
|
||||||
done
|
done
|
||||||
|
|
||||||
[ -n "$PATTERN" ] || die "missing --name PATTERN"
|
# Positional shapes (single-thread mode):
|
||||||
[ -z "$TITLE" ] && TITLE="$(printf '%s' "$PATTERN" | tr '[:upper:]' '[:lower:]')"
|
# <thread> thread only
|
||||||
|
# <thread> <site> thread + site
|
||||||
|
# <site>/<thread> v1 node form (nc-paths output feeds back in)
|
||||||
|
if [ -z "$THREAD_ARG" ] && [ "${#POSITIONAL[@]}" -ge 1 ]; then THREAD_ARG="${POSITIONAL[0]}"; fi
|
||||||
|
if [ -z "$SITE_ARG" ] && [ "${#POSITIONAL[@]}" -ge 2 ]; then SITE_ARG="${POSITIONAL[1]}"; fi
|
||||||
|
case "$THREAD_ARG" in
|
||||||
|
*/*) _ss="${THREAD_ARG%%/*}"; _st="${THREAD_ARG#*/}"
|
||||||
|
if [ -n "$_ss" ] && [ -n "$_st" ]; then THREAD_ARG="$_st"; SITE_ARG="$_ss"; fi ;;
|
||||||
|
esac
|
||||||
|
|
||||||
# Determine the NetConfig list
|
[ -n "$PATTERN" ] || [ -n "$THREAD_ARG" ] || \
|
||||||
NCONFIGS=()
|
die "give a <thread> [site] (single-thread mode) OR --name PATTERN (system mode). Try --help."
|
||||||
if [ -n "$NETCONFIGS_OVERRIDE" ]; then
|
|
||||||
IFS=':' read -ra NCONFIGS <<< "$NETCONFIGS_OVERRIDE"
|
|
||||||
else
|
|
||||||
ROOT="${HCIROOT_OVERRIDE:-${HCIROOT:-}}"
|
|
||||||
[ -n "$ROOT" ] || die "no \$HCIROOT and no --hciroot; pass one or set the env var"
|
|
||||||
[ -d "$ROOT" ] || die "hciroot not a directory: $ROOT"
|
|
||||||
while IFS= read -r nc; do
|
|
||||||
NCONFIGS+=("$nc")
|
|
||||||
done < <(find "$ROOT" -maxdepth 2 -name NetConfig -type f 2>/dev/null)
|
|
||||||
fi
|
|
||||||
[ ${#NCONFIGS[@]} -gt 0 ] || die "no NetConfig files found"
|
|
||||||
|
|
||||||
# Emit to OUT or stdout
|
ROOT="${HCIROOT_OVERRIDE:-${HCIROOT:-}}"
|
||||||
|
[ -n "$ROOT" ] || die "no \$HCIROOT and no --hciroot; pass one or set the env var"
|
||||||
|
[ -d "$ROOT" ] || die "hciroot not a directory: $ROOT"
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Site discovery: site name → NetConfig path (two parallel arrays, bash-3.2 safe).
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
SITE_NAMES=()
|
||||||
|
SITE_NCS=()
|
||||||
|
while IFS= read -r nc; do
|
||||||
|
[ -f "$nc" ] || continue
|
||||||
|
SITE_NAMES+=("$(basename "$(dirname "$nc")")")
|
||||||
|
SITE_NCS+=("$nc")
|
||||||
|
done < <(find "$ROOT" -maxdepth 2 -name NetConfig -type f 2>/dev/null | sort)
|
||||||
|
[ "${#SITE_NCS[@]}" -gt 0 ] || die "no NetConfig files found under $ROOT"
|
||||||
|
|
||||||
|
_nc_for_site() { # site → NetConfig path (first match)
|
||||||
|
local want="$1" i
|
||||||
|
for ((i=0; i<${#SITE_NAMES[@]}; i++)); do
|
||||||
|
[ "${SITE_NAMES[$i]}" = "$want" ] && { printf '%s' "${SITE_NCS[$i]}"; return 0; }
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Locate the first site whose NetConfig declares <thread>. Emits "site".
|
||||||
|
_locate_thread() {
|
||||||
|
local want="$1" i nc
|
||||||
|
for ((i=0; i<${#SITE_NAMES[@]}; i++)); do
|
||||||
|
nc="${SITE_NCS[$i]}"
|
||||||
|
if "$NCP" list-protocols "$nc" 2>/dev/null | grep -qxF -- "$want"; then
|
||||||
|
printf '%s' "${SITE_NAMES[$i]}"; return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Output sink
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
out_target() {
|
out_target() {
|
||||||
if [ -n "$OUT" ]; then
|
if [ -n "$OUT" ]; then
|
||||||
mkdir -p "$(dirname "$OUT")" 2>/dev/null
|
mkdir -p "$(dirname "$OUT")" 2>/dev/null
|
||||||
@ -90,145 +171,546 @@ out_target() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Gather all matching protocols across all NetConfigs
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
declare -a MATCHES
|
# strip a leading "{" / trailing "}" / empty-brace marker from a scalar value
|
||||||
for nc in "${NCONFIGS[@]}"; do
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
site=$(basename "$(dirname "$nc")")
|
_clean() { printf '%s' "$1" | sed 's/^{}$//; s/^{//; s/}$//'; }
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# ROUTE EXTRACTION (deterministic, pure awk).
|
||||||
|
#
|
||||||
|
# Walk a thread's DATAXLATE block and emit ONE record per route, fields delimited
|
||||||
|
# by the UNIT SEPARATOR (\037 — a NON-whitespace char, so bash `read` does NOT
|
||||||
|
# collapse consecutive empty fields the way it would with TAB/space):
|
||||||
|
# <DEST>\037<TRXID>\037<TYPE>\037<XLATE>\037<PRE>\037<POST>\037<PROCS>\037<WILDCARD>\037<ENABLED>
|
||||||
|
# where PRE/POST/PROCS are space-joined proc-name lists (the actual UPOC procs).
|
||||||
|
# A DATAXLATE route is a depth-2 sub-block; within it ROUTE_DETAILS (depth 3) holds
|
||||||
|
# DEST/TYPE/XLATE and the PREPROCS/POSTPROCS/PROCS nested blocks; TRXID/WILDCARD/
|
||||||
|
# ROUTE_ENABLED sit at the route level (depth 2). Empty {} values are skipped.
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
_routes_of() { # nc thread → US-delimited route records
|
||||||
|
local nc="$1" thr="$2"
|
||||||
|
# Depth map (from the real integrator): DATAXLATE opens 0->2; each ROUTE opens
|
||||||
|
# into depth 3 (first route: a bare `{` 2->3; later routes: `} {` 3->3). At the
|
||||||
|
# ROUTE level (depth 3) sit TRXID / WILDCARD / ROUTE_ENABLED. ROUTE_DETAILS opens
|
||||||
|
# 3->5; DEST/TYPE/XLATE sit at depth 6; PREPROCS/POSTPROCS/PROCS open 6->8 and the
|
||||||
|
# inner `{ PROCS <name> }` sits at depth 8. A new route boundary is any line that
|
||||||
|
# ENTERS or RE-ENTERS a depth-3 sub-block (the `{` or `} {` separator lines).
|
||||||
|
"$NCP" route-block "$nc" "$thr" 2>/dev/null | awk -v US="$(printf '\037')" '
|
||||||
|
BEGIN { depth=0; route=0; pp_mode="" }
|
||||||
|
function flush() {
|
||||||
|
if (route) {
|
||||||
|
printf "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n",
|
||||||
|
dest, US, trxid, US, rtype, US, xlate, US, pre, US, post, US, procs, US, wild, US, enabled
|
||||||
|
}
|
||||||
|
dest=""; trxid=""; rtype=""; xlate=""; pre=""; post=""; procs=""; wild=""; enabled=""
|
||||||
|
}
|
||||||
|
{
|
||||||
|
line=$0
|
||||||
|
no=gsub(/\{/,"{",line); nc_=gsub(/\}/,"}",line)
|
||||||
|
prev=depth; depth += no - nc_
|
||||||
|
stripped=$0; sub(/^[[:space:]]+/,"",stripped); sub(/[[:space:]]+$/,"",stripped)
|
||||||
|
|
||||||
|
# ROUTE BOUNDARY: a line that is exactly `{` (first route, prev 2 -> 3) or
|
||||||
|
# `} {` (close prior + open next, depth stays 3). Both land us at depth 3.
|
||||||
|
if ((prev==2 && depth==3 && stripped=="{") || (stripped=="} {" && depth==3)) {
|
||||||
|
flush(); route=1; next
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!route) next
|
||||||
|
|
||||||
|
# route-level scalars live at depth 3 (prev==3 before any brace change)
|
||||||
|
if (prev==3) {
|
||||||
|
if (match($0, /\{ TRXID .* \}/)) { v=$0; sub(/^[[:space:]]+\{ TRXID /,"",v); sub(/ \}[[:space:]]*$/,"",v); trxid=v }
|
||||||
|
if (match($0, /\{ WILDCARD [A-Za-z]+ \}/)) { v=$0; sub(/^[[:space:]]+\{ WILDCARD /,"",v); sub(/ \}[[:space:]]*$/,"",v); wild=v }
|
||||||
|
if (match($0, /\{ ROUTE_ENABLED [0-9]+ \}/)){ v=$0; sub(/^[[:space:]]+\{ ROUTE_ENABLED /,"",v); sub(/ \}[[:space:]]*$/,"",v); enabled=v }
|
||||||
|
}
|
||||||
|
# ROUTE_DETAILS scalars (DEST/TYPE/XLATE) — each on its own line at depth 6
|
||||||
|
if (match($0, /\{ DEST [A-Za-z0-9_]+ \}/)) { v=$0; sub(/^.*\{ DEST /,"",v); sub(/ \}.*$/,"",v); dest=v }
|
||||||
|
if (match($0, /\{ TYPE [A-Za-z0-9_]+ \}/)) { v=$0; sub(/^.*\{ TYPE /,"",v); sub(/ \}.*$/,"",v); rtype=v }
|
||||||
|
if (match($0, /\{ XLATE [A-Za-z0-9_.]+ \}/)) { v=$0; sub(/^.*\{ XLATE /,"",v); sub(/ \}.*$/,"",v); xlate=v }
|
||||||
|
|
||||||
|
# enter a PREPROCS / POSTPROCS / (bare) PROCS block; the inner PROCS line
|
||||||
|
# carries the actual proc name(s).
|
||||||
|
if ($0 ~ /\{ PREPROCS \{$/) pp_mode="pre"
|
||||||
|
else if ($0 ~ /\{ POSTPROCS \{$/) pp_mode="post"
|
||||||
|
else if ($0 ~ /\{ PROCS \{$/) pp_mode="procs"
|
||||||
|
# the inner PROCS line: { PROCS name } | { PROCS {a b} } | { PROCS {} }
|
||||||
|
if (pp_mode != "" && match($0, /\{ PROCS /)) {
|
||||||
|
v=$0; sub(/^[[:space:]]+\{ PROCS /,"",v); sub(/[[:space:]]*\}[[:space:]]*$/,"",v)
|
||||||
|
gsub(/[{}]/,"",v); gsub(/^[[:space:]]+|[[:space:]]+$/,"",v)
|
||||||
|
if (v != "") {
|
||||||
|
if (pp_mode=="pre") pre = (pre=="" ? v : pre " " v)
|
||||||
|
else if (pp_mode=="post") post= (post=="" ? v : post " " v)
|
||||||
|
else procs=(procs=="" ? v : procs " " v)
|
||||||
|
}
|
||||||
|
pp_mode=""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
END { flush() }
|
||||||
|
'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Inbound how-received facts. We read each field into a NAMED variable directly
|
||||||
|
# (NOT positional TSV — bash `read` with a single-char IFS collapses CONSECUTIVE
|
||||||
|
# empty fields, which silently shifts columns when HOST/PORT/ISSERVER are empty on
|
||||||
|
# an ICL/file inbound). Caller passes a prefix; we set <prefix>_TYPE etc. via
|
||||||
|
# globals. Robust and order-independent.
|
||||||
|
_inbound_facts() { # nc thread
|
||||||
|
local nc="$1" thr="$2"
|
||||||
|
IN_TYPE=$("$NCP" protocol-nested "$nc" "$thr" PROTOCOL.TYPE 2>/dev/null | head -1)
|
||||||
|
IN_HOST=$(_clean "$("$NCP" protocol-nested "$nc" "$thr" PROTOCOL.HOST 2>/dev/null | head -1)")
|
||||||
|
IN_PORT=$(_clean "$("$NCP" protocol-nested "$nc" "$thr" PROTOCOL.PORT 2>/dev/null | head -1)")
|
||||||
|
IN_ISSERVER=$("$NCP" protocol-nested "$nc" "$thr" PROTOCOL.ISSERVER 2>/dev/null | head -1)
|
||||||
|
IN_ICLPORT=$(_clean "$("$NCP" protocol-field "$nc" "$thr" ICLSERVERPORT 2>/dev/null | head -1)")
|
||||||
|
IN_PROC=$("$NCP" protocol-nested "$nc" "$thr" DATAFORMAT.PROC 2>/dev/null | head -1)
|
||||||
|
IN_PNAME=$("$NCP" protocol-field "$nc" "$thr" PROCESSNAME 2>/dev/null | head -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# ★ DETERMINISTIC UPOC-BITS EXTRACTION (the key feature).
|
||||||
|
#
|
||||||
|
# Locate <proc>.tcl under $HCIROOT/<site>/tclprocs/ (then any site as a fallback)
|
||||||
|
# and extract, with NO API:
|
||||||
|
# COMMENTS — header + inline `#` lines (the author's own filter notes), cleaned.
|
||||||
|
# FIELDS — HL7 field accessors / segment-field tokens (PID.8, PV1_3_3, EVN.1,
|
||||||
|
# getHL7Field … "PV1" N, replaceHL7Field … SEG N).
|
||||||
|
# MATCHES — literal HL7 event/trigger codes referenced (A01 A02 … A53).
|
||||||
|
# CONDS — `if`/condition lines and other literal comparison values.
|
||||||
|
# TABLES — tbllookup / .tbl table names (e.g. PeriCalm_Loc).
|
||||||
|
# DISP — dispositions: CONTINUE / KILL / return (pass vs kill).
|
||||||
|
# Output is a small set of key=value lines on stdout (one fact-list per key):
|
||||||
|
# TCLFILE=<abs path or empty>
|
||||||
|
# COMMENTS<TAB>... (one per matched comment, capped)
|
||||||
|
# FIELDS=<space-joined sorted-unique>
|
||||||
|
# MATCHES=<space-joined sorted-unique>
|
||||||
|
# TABLES=<space-joined sorted-unique>
|
||||||
|
# DISP=<space-joined sorted-unique>
|
||||||
|
# CONDS<TAB>... (one per condition line, capped)
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
_find_tcl() { # site proc → abs path or empty
|
||||||
|
local site="$1" proc="$2" p
|
||||||
|
[ -z "$proc" ] && return 0
|
||||||
|
# 1) home site
|
||||||
|
p="$ROOT/$site/tclprocs/$proc.tcl"
|
||||||
|
[ -f "$p" ] && { printf '%s' "$p"; return 0; }
|
||||||
|
# 2) any site (deterministic order — first wins)
|
||||||
|
local i
|
||||||
|
for ((i=0; i<${#SITE_NAMES[@]}; i++)); do
|
||||||
|
p="$ROOT/${SITE_NAMES[$i]}/tclprocs/$proc.tcl"
|
||||||
|
[ -f "$p" ] && { printf '%s' "$p"; return 0; }
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract the bits from a single .tcl file. Emits the key=value / key<TAB>... stream.
|
||||||
|
_upoc_bits() { # tclfile
|
||||||
|
local f="$1"
|
||||||
|
[ -n "$f" ] || return 0
|
||||||
|
[ -f "$f" ] || return 0
|
||||||
|
# PORTABILITY: this awk uses NO `\b` word-boundary metachar — BSD/BWK awk
|
||||||
|
# (macOS) and mawk do not support it (it silently matches nothing). Token
|
||||||
|
# boundaries are enforced by explicit char-class scanning instead.
|
||||||
|
awk '
|
||||||
|
BEGIN {
|
||||||
|
ncomm=0; ncond=0; MAXCOMM=24; MAXCOND=18
|
||||||
|
# segment ids we recognise (for the underscore PV1_3_3 form and seg N form)
|
||||||
|
segs="MSH EVN PID PV1 PV2 OBR OBX ORC NK1 IN1 GT1 ZPD ZID MRG DG1 AL1 SCH RGS AIS AIL AIP MSA ZIN"
|
||||||
|
nseg=split(segs, SEG, " "); for (i=1;i<=nseg;i++) ISSEG[SEG[i]]=1
|
||||||
|
}
|
||||||
|
function addset(arr, key) { if (key != "" && !(key in arr)) arr[key]=1 }
|
||||||
|
# is char c an identifier char (so a token boundary is a NON-identifier char)?
|
||||||
|
function idc(ch) { return (ch ~ /[A-Za-z0-9_]/) }
|
||||||
|
{
|
||||||
|
line=$0
|
||||||
|
# ---- comments: lines whose first non-space char is # ----
|
||||||
|
c=line; sub(/^[[:space:]]+/,"",c)
|
||||||
|
if (c ~ /^#/) {
|
||||||
|
t=c; sub(/^#+[[:space:]]*/,"",t); gsub(/[[:space:]]+$/,"",t)
|
||||||
|
if (t != "" && t !~ /^[#=*_-]+$/) { if (ncomm < MAXCOMM) comm[++ncomm]=t }
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- HL7 field accessors: dotted form PID.8 / PV1.45 / EVN.1 / MSH.9.1 ----
|
||||||
|
s=line
|
||||||
|
while (match(s, /[A-Z][A-Z][A-Z0-9]\.[0-9]+(\.[0-9]+)?/)) {
|
||||||
|
tok=substr(s, RSTART, RLENGTH); addset(fields, tok); s=substr(s, RSTART+RLENGTH)
|
||||||
|
}
|
||||||
|
# underscore form PV1_3_3 / EVN_5_8 / PID_8 -> normalize to dotted. We scan
|
||||||
|
# for SEG_<digits>(_<digits>) where SEG is a known segment id, enforcing a
|
||||||
|
# boundary by requiring the char before SEG to be non-identifier.
|
||||||
|
s=line
|
||||||
|
while (match(s, /[A-Z][A-Z][A-Z0-9]_[0-9]+(_[0-9]+)?/)) {
|
||||||
|
st=RSTART; ln=RLENGTH; tok=substr(s, st, ln)
|
||||||
|
before = (st==1) ? "" : substr(s, st-1, 1)
|
||||||
|
seg3=substr(tok,1,3)
|
||||||
|
if ((before=="" || !idc(before)) && (seg3 in ISSEG)) {
|
||||||
|
d=tok; gsub(/_/,".",d); addset(fields, d)
|
||||||
|
}
|
||||||
|
s=substr(s, st+ln)
|
||||||
|
}
|
||||||
|
# replaceHL7Field/getHL7Field on a NAMED segment + numeric field: SEG N
|
||||||
|
s=line
|
||||||
|
while (match(s, /(MSH|EVN|PID|PV1|PV2|OBR|OBX|ORC|MSA|DG1|AL1|NK1|IN1)[[:space:]]+[0-9]+/)) {
|
||||||
|
st=RSTART; ln=RLENGTH; tok=substr(s, st, ln)
|
||||||
|
before=(st==1)?"":substr(s, st-1, 1)
|
||||||
|
# only when the line is an HL7 field op (avoids matching arbitrary "PV1 6")
|
||||||
|
if ((before=="" || !idc(before)) && line ~ /(get|replace)HL7Field/) {
|
||||||
|
gsub(/[[:space:]]+/,".",tok); addset(fields, tok)
|
||||||
|
}
|
||||||
|
s=substr(s, st+ln)
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- literal HL7 trigger/event codes A01..A99 (boundary-checked, no \b) ----
|
||||||
|
s=line
|
||||||
|
while (match(s, /A[0-9][0-9]/)) {
|
||||||
|
st=RSTART; ln=RLENGTH; tok=substr(s, st, ln)
|
||||||
|
before=(st==1)?"":substr(s, st-1, 1)
|
||||||
|
afterpos=st+ln; after=(afterpos>length(s))?"":substr(s, afterpos, 1)
|
||||||
|
if ((before=="" || !idc(before)) && (after=="" || !idc(after))) addset(matches, tok)
|
||||||
|
s=substr(s, st+ln)
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- table lookups: tbllookup <TABLE> ... / word.tbl ----
|
||||||
|
if (match(line, /tbllookup[[:space:]]+[A-Za-z_][A-Za-z0-9_]*/)) {
|
||||||
|
tb=substr(line,RSTART,RLENGTH); sub(/tbllookup[[:space:]]+/,"",tb); addset(tables, tb)
|
||||||
|
}
|
||||||
|
s=line
|
||||||
|
while (match(s, /[A-Za-z_][A-Za-z0-9_]*\.tbl/)) {
|
||||||
|
tok=substr(s, RSTART, RLENGTH); sub(/\.tbl$/,"",tok); addset(tables, tok); s=substr(s, RSTART+RLENGTH)
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- dispositions: plain substring match, boundary not needed (these are
|
||||||
|
# distinct all-caps tokens in TCL disposition code) ----
|
||||||
|
if (line ~ /CONTINUE/) addset(disp, "CONTINUE")
|
||||||
|
if (line ~ /KILL/) addset(disp, "KILL")
|
||||||
|
if (line ~ /disp/ && line ~ /ERROR/) addset(disp, "ERROR")
|
||||||
|
if (c ~ /^return([[:space:]]|$)/ || line ~ /return "\{/) addset(disp, "return")
|
||||||
|
|
||||||
|
# ---- condition lines: TCL `if {...}` carrying a comparison ----
|
||||||
|
ct=line; sub(/^[[:space:]]+/,"",ct); gsub(/[[:space:]]+$/,"",ct)
|
||||||
|
if (ct ~ /^(if|elseif|\} elseif|switch)([[:space:]]|\{)/ && ct ~ /(==|!=|<|>|lcontain|cequal|string)/) {
|
||||||
|
if (ncond < MAXCOND) cond[++ncond]=ct
|
||||||
|
}
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
# FIELDS / MATCHES / TABLES / DISP: sorted-unique, space-joined
|
||||||
|
printf "FIELDS="; n=0; for (k in fields) { a[n++]=k }
|
||||||
|
asort_keys(a, n); for (i=0;i<n;i++) printf "%s%s", (i?" ":""), a[i]; printf "\n"
|
||||||
|
delete a
|
||||||
|
printf "MATCHES="; n=0; for (k in matches) { a[n++]=k }
|
||||||
|
asort_keys(a, n); for (i=0;i<n;i++) printf "%s%s", (i?" ":""), a[i]; printf "\n"
|
||||||
|
delete a
|
||||||
|
printf "TABLES="; n=0; for (k in tables) { a[n++]=k }
|
||||||
|
asort_keys(a, n); for (i=0;i<n;i++) printf "%s%s", (i?" ":""), a[i]; printf "\n"
|
||||||
|
delete a
|
||||||
|
printf "DISP="; n=0; for (k in disp) { a[n++]=k }
|
||||||
|
asort_keys(a, n); for (i=0;i<n;i++) printf "%s%s", (i?" ":""), a[i]; printf "\n"
|
||||||
|
for (i=1;i<=ncomm;i++) printf "COMMENT\t%s\n", comm[i]
|
||||||
|
for (i=1;i<=ncond;i++) printf "COND\t%s\n", cond[i]
|
||||||
|
}
|
||||||
|
# portable insertion sort (no gawk asort dependency — works with mawk/BWK awk)
|
||||||
|
function asort_keys(arr, n, i, j, tmp) {
|
||||||
|
for (i=1;i<n;i++){ tmp=arr[i]; j=i-1; while (j>=0 && arr[j]>tmp){ arr[j+1]=arr[j]; j-- } arr[j+1]=tmp }
|
||||||
|
}
|
||||||
|
' "$f"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Compose the compact one-line UPOC summary for the Description, from a bits stream.
|
||||||
|
# _upoc_oneline <procname> <bits-file>
|
||||||
|
_upoc_oneline() {
|
||||||
|
local proc="$1" bf="$2"
|
||||||
|
local fields matches tables disp comments
|
||||||
|
fields=$(awk -F= '/^FIELDS=/{sub(/^FIELDS=/,"");print}' "$bf")
|
||||||
|
matches=$(awk -F= '/^MATCHES=/{sub(/^MATCHES=/,"");print}' "$bf")
|
||||||
|
tables=$(awk -F= '/^TABLES=/{sub(/^TABLES=/,"");print}' "$bf")
|
||||||
|
disp=$(awk -F= '/^DISP=/{sub(/^DISP=/,"");print}' "$bf")
|
||||||
|
comments=$(awk -F'\t' '/^COMMENT\t/{print $2}' "$bf" \
|
||||||
|
| grep -iE 'pass|filter|block|only|kill|continue|drop|route|female|newborn|discharge|location|purpose|determine' \
|
||||||
|
| grep -ivE '^(name|author|date|args|returns|upoc type|revision|notes)\b' \
|
||||||
|
| head -3 \
|
||||||
|
| awk 'NR==1{printf "%s",$0;next}{printf " · %s",$0}END{print ""}')
|
||||||
|
local out="UPOC \`$proc\`"
|
||||||
|
[ -n "$comments" ] && out="$out — $comments"
|
||||||
|
[ -n "$fields" ] && out="$out · fields: $fields"
|
||||||
|
[ -n "$matches" ] && out="$out · matches: $matches"
|
||||||
|
[ -n "$tables" ] && out="$out · table: $tables"
|
||||||
|
if [ -n "$disp" ]; then
|
||||||
|
case "$disp" in
|
||||||
|
*KILL*CONTINUE*|*CONTINUE*KILL*) out="$out · disposition: pass matching / kill non-matching" ;;
|
||||||
|
*KILL*) out="$out · disposition: kill non-matching" ;;
|
||||||
|
*CONTINUE*) out="$out · disposition: pass matching" ;;
|
||||||
|
*) out="$out · disposition: $disp" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
printf '%s\n' "$out"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Build the doc section for ONE outbound (delivery) thread.
|
||||||
|
# $1 = outbound thread name $2 = its home site $3 = its NetConfig
|
||||||
|
# Emits markdown to stdout. Appends raw proc TCL paths to the global APPENDIX_PROCS
|
||||||
|
# set (printed once at the end).
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
declare -A APPENDIX_SEEN
|
||||||
|
APPENDIX_LIST=() # "site|proc|abs-path" records
|
||||||
|
|
||||||
|
_register_appendix() { # site proc
|
||||||
|
local site="$1" proc="$2" key="$site|$proc" p
|
||||||
|
[ -z "$proc" ] && return 0
|
||||||
|
[ -n "${APPENDIX_SEEN[$key]:-}" ] && return 0
|
||||||
|
APPENDIX_SEEN[$key]=1
|
||||||
|
p=$(_find_tcl "$site" "$proc")
|
||||||
|
APPENDIX_LIST+=("$site|$proc|$p")
|
||||||
|
}
|
||||||
|
|
||||||
|
document_thread() {
|
||||||
|
local ob="$1" site="$2" nc="$3"
|
||||||
|
|
||||||
|
# --- the full route chain (flow) via nc-paths ---
|
||||||
|
local chain
|
||||||
|
chain=$("$NCPATHS" "$site/$ob" --hciroot "$ROOT" --format v1 2>/dev/null | head -1)
|
||||||
|
|
||||||
|
# --- destination (the outbound thread's delivery endpoint) ---
|
||||||
|
local dtype dhost dport dproc
|
||||||
|
dtype=$("$NCP" protocol-nested "$nc" "$ob" PROTOCOL.TYPE 2>/dev/null | head -1)
|
||||||
|
dhost=$(_clean "$("$NCP" protocol-nested "$nc" "$ob" PROTOCOL.HOST 2>/dev/null | head -1)")
|
||||||
|
dport=$(_clean "$("$NCP" protocol-nested "$nc" "$ob" PROTOCOL.PORT 2>/dev/null | head -1)")
|
||||||
|
dproc=$("$NCP" protocol-field "$nc" "$ob" PROCESSNAME 2>/dev/null | head -1)
|
||||||
|
|
||||||
|
# --- find the SOURCE (routing) thread: who DESTs to this outbound, same site ---
|
||||||
|
local route_thr=""
|
||||||
|
while IFS= read -r s; do
|
||||||
|
[ -z "$s" ] && continue
|
||||||
|
route_thr="$s"; break
|
||||||
|
done < <("$NCP" sources "$nc" "$ob" 2>/dev/null)
|
||||||
|
|
||||||
|
# --- the specific route (TRXID/TYPE/XLATE/PRE/POST) that targets this outbound ---
|
||||||
|
local r_trxid="" r_type="" r_xlate="" r_pre="" r_post="" r_procs="" r_wild="" r_enabled=""
|
||||||
|
local US; US=$(printf '\037')
|
||||||
|
if [ -n "$route_thr" ]; then
|
||||||
|
while IFS="$US" read -r dest trxid rtype xlate pre post procs wild enabled; do
|
||||||
|
[ "$dest" = "$ob" ] || continue
|
||||||
|
r_trxid="$trxid"; r_type="$rtype"; r_xlate="$xlate"
|
||||||
|
r_pre="$pre"; r_post="$post"; r_procs="$procs"; r_wild="$wild"; r_enabled="$enabled"
|
||||||
|
break
|
||||||
|
done < <(_routes_of "$nc" "$route_thr")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- inbound how-received facts for the routing thread (the local inbound) ---
|
||||||
|
local in_type="" in_host="" in_port="" in_isserver="" in_iclport="" in_proc="" in_pname=""
|
||||||
|
if [ -n "$route_thr" ]; then
|
||||||
|
IN_TYPE=""; IN_HOST=""; IN_PORT=""; IN_ISSERVER=""; IN_ICLPORT=""; IN_PROC=""; IN_PNAME=""
|
||||||
|
_inbound_facts "$nc" "$route_thr"
|
||||||
|
in_type="$IN_TYPE"; in_host="$IN_HOST"; in_port="$IN_PORT"; in_isserver="$IN_ISSERVER"
|
||||||
|
in_iclport="$IN_ICLPORT"; in_proc="$IN_PROC"; in_pname="$IN_PNAME"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- the feed root (Epic-side) from the chain ---
|
||||||
|
local feed_root feed_site feed_thr
|
||||||
|
feed_root="${chain%% *}" # first node "site/thread"
|
||||||
|
feed_site="${feed_root%%/*}"; feed_thr="${feed_root#*/}"
|
||||||
|
|
||||||
|
# ── UPOC bits for every proc this delivery touches (inbound TRXID/TPS proc +
|
||||||
|
# the route's PRE/POST/PROCS). Collect bits files for the Description and
|
||||||
|
# register the raw TCL for the appendix.
|
||||||
|
local upoc_lines=() bf proc
|
||||||
|
for proc in $in_proc $r_pre $r_post $r_procs; do
|
||||||
|
[ -z "$proc" ] && continue
|
||||||
|
local tcl; tcl=$(_find_tcl "$site" "$proc")
|
||||||
|
[ -z "$tcl" ] && tcl=$(_find_tcl "$feed_site" "$proc")
|
||||||
|
if [ -n "$tcl" ]; then
|
||||||
|
bf=$(mktemp); _upoc_bits "$tcl" > "$bf"
|
||||||
|
upoc_lines+=("$(_upoc_oneline "$proc" "$bf")")
|
||||||
|
rm -f "$bf"
|
||||||
|
else
|
||||||
|
upoc_lines+=("UPOC \`$proc\` — _(proc .tcl not found under any site's tclprocs/)_")
|
||||||
|
fi
|
||||||
|
_register_appendix "$site" "$proc"
|
||||||
|
done
|
||||||
|
|
||||||
|
# ─────────────────────────── render the section ───────────────────────────
|
||||||
|
printf '## %s\n\n' "$ob"
|
||||||
|
|
||||||
|
# Description (prose seeded from deterministic facts; the model polishes this
|
||||||
|
# into smoother prose when run WITH the API — no marker here).
|
||||||
|
printf '### Description\n\n'
|
||||||
|
{
|
||||||
|
printf 'The **%s** interface delivers messages to `%s`' "$ob" "$ob"
|
||||||
|
[ -n "$dhost" ] && printf ' on **%s' "$dhost"
|
||||||
|
[ -n "$dport" ] && printf ':%s' "$dport"
|
||||||
|
[ -n "$dhost" ] && printf '**'
|
||||||
|
[ -n "$dproc" ] && printf ' (process `%s`)' "$dproc"
|
||||||
|
printf '.'
|
||||||
|
if [ -n "$route_thr" ]; then
|
||||||
|
printf ' Routing and filtering happen on the inbound thread `%s`' "$route_thr"
|
||||||
|
[ -n "$in_proc" ] && printf ', whose inbound TRXID/TPS proc `%s` assigns the transaction id that the routes key on' "$in_proc"
|
||||||
|
printf '.'
|
||||||
|
fi
|
||||||
|
if [ -n "$r_trxid" ]; then
|
||||||
|
printf ' This delivery is selected by the TRXID filter `%s`' "$r_trxid"
|
||||||
|
[ "$r_wild" = "ON" ] && printf ' (wildcard match)'
|
||||||
|
printf '.'
|
||||||
|
fi
|
||||||
|
if [ -n "$r_xlate" ]; then
|
||||||
|
printf ' Translation is done by the xlate `%s`' "$r_xlate"
|
||||||
|
printf '.'
|
||||||
|
elif [ "$r_type" = "raw" ]; then
|
||||||
|
printf ' Messages are passed **raw** (no translation).'
|
||||||
|
fi
|
||||||
|
printf '\n\n'
|
||||||
|
}
|
||||||
|
if [ "${#upoc_lines[@]}" -gt 0 ]; then
|
||||||
|
printf 'Filter / translation logic (surfaced deterministically from the referenced UPOC procs):\n\n'
|
||||||
|
local l
|
||||||
|
for l in "${upoc_lines[@]}"; do printf -- '- %s\n' "$l"; done
|
||||||
|
printf '\n'
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Message Flow table. The middle "routing" row's wording adapts to whether the
|
||||||
|
# chain actually crosses a site boundary (a `==>` hop): cross-site routing goes
|
||||||
|
# via a named destination block; an intra-site chain is a local DATAXLATE route.
|
||||||
|
local is_cross=0 route_desc
|
||||||
|
case "$chain" in *' ==> '*) is_cross=1 ;; esac
|
||||||
|
if [ "$is_cross" = "1" ]; then
|
||||||
|
route_desc="Cross-site route via destination block; inbound \`${route_thr:-?}\` keys TRXID and routes per delivery"
|
||||||
|
else
|
||||||
|
route_desc="Intra-site DATAXLATE route; inbound \`${route_thr:-?}\` keys TRXID and routes per delivery"
|
||||||
|
fi
|
||||||
|
printf '### Message Flow\n\n'
|
||||||
|
printf '| Platform | Action | Description | From | To |\n'
|
||||||
|
printf '|---|---|---|---|---|\n'
|
||||||
|
# Row 1: Epic feed — From = the upstream system/process, To = the engine feed thread.
|
||||||
|
printf '| Epic | feed | Raw Epic feed entering the integrator | Epic (process `%s`) | `%s` |\n' \
|
||||||
|
"${in_pname:-${dproc:-ADT}}" "${feed_root:-—}"
|
||||||
|
# Row 2: Cloverleaf routing (the chain itself)
|
||||||
|
printf '| Cloverleaf%s | message routing | %s | `%s` | `%s` |\n' \
|
||||||
|
"$( [ "$is_cross" = "1" ] && printf ' (cross-site)' || printf '' )" \
|
||||||
|
"$route_desc" "${chain:-—}" "${route_thr:-—}"
|
||||||
|
# Row 3: Final delivery
|
||||||
|
printf '| Final Delivery | outbound to %s | TRXID `%s` → TYPE `%s`%s | `%s` | `%s`%s%s |\n' \
|
||||||
|
"${dproc:-vendor}" "${r_trxid:-—}" "${r_type:-—}" \
|
||||||
|
"$( [ -n "$r_xlate" ] && printf ', xlate `%s`' "$r_xlate" )" \
|
||||||
|
"${route_thr:-—}" "$ob" \
|
||||||
|
"$( [ -n "$dhost" ] && printf ' → %s' "$dhost" )" \
|
||||||
|
"$( [ -n "$dport" ] && printf ':%s' "$dport" )"
|
||||||
|
printf '\n'
|
||||||
|
|
||||||
|
# Per-delivery breakdown
|
||||||
|
printf '### Delivery breakdown — `%s`\n\n' "$ob"
|
||||||
|
printf -- '- **Flow:** `%s`\n' "${chain:-—}"
|
||||||
|
printf -- '- **How received (inbound `%s`):** PROTOCOL TYPE `%s`' "${route_thr:-?}" "${in_type:-—}"
|
||||||
|
[ -n "$in_host" ] && printf ' · HOST `%s`' "$in_host"
|
||||||
|
[ -n "$in_port" ] && printf ' · PORT `%s`' "$in_port"
|
||||||
|
[ -n "$in_isserver" ] && printf ' · ISSERVER `%s`' "$in_isserver"
|
||||||
|
[ -n "$in_iclport" ] && printf ' · ICLSERVERPORT `%s`' "$in_iclport"
|
||||||
|
printf '\n'
|
||||||
|
printf -- '- **Inbound TRXID/TPS proc:** `%s`\n' "${in_proc:-—}"
|
||||||
|
printf -- '- **Route TRXID filter:** `%s`%s\n' "${r_trxid:-—}" "$( [ "$r_wild" = "ON" ] && printf ' (WILDCARD ON)' )"
|
||||||
|
printf -- '- **Route TYPE:** `%s`\n' "${r_type:-—}"
|
||||||
|
printf -- '- **UPOC PREPROCS:** `%s`\n' "${r_pre:-—}"
|
||||||
|
printf -- '- **UPOC POSTPROCS:** `%s`\n' "${r_post:-—}"
|
||||||
|
printf -- '- **XLATE:** `%s`\n' "${r_xlate:-—}"
|
||||||
|
printf -- '- **Destination:** `%s`%s%s · process `%s` · TYPE `%s`\n' \
|
||||||
|
"${dhost:-—}" "$( [ -n "$dport" ] && printf ':%s' "$dport" )" "" "${dproc:-—}" "${dtype:-—}"
|
||||||
|
printf '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Resolve the set of OUTBOUND (delivery) threads to document.
|
||||||
|
# single-thread mode: the one thread (resolve its site).
|
||||||
|
# system mode: every thread (across sites) matching --name that is OUTBOUNDONLY
|
||||||
|
# (a delivery endpoint); if a matched thread is NOT outbound (e.g. an
|
||||||
|
# inbound router) we still document its OUTBOUND children that match.
|
||||||
|
# Emits "site|nc|thread" records.
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
TARGETS=()
|
||||||
|
|
||||||
|
if [ -n "$THREAD_ARG" ]; then
|
||||||
|
local_site="$SITE_ARG"
|
||||||
|
if [ -z "$local_site" ]; then
|
||||||
|
local_site=$(_locate_thread "$THREAD_ARG") || die "thread not found in any site under $ROOT: $THREAD_ARG"
|
||||||
|
fi
|
||||||
|
nc=$(_nc_for_site "$local_site") || die "no NetConfig for site: $local_site"
|
||||||
|
"$NCP" list-protocols "$nc" 2>/dev/null | grep -qxF -- "$THREAD_ARG" \
|
||||||
|
|| die "thread '$THREAD_ARG' not found in site '$local_site'"
|
||||||
|
TARGETS+=("$local_site|$nc|$THREAD_ARG")
|
||||||
|
[ -z "$TITLE" ] && TITLE="$THREAD_ARG"
|
||||||
|
else
|
||||||
|
# system / pattern mode: document each matching DELIVERY (outbound) thread. A
|
||||||
|
# delivery endpoint is a thread that is NOT an inbound TCP listener (ISSERVER!=1)
|
||||||
|
# and NOT an ICL/file inbound router (OBWORKASIB!=1) — i.e. it has an outbound
|
||||||
|
# client connection to a downstream system. This covers both pure OUTBOUNDONLY=1
|
||||||
|
# threads (e.g. ADTto_CodaMetrix) and bidirectional `Xto_*` deliveries whose
|
||||||
|
# OUTBOUNDONLY=0 (e.g. DFTto_codaMetrix). Inbound listeners/routers are skipped
|
||||||
|
# (they are documented as the "how received" leg of their deliveries).
|
||||||
|
for ((i=0; i<${#SITE_NAMES[@]}; i++)); do
|
||||||
|
site="${SITE_NAMES[$i]}"; nc="${SITE_NCS[$i]}"
|
||||||
while IFS= read -r prot; do
|
while IFS= read -r prot; do
|
||||||
[ -z "$prot" ] && continue
|
[ -z "$prot" ] && continue
|
||||||
MATCHES+=("$site|$nc|$prot")
|
isserver=$("$NCP" protocol-nested "$nc" "$prot" PROTOCOL.ISSERVER 2>/dev/null | head -1)
|
||||||
|
obib=$("$NCP" protocol-field "$nc" "$prot" OBWORKASIB 2>/dev/null | head -1)
|
||||||
|
[ "$isserver" = "1" ] && continue # inbound listener — not a delivery
|
||||||
|
[ "$obib" = "1" ] && continue # ICL/file inbound router — not a delivery
|
||||||
|
TARGETS+=("$site|$nc|$prot")
|
||||||
done < <("$NCP" list-protocols "$nc" 2>/dev/null | grep -i -- "$PATTERN" || true)
|
done < <("$NCP" list-protocols "$nc" 2>/dev/null | grep -i -- "$PATTERN" || true)
|
||||||
done
|
done
|
||||||
|
[ "${#TARGETS[@]}" -gt 0 ] || die "no delivery (outbound) threads matching \"$PATTERN\" under $ROOT"
|
||||||
if [ ${#MATCHES[@]} -eq 0 ]; then
|
[ -z "$TITLE" ] && TITLE="$(printf '%s' "$PATTERN" | tr '[:upper:]' '[:lower:]')"
|
||||||
printf 'No protocols matching "%s" found in %d NetConfig(s).\n' "$PATTERN" "${#NCONFIGS[@]}" >&2
|
|
||||||
exit 2
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
# Compose markdown
|
# Compose the document
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
{
|
{
|
||||||
printf '# %s — Cloverleaf System Knowledge Entry\n\n' "$TITLE"
|
printf '# %s\n\n' "$TITLE"
|
||||||
printf '_Auto-generated by Larry-Anywhere v3 nc-document.sh on %s. Auto-derived facts are below; context fields are for humans to fill or refine._\n\n' "$(date -Iseconds 2>/dev/null || date)"
|
if [ -n "$PATTERN" ]; then
|
||||||
|
printf '_Cloverleaf interface documentation for the `%s` system — one section per matching delivery thread. Auto-generated by Larry-Anywhere nc-document.sh (deterministic, API-free) on %s._\n\n' \
|
||||||
|
"$PATTERN" "$(date -Iseconds 2>/dev/null || date)"
|
||||||
|
else
|
||||||
|
printf '_Cloverleaf interface documentation for `%s`. Auto-generated by Larry-Anywhere nc-document.sh (deterministic, API-free) on %s._\n\n' \
|
||||||
|
"$THREAD_ARG" "$(date -Iseconds 2>/dev/null || date)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Context block (human fill-ins kept from prior versions)
|
||||||
printf '## Context\n\n'
|
printf '## Context\n\n'
|
||||||
printf -- '- **Vendor POC:** %s\n' "${POC_VENDOR:-_(unfilled — add vendor contact name + email/phone)_}"
|
printf -- '- **Vendor POC:** %s\n' "${POC_VENDOR:-_(unfilled)_}"
|
||||||
printf -- '- **Internal Owner:** %s\n' "${POC_INTERNAL:-_(unfilled — add the internal owner / engineer)_}"
|
printf -- '- **Internal Owner:** %s\n' "${POC_INTERNAL:-_(unfilled)_}"
|
||||||
printf -- '- **Status:** %s\n' "${STATUS:-_(unfilled — production / test / decommissioning / on hold)_}"
|
printf -- '- **Status:** %s\n' "${STATUS:-_(unfilled — production / test / decommissioning)_}"
|
||||||
printf -- '- **Escalation:** %s\n' "${ESCALATION:-_(unfilled — on-call path, ticket queue, etc.)_}"
|
printf -- '- **Escalation:** %s\n' "${ESCALATION:-_(unfilled)_}"
|
||||||
printf '\n### Open items\n'
|
printf '\n'
|
||||||
if [ -n "$OPEN_ITEMS" ]; then
|
if [ -n "$OPEN_ITEMS" ]; then printf '### Open items\n%s\n\n' "$OPEN_ITEMS"; fi
|
||||||
printf '%s\n\n' "$OPEN_ITEMS"
|
if [ -n "$NOTES" ]; then printf '### Notes\n%s\n\n' "$NOTES"; fi
|
||||||
else
|
|
||||||
printf '_(unfilled — add open items / known issues / pending work)_\n\n'
|
|
||||||
fi
|
|
||||||
printf '### Notes\n'
|
|
||||||
if [ -n "$NOTES" ]; then
|
|
||||||
printf '%s\n\n' "$NOTES"
|
|
||||||
else
|
|
||||||
printf '_(unfilled — add any free-form context)_\n\n'
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ─── Threads inventory ───
|
# one section per delivery thread
|
||||||
# v0.7.5: tr -cd '0-9' instead of tr -d ' ' — Cygwin wc.exe CR-taint would
|
for line in "${TARGETS[@]}"; do
|
||||||
# otherwise crash `printf '%d'` with "invalid number".
|
|
||||||
printf '## Threads (%d matched in %d site(s))\n\n' "${#MATCHES[@]}" "$(printf '%s\n' "${MATCHES[@]}" | awk -F'|' '{print $1}' | sort -u | wc -l | tr -cd '0-9')"
|
|
||||||
printf '| Site | Thread | Process | Direction | Port | Host | Type |\n'
|
|
||||||
printf '|---|---|---|---|---|---|---|\n'
|
|
||||||
for line in "${MATCHES[@]}"; do
|
|
||||||
IFS='|' read -r site nc prot <<< "$line"
|
IFS='|' read -r site nc prot <<< "$line"
|
||||||
pname=$("$NCP" protocol-field "$nc" "$prot" PROCESSNAME 2>/dev/null | head -1)
|
document_thread "$prot" "$site" "$nc"
|
||||||
obib=$("$NCP" protocol-field "$nc" "$prot" OBWORKASIB 2>/dev/null | head -1)
|
|
||||||
outonly=$("$NCP" protocol-field "$nc" "$prot" OUTBOUNDONLY 2>/dev/null | head -1)
|
|
||||||
ptype=$("$NCP" protocol-nested "$nc" "$prot" PROTOCOL.TYPE 2>/dev/null | head -1)
|
|
||||||
phost=$("$NCP" protocol-nested "$nc" "$prot" PROTOCOL.HOST 2>/dev/null | head -1)
|
|
||||||
pport=$("$NCP" protocol-nested "$nc" "$prot" PROTOCOL.PORT 2>/dev/null | head -1)
|
|
||||||
isserver=$("$NCP" protocol-nested "$nc" "$prot" PROTOCOL.ISSERVER 2>/dev/null | head -1)
|
|
||||||
|
|
||||||
direction="?"
|
|
||||||
[ "$isserver" = "1" ] && direction="inbound (TCP listener)"
|
|
||||||
[ "$obib" = "1" ] && [ "$direction" = "?" ] && direction="inbound (ICL/file)"
|
|
||||||
[ "$outonly" = "1" ] && [ "$direction" = "?" ] && direction="outbound"
|
|
||||||
|
|
||||||
phost_clean=$(printf '%s' "$phost" | sed 's/^{}$//')
|
|
||||||
pport_clean=$(printf '%s' "$pport" | sed 's/^{}$//')
|
|
||||||
|
|
||||||
printf '| `%s` | `%s` | `%s` | %s | %s | %s | %s |\n' \
|
|
||||||
"$site" "$prot" "${pname:-?}" "$direction" "${pport_clean:-—}" "${phost_clean:-—}" "${ptype:-?}"
|
|
||||||
done
|
|
||||||
printf '\n'
|
|
||||||
|
|
||||||
# ─── Per-thread detail ───
|
|
||||||
for line in "${MATCHES[@]}"; do
|
|
||||||
IFS='|' read -r site nc prot <<< "$line"
|
|
||||||
printf '## `%s` (site: `%s`)\n\n' "$prot" "$site"
|
|
||||||
|
|
||||||
printf '### Sources (what feeds this thread)\n\n'
|
|
||||||
sources=$("$NCP" sources "$nc" "$prot" 2>/dev/null)
|
|
||||||
if [ -n "$sources" ]; then
|
|
||||||
printf '%s\n' "$sources" | awk '{print "- `" $0 "`"}'
|
|
||||||
else
|
|
||||||
printf '_(none found in `%s`; may be fed via TCP from outside, or from another site via ICL)_\n' "$site"
|
|
||||||
fi
|
|
||||||
printf '\n'
|
|
||||||
|
|
||||||
printf '### Destinations (where this thread routes to)\n\n'
|
|
||||||
dests=$("$NCP" destinations "$nc" "$prot" 2>/dev/null)
|
|
||||||
if [ -n "$dests" ]; then
|
|
||||||
printf '%s\n' "$dests" | awk '{print "- `" $0 "`"}'
|
|
||||||
else
|
|
||||||
printf '_(no DEST entries in DATAXLATE block)_\n'
|
|
||||||
fi
|
|
||||||
printf '\n'
|
|
||||||
|
|
||||||
printf '### Xlates referenced\n\n'
|
|
||||||
xlates=$("$NCP" xlate-refs "$nc" "$prot" 2>/dev/null)
|
|
||||||
if [ -n "$xlates" ]; then
|
|
||||||
printf '%s\n' "$xlates" | awk -v site="$site" '{print "- `" site "/Xlate/" $0 "`"}'
|
|
||||||
else
|
|
||||||
printf '_(no xlates — pass-through or raw routing only)_\n'
|
|
||||||
fi
|
|
||||||
printf '\n'
|
|
||||||
|
|
||||||
printf '### TCL procs referenced\n\n'
|
|
||||||
tcls=$("$NCP" tclproc-refs "$nc" "$prot" 2>/dev/null)
|
|
||||||
if [ -n "$tcls" ]; then
|
|
||||||
printf '%s\n' "$tcls" | awk -v site="$site" '{print "- `" site "/tclprocs/" $0 ".tcl`"}'
|
|
||||||
else
|
|
||||||
printf '_(no TCL procs referenced)_\n'
|
|
||||||
fi
|
|
||||||
printf '\n'
|
|
||||||
done
|
done
|
||||||
|
|
||||||
# ─── Sources outside the matched set (the "fed by" landscape) ───
|
# Appendix — raw proc source (plainly labelled; NO "summarize" marker)
|
||||||
printf '## Adjacent threads (the network this subsystem talks to)\n\n'
|
if [ "$WANT_APPENDIX" = "1" ] && [ "${#APPENDIX_LIST[@]}" -gt 0 ]; then
|
||||||
printf '_All threads that **either feed** matched threads **or are fed by** matched threads. These are the immediate operational neighbors._\n\n'
|
printf '## Referenced proc source\n\n'
|
||||||
printf '| Site | Thread | Relationship to matched set |\n'
|
printf '_Raw TCL of every UPOC proc referenced above (the deterministic UPOC bits in each Description are extracted from these — included verbatim for audit)._\n\n'
|
||||||
printf '|---|---|---|\n'
|
for rec in "${APPENDIX_LIST[@]}"; do
|
||||||
declare -A SEEN
|
IFS='|' read -r asite aproc apath <<< "$rec"
|
||||||
for line in "${MATCHES[@]}"; do
|
printf '### `%s` (site `%s`)\n\n' "$aproc" "$asite"
|
||||||
IFS='|' read -r site nc prot <<< "$line"
|
if [ -n "$apath" ] && [ -f "$apath" ]; then
|
||||||
while IFS= read -r src; do
|
printf '_Source: `%s`_\n\n' "$apath"
|
||||||
key="$site|$src"
|
printf '```tcl\n'
|
||||||
[ -n "${SEEN[$key]:-}" ] && continue
|
cat "$apath"
|
||||||
SEEN[$key]=1
|
printf '\n```\n\n'
|
||||||
printf '| `%s` | `%s` | feeds `%s` |\n' "$site" "$src" "$prot"
|
else
|
||||||
done < <("$NCP" sources "$nc" "$prot" 2>/dev/null)
|
printf '_(proc `%s.tcl` not found under any site tclprocs/)_\n\n' "$aproc"
|
||||||
while IFS= read -r dst; do
|
fi
|
||||||
key="$site|$dst"
|
|
||||||
[ -n "${SEEN[$key]:-}" ] && continue
|
|
||||||
SEEN[$key]=1
|
|
||||||
printf '| `%s` | `%s` | receives from `%s` |\n' "$site" "$dst" "$prot"
|
|
||||||
done < <("$NCP" destinations "$nc" "$prot" 2>/dev/null)
|
|
||||||
done
|
done
|
||||||
printf '\n'
|
fi
|
||||||
|
|
||||||
# ─── Footer ───
|
|
||||||
printf '---\n\n'
|
printf '---\n\n'
|
||||||
printf '_Generated: %s · NetConfigs scanned: %d · Pattern: `%s`_\n' \
|
printf '_Generated: %s · sites scanned: %d · %s_\n' \
|
||||||
"$(date -Iseconds 2>/dev/null || date)" "${#NCONFIGS[@]}" "$PATTERN"
|
"$(date -Iseconds 2>/dev/null || date)" "${#SITE_NCS[@]}" \
|
||||||
|
"$( [ -n "$PATTERN" ] && printf 'pattern: `%s`' "$PATTERN" || printf 'thread: `%s`' "$THREAD_ARG" )"
|
||||||
} | out_target
|
} | out_target
|
||||||
|
|
||||||
[ -n "$OUT" ] && printf 'nc-document: wrote %s (%d matched threads across %d site(s))\n' \
|
if [ -n "$OUT" ]; then
|
||||||
"$OUT" "${#MATCHES[@]}" "$(printf '%s\n' "${MATCHES[@]}" | awk -F'|' '{print $1}' | sort -u | wc -l | tr -cd '0-9')" >&2
|
printf 'nc-document: wrote %s (%d delivery section(s))\n' "$OUT" "${#TARGETS[@]}" >&2
|
||||||
|
fi
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user