v0.8.24: doc tool plain-text output for OneNote (no markdown)

Default render is now plain text (no #/**/pipe-tables/fences) so it pastes
cleanly into OneNote. Tabular sections default to label:value hop blocks;
--onenote-table emits tab-separated rows for paste -> Insert>Table. Raw TCL
moved behind opt-in --raw-tcl (readable UPOC bits stay inline). Removed the
verbose "Filter / translation logic (surfaced deterministically...)" label.
Fixes a markdown leak in the proc-not-found fallback (Vera gate). Folds in
two prior deferred minors (dead counters; local _dest_hit/_d).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Bryan Johnson 2026-05-28 13:09:11 -07:00
parent f5f56439d0
commit 88fc104c54
5 changed files with 281 additions and 130 deletions

View File

@ -4,6 +4,48 @@ 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.24 — 2026-05-28
**★ PLAIN-TEXT output rewrite of `lib/nc-document.sh` (Bryan's priority — OneNote
readability).** Bryan shares the generated interface doc with a teammate in
OneNote, which does NOT render markdown — `#`, `**bold**`, `| pipe tables |`,
`---` rules and backticks all show as literal junk. The OUTPUT layer is rewritten
to emit clean PLAIN TEXT by default (parsing/extraction logic unchanged):
- **Default = plain text everywhere.** UPPERCASE headings underlined with a
dashes line; no `**bold**`, no backtick code spans, no `---` horizontal rules,
no `| pipe tables |`. Reads cleanly in any editor and pastes straight into
OneNote.
- **Tabular sections (Message Flow, Delivery breakdown) — two render modes.**
DEFAULT = indented `label:value` blocks (one block per hop/row; reads in any
font, zero setup). New `--onenote-table` flag renders those same sections as
TAB-separated rows (header row + one data row per record, real `\t` between
cells, NO leading/trailing pipes) — paste into OneNote then Insert > Table to
get real cells. Non-tabular sections (Title/Context/Description) stay plain
text in both modes.
- **Removed** the verbose `Filter / translation logic (surfaced deterministically
from the referenced UPOC procs):` preamble line; the surfaced UPOC bits now sit
under a simple `FILTER / TRANSLATION LOGIC` plain heading, still inline in each
interface description (always on).
- **Raw TCL is now OPT-IN behind `--raw-tcl`** (off by default). The readable,
deterministically-extracted UPOC bits stay inline in the per-interface
description regardless; only the verbatim raw-proc appendix is gated. The
appendix is rendered as a plain indented source dump (no ```` ```tcl ```` fences).
- New flags `--onenote-table` and `--raw-tcl` documented in the lib `--help` and
in the `nc_document` tool schema/wrapper in `larry.sh` (`onenote_table`,
`raw_tcl` booleans). The `--help` `sed -n` range was re-pinned to the END of the
comment header (`2,92p`) so it does not leak live code.
**Folded in two Vera-deferred v0.8.22 minors** (`lib/nc-document.sh`):
- Removed dead/unused `sup`/`fan` counters in `_xlate_filter_block`.
- Added `local` to the previously un-`local`'d `_dest_hit`/`_d` loop variables in
`document_thread`.
Verified against the real integrator (`HCIROOT=/tmp/clvf_realtest/integrator`,
`--name codametrix`): default output is plain text with `label:value` hop blocks
and no markdown chars; `--onenote-table` produces tab-separated rows; the removed
preamble line is gone; raw TCL only appears with `--raw-tcl`.
## v0.8.23 — 2026-05-28 ## v0.8.23 — 2026-05-28
**★ REGRESSION CHAIN-WALK route-test capture (Bryan's priority).** New **★ REGRESSION CHAIN-WALK route-test capture (Bryan's priority).** New

View File

@ -23,16 +23,16 @@
# scripts/make-manifest.sh and bump VERSION. # scripts/make-manifest.sh and bump VERSION.
# Top-level scripts # Top-level scripts
larry.sh 57a96b78c7ab319537b2203a7363454ebcd482ed048d63dc05668fad3c292ee7 larry.sh b3af140dcb8518ea98327b33177bcef8973ca223ad1f46f90ad6dba0e9f7ded7
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 fa36e23a39eacbd0d7ecedd3b42131902f816ee7e98241dfc6e28c6e4ba80423 install-larry.sh fa36e23a39eacbd0d7ecedd3b42131902f816ee7e98241dfc6e28c6e4ba80423
# Metadata # Metadata
VERSION 189191ed6bf46fb5d0b7f887c60f28c097b6b4df83273227744f0510b00d89db VERSION 05d77ce62e5abe7212c0fa5ca747f16348d012c39016080e99c10f8f7f5e20bf
MANUAL.md c64bd0251a51ad150508b4e1185355bc4826a64071d4de339f92ed550dbfacde MANUAL.md c64bd0251a51ad150508b4e1185355bc4826a64071d4de339f92ed550dbfacde
CHANGELOG.md 646791fb1a6f99c326869c57e17cb1662827afa566e68d91fe580ed68d0f02df CHANGELOG.md 5432e98a75500cf4cb7d1fa171124d64e693865215d4612f5071294a4f871f6a
# Agent personas (system-prompt overlays) # Agent personas (system-prompt overlays)
agents/larry.md 0a1ef737e7fc133ab35be09f79c3a4df33de814e0404b69b950932d0c8a01be1 agents/larry.md 0a1ef737e7fc133ab35be09f79c3a4df33de814e0404b69b950932d0c8a01be1
@ -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 e0b5c5b0a778abff2f09377cd1692ba445140e7da84aa8a96a002081f31b870c lib/nc-document.sh 56808d5ba86ca6b355f405bf33db7de62e8894a5582987e9de1eeee77a8ab3a8
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

View File

@ -1 +1 @@
0.8.23 0.8.24

View File

@ -78,7 +78,7 @@ set -o pipefail
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
# Config # Config
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
LARRY_VERSION="0.8.23" LARRY_VERSION="0.8.24"
LARRY_HOME="${LARRY_HOME:-$HOME/.larry}" LARRY_HOME="${LARRY_HOME:-$HOME/.larry}"
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
@ -4089,6 +4089,7 @@ tool_nc_document() {
# section per matching delivery thread across sites. Exactly one of thread/name. # 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 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:-}" local title="${6:-}" status="${7:-}" poc_internal="${8:-}" poc_vendor="${9:-}" escalation="${10:-}" open_items="${11:-}" notes="${12:-}"
local onenote_table="${13:-0}" raw_tcl="${14:-0}"
_lib_err_if_missing || return _lib_err_if_missing || return
[ -n "$thread" ] || [ -n "$name" ] \ [ -n "$thread" ] || [ -n "$name" ] \
|| { echo "ERROR: nc_document needs either thread (single interface) or name (system pattern)"; return 1; } || { echo "ERROR: nc_document needs either thread (single interface) or name (system pattern)"; return 1; }
@ -4108,6 +4109,8 @@ tool_nc_document() {
[ -n "$escalation" ] && args+=(--escalation "$escalation") [ -n "$escalation" ] && args+=(--escalation "$escalation")
[ -n "$open_items" ] && args+=(--open-items "$open_items") [ -n "$open_items" ] && args+=(--open-items "$open_items")
[ -n "$notes" ] && args+=(--notes "$notes") [ -n "$notes" ] && args+=(--notes "$notes")
[ "$onenote_table" = "1" ] && args+=(--onenote-table)
[ "$raw_tcl" = "1" ] && args+=(--raw-tcl)
"$LARRY_LIB_DIR/nc-document.sh" "${args[@]}" 2>&1 "$LARRY_LIB_DIR/nc-document.sh" "${args[@]}" 2>&1
} }
@ -4182,7 +4185,9 @@ execute_tool() {
"$(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 // ""')" \
"$(J '.notes // ""')" ;; "$(J '.notes // ""')" \
"$(J '.onenote_table // 0' | sed "s/false/0/;s/true/1/")" \
"$(J '.raw_tcl // 0' | sed "s/false/0/;s/true/1/")" ;;
nc_find) tool_nc_find "$(J '.mode')" "$(J '.query')" "$(J '.format // "table"')" "$(J '.hciroot // ""')" ;; nc_find) tool_nc_find "$(J '.mode')" "$(J '.query')" "$(J '.format // "table"')" "$(J '.hciroot // ""')" ;;
nc_insert_protocol) tool_nc_insert_protocol "$(J '.netconfig')" "$(J '.block')" "$(J '.mode // "end"')" "$(J '.anchor // ""')" ;; nc_insert_protocol) tool_nc_insert_protocol "$(J '.netconfig')" "$(J '.block')" "$(J '.mode // "end"')" "$(J '.anchor // ""')" ;;
nc_add_route) tool_nc_add_route "$(J '.netconfig')" "$(J '.protocol_name')" "$(J '.route')" ;; nc_add_route) tool_nc_add_route "$(J '.netconfig')" "$(J '.protocol_name')" "$(J '.route')" ;;
@ -4237,7 +4242,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":"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_document","description":"Document a Cloverleaf INTERFACE end-to-end as a PLAIN-TEXT knowledge entry in Bryan's confirmed Legacy 'ADT Messages' template. OUTPUT IS PLAIN TEXT BY DEFAULT (no markdown) so it pastes cleanly into OneNote, which does NOT render markdown: UPPERCASE headings underlined with dashes, no bold, no backticks, no pipe tables, no '---' rules. Structure: Title; Description prose; Message Flow (one hop per record: 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). The two tabular sections (Message Flow, Delivery breakdown) render as INDENTED label:value blocks by default (read in any font, zero setup); set onenote_table=true to render them instead as TAB-separated rows (header + one row per record, no leading/trailing pipes) for paste-into-OneNote → Insert > Table. 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, INLINE in 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). Those inline bits are ALWAYS ON. The raw proc TCL appendix is OPT-IN via raw_tcl=true (off by default). ★ 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 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>.txt (plain text)."},"hciroot":{"type":"string","description":"Override $HCIROOT for the NetConfig scan."},"title":{"type":"string","description":"Doc title. Default derived from thread/name."},"onenote_table":{"type":"boolean","description":"Render the tabular sections (Message Flow, Delivery breakdown) as TAB-separated rows (header + one data row per record, real tabs, no leading/trailing pipes) for paste-into-OneNote → Insert > Table. Default false = indented label:value blocks. Non-tabular sections stay plain text either way."},"raw_tcl":{"type":"boolean","description":"Also emit the raw proc-source appendix (verbatim TCL of every referenced UPOC proc). Default false — the readable extracted UPOC bits stay inline in each description regardless; only the verbatim appendix is gated behind this."},"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"]}},

View File

@ -1,7 +1,14 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# nc-document.sh — document a Cloverleaf INTERFACE end-to-end as a native markdown # nc-document.sh — document a Cloverleaf INTERFACE end-to-end as a PLAIN-TEXT
# knowledge entry in Bryan's confirmed Legacy "ADT Messages" template. # knowledge entry in Bryan's confirmed Legacy "ADT Messages" template.
# #
# OUTPUT IS PLAIN TEXT BY DEFAULT (no markdown). Bryan shares the generated doc
# in OneNote, which does NOT render markdown — `#`, `**bold**`, `| pipe tables |`,
# `---` rules and backticks all show up as literal junk. So the default render is
# clean plain text: UPPERCASE headings (underlined with a dashes line), no bold,
# no backticks, no pipe tables, no `---` rules. It reads cleanly in any editor and
# pastes straight into OneNote.
#
# Two modes: # Two modes:
# SINGLE THREAD nc-document.sh <thread> [site] (e.g. ADTto_CodaMetrix ancout) # SINGLE THREAD nc-document.sh <thread> [site] (e.g. ADTto_CodaMetrix ancout)
# nc-document.sh <site>/<thread> (v1 node form) # nc-document.sh <site>/<thread> (v1 node form)
@ -10,9 +17,10 @@
# #
# Everything emitted by THIS tool is DETERMINISTIC, PURE BASH+AWK, and API-FREE. # 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 # 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 # LLM and never reaches the network. The deterministic UPOC bits (always inline in
# appendix ARE the deliverable; when larry runs WITH the API the model transparently # each interface's description) ARE the deliverable; the raw proc TCL appendix is
# polishes those surfaced bits into smoother prose in the Description — that is # OPT-IN via --raw-tcl. When larry runs WITH the API the model transparently
# polishes the surfaced bits into smoother prose in the Description — that is
# normal agent behavior, NOT a mechanism in this script. # normal agent behavior, NOT a mechanism in this script.
# #
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
@ -20,14 +28,17 @@
# - Title = the interface / message type. # - Title = the interface / message type.
# - Description = prose: what the messages are, what the filters key on, where # - Description = prose: what the messages are, what the filters key on, where
# translation happens, how it's fed — seeded from the surfaced # translation happens, how it's fed — seeded from the surfaced
# UPOC bits. # UPOC bits, which stay INLINE in the description by default.
# - Message Flow = a table (Platform | Action | Description | From | To), one row # - Message Flow = one block per hop (Epic feed → Cloverleaf routing → Final
# per hop: Epic feed → Cloverleaf cross-site routing → Final # Delivery), built from nc-paths.sh (the route-chain enumerator).
# Delivery. Built from nc-paths.sh (the route-chain enumerator). # DEFAULT render = a label:value block per hop (reads in ANY
# font, zero setup). With --onenote-table this becomes TAB-
# separated rows (header + one row per hop) you paste into
# OneNote and turn into a real table via Insert > Table.
# - Per-delivery breakdown: # - Per-delivery breakdown:
# inbound PROTOCOL TYPE/HOST/PORT/ISSERVER + inbound TRXID/TPS proc, # inbound PROTOCOL TYPE/HOST/PORT/ISSERVER + inbound TRXID/TPS proc,
# the route's TRXID filter + TYPE + PREPROCS/POSTPROCS + XLATE, # the route's TRXID filter + TYPE + PREPROCS/POSTPROCS + XLATE,
# destination host:port / process. # destination host:port / process. Same DEFAULT/--onenote-table behavior.
# - ★ DETERMINISTIC UPOC-BITS — for each referenced proc, locate its .tcl under # - ★ DETERMINISTIC UPOC-BITS — for each referenced proc, locate its .tcl under
# $HCIROOT/<site>/tclprocs/ and extract (no API): # $HCIROOT/<site>/tclprocs/ and extract (no API):
# 1. comments (header + inline `#` lines — the author's own filter notes) # 1. comments (header + inline `#` lines — the author's own filter notes)
@ -35,9 +46,8 @@
# 3. conditions + literal values (matched event-code lists A01/A02/…, etc.) # 3. conditions + literal values (matched event-code lists A01/A02/…, etc.)
# 4. table lookups (.tbl / table names, e.g. PeriCalm_Loc) # 4. table lookups (.tbl / table names, e.g. PeriCalm_Loc)
# 5. disposition (CONTINUE / KILL / return — pass vs kill) # 5. disposition (CONTINUE / KILL / return — pass vs kill)
# Rendered compactly into the Description. # Rendered compactly INLINE in the Description (always on).
# - Raw proc TCL in a plain appendix (`## Referenced proc source`). NO "summarize # - Raw proc TCL appendix — OPT-IN behind --raw-tcl (off by default).
# by hand / on an API box" marker — the extracted bits are the content.
# #
# Usage: # Usage:
# nc-document.sh <thread> [site] [options] # nc-document.sh <thread> [site] [options]
@ -49,15 +59,26 @@
# --thread NAME single-thread mode (alternative to the positional form) # --thread NAME single-thread mode (alternative to the positional form)
# --site NAME home site of the thread (disambiguates a multi-site name) # --site NAME home site of the thread (disambiguates a multi-site name)
# --hciroot DIR defaults to $HCIROOT # --hciroot DIR defaults to $HCIROOT
# --out PATH output markdown path (default: stdout) # --out PATH output text path (default: stdout)
# --title TITLE doc title (default: derived from thread/name) # --title TITLE doc title (default: derived from thread/name)
# --onenote-table render the tabular sections (Message Flow, Delivery
# breakdown) as TAB-separated rows (header row + one data
# row per record, real \t between cells, NO leading/trailing
# pipes) for paste-into-OneNote → Insert > Table. The
# DEFAULT (without this flag) renders them as indented
# label:value blocks that read in any font. Non-tabular
# sections (Title/Context/Description) stay plain text in
# both modes.
# --raw-tcl also emit the raw proc-source appendix (verbatim TCL of
# every referenced UPOC proc). OFF by default — the readable
# extracted UPOC bits stay inline in each description; only
# the verbatim appendix is gated behind this flag.
# --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 # --open-items TXT Open items text
# --notes TXT freeform additional notes # --notes TXT freeform additional notes
# --no-appendix omit the raw proc-source appendix
# --inbound-systems P path to the curated inbound-systems lookup TSV (default: # --inbound-systems P path to the curated inbound-systems lookup TSV (default:
# $LARRY_HOME/inbound-systems.tsv, then the shipped seed). # $LARRY_HOME/inbound-systems.tsv, then the shipped seed).
# Maps a feed thread name / port:<n> to the external sender # Maps a feed thread name / port:<n> to the external sender
@ -139,7 +160,12 @@ STATUS=""
ESCALATION="" ESCALATION=""
OPEN_ITEMS="" OPEN_ITEMS=""
NOTES="" NOTES=""
WANT_APPENDIX=1 # Raw TCL appendix is OPT-IN (--raw-tcl). The readable extracted UPOC bits stay
# inline in each description regardless; only the verbatim appendix is gated.
WANT_APPENDIX=0
# Tabular sections render as label:value blocks by default; --onenote-table emits
# TAB-separated rows instead (header + one row per record) for OneNote paste.
ONENOTE_TABLE=0
STRICT_DELIVERY=0 STRICT_DELIVERY=0
INBOUND_SYSTEMS_FILE="${INBOUND_SYSTEMS_FILE:-}" INBOUND_SYSTEMS_FILE="${INBOUND_SYSTEMS_FILE:-}"
POSITIONAL=() POSITIONAL=()
@ -152,16 +178,18 @@ while [ $# -gt 0 ]; do
--hciroot) shift; HCIROOT_OVERRIDE="${1:-}" ;; --hciroot) shift; HCIROOT_OVERRIDE="${1:-}" ;;
--out) shift; OUT="${1:-}" ;; --out) shift; OUT="${1:-}" ;;
--title) shift; TITLE="${1:-}" ;; --title) shift; TITLE="${1:-}" ;;
--onenote-table) ONENOTE_TABLE=1 ;;
--raw-tcl) WANT_APPENDIX=1 ;;
--poc-vendor) shift; POC_VENDOR="${1:-}" ;; --poc-vendor) shift; POC_VENDOR="${1:-}" ;;
--poc-internal) shift; POC_INTERNAL="${1:-}" ;; --poc-internal) shift; POC_INTERNAL="${1:-}" ;;
--status) shift; STATUS="${1:-}" ;; --status) shift; STATUS="${1:-}" ;;
--escalation) shift; ESCALATION="${1:-}" ;; --escalation) shift; ESCALATION="${1:-}" ;;
--open-items) shift; OPEN_ITEMS="${1:-}" ;; --open-items) shift; OPEN_ITEMS="${1:-}" ;;
--notes) shift; NOTES="${1:-}" ;; --notes) shift; NOTES="${1:-}" ;;
--no-appendix) WANT_APPENDIX=0 ;; --no-appendix) WANT_APPENDIX=0 ;; # back-compat no-op (appendix is now off by default)
--strict-delivery) STRICT_DELIVERY=1 ;; --strict-delivery) STRICT_DELIVERY=1 ;;
--inbound-systems) shift; INBOUND_SYSTEMS_FILE="${1:-}" ;; --inbound-systems) shift; INBOUND_SYSTEMS_FILE="${1:-}" ;;
-h|--help) sed -n '2,71p' "$NC_SELF" | sed 's/^# \{0,1\}//'; exit 0 ;; -h|--help) sed -n '2,92p' "$NC_SELF" | sed 's/^# \{0,1\}//'; exit 0 ;;
--*) die "unknown flag: $1" ;; --*) die "unknown flag: $1" ;;
*) POSITIONAL+=("$1") ;; *) POSITIONAL+=("$1") ;;
esac esac
@ -230,6 +258,36 @@ out_target() {
fi fi
} }
# ─────────────────────────────────────────────────────────────────────────────
# PLAIN-TEXT RENDER HELPERS (v0.8.24). The whole doc is plain text so it pastes
# cleanly into OneNote (which does NOT render markdown). No `#`/`##`, no
# `**bold**`, no backticks, no `---` rules, no `| pipe tables |`.
# ─────────────────────────────────────────────────────────────────────────────
TAB=$(printf '\t')
# A top-level heading: UPPERCASE text underlined with a full-width dashes line.
_h1() { # text
local t; t=$(printf '%s' "$1" | tr '[:lower:]' '[:upper:]')
printf '%s\n' "$t"
printf '%s\n\n' "$(printf '%*s' "${#t}" '' | tr ' ' '=')"
}
# A section heading: UPPERCASE text underlined with dashes.
_h2() { # text
local t; t=$(printf '%s' "$1" | tr '[:lower:]' '[:upper:]')
printf '%s\n' "$t"
printf '%s\n\n' "$(printf '%*s' "${#t}" '' | tr ' ' '-')"
}
# A sub-heading: UPPERCASE text on its own line, no underline (keeps it light).
_h3() { # text
printf '%s\n\n' "$(printf '%s' "$1" | tr '[:lower:]' '[:upper:]')"
}
# A label:value line for a key/value bullet block. Pads the label to a common
# width so values line up in a monospaced view (and still reads fine elsewhere).
# _kv <label> <value>
_kv() { # label value
printf ' %-14s %s\n' "$1:" "$2"
}
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
# strip a leading "{" / trailing "}" / empty-brace marker from a scalar value # strip a leading "{" / trailing "}" / empty-brace marker from a scalar value
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
@ -496,7 +554,7 @@ _upoc_oneline() {
| grep -ivE '^(name|author|date|args|returns|upoc type|revision|notes)\b' \ | grep -ivE '^(name|author|date|args|returns|upoc type|revision|notes)\b' \
| head -3 \ | head -3 \
| awk 'NR==1{printf "%s",$0;next}{printf " · %s",$0}END{print ""}') | awk 'NR==1{printf "%s",$0;next}{printf " · %s",$0}END{print ""}')
local out="UPOC \`$proc\`" local out="UPOC $proc"
[ -n "$comments" ] && out="$out$comments" [ -n "$comments" ] && out="$out$comments"
[ -n "$fields" ] && out="$out · fields: $fields" [ -n "$fields" ] && out="$out · fields: $fields"
[ -n "$matches" ] && out="$out · matches: $matches" [ -n "$matches" ] && out="$out · matches: $matches"
@ -590,42 +648,33 @@ _xlate_actions() { # xltfile
} }
# Render a human "Xlate filtering & fan-out" block for one xlate, from its action # Render a human "Xlate filtering & fan-out" block for one xlate, from its action
# records. Emits markdown bullet lines on stdout (empty output if none). # records. Emits PLAIN-TEXT lines on stdout (empty output if none).
# _xlate_filter_block <xlatename> <xltfile> # _xlate_filter_block <xlatename> <xltfile>
_xlate_filter_block() { _xlate_filter_block() {
local xl="$1" f="$2" recs local xl="$1" f="$2" recs act br cond
[ -n "$f" ] || return 0 [ -n "$f" ] || return 0
[ -f "$f" ] || return 0 [ -f "$f" ] || return 0
recs=$(_xlate_actions "$f") recs=$(_xlate_actions "$f")
[ -n "$recs" ] || return 0 [ -n "$recs" ] || return 0
local sup=0 fan=0 line act br cond # header line (plain text — no markdown emphasis)
while IFS=$'\t' read -r act br cond; do printf 'Xlate %s changes the message count:\n' "$xl"
[ -z "$act" ] && continue
case "$act" in
SUPPRESS) sup=$((sup+1)) ;;
SEND|CONTINUE) fan=$((fan+1)) ;;
esac
done <<< "$recs"
# header line
printf '_Xlate `%s` changes the message count:_\n\n' "$xl"
while IFS=$'\t' read -r act br cond; do while IFS=$'\t' read -r act br cond; do
[ -z "$act" ] && continue [ -z "$act" ] && continue
local when="" local when=""
case "$br" in case "$br" in
when) [ -n "$cond" ] && when=" when \`$cond\`" ;; when) [ -n "$cond" ] && when=" when $cond" ;;
when-not) [ -n "$cond" ] && when=" when NOT \`$cond\`" ;; when-not) [ -n "$cond" ] && when=" when NOT $cond" ;;
*) when=" unconditionally" ;; *) when=" unconditionally" ;;
esac esac
case "$act" in case "$act" in
SUPPRESS) SUPPRESS)
printf -- '- **FILTERING — message SUPPRESSED (dropped)**%s.\n' "$when" ;; printf -- ' - FILTERING — message SUPPRESSED (dropped)%s.\n' "$when" ;;
SEND) SEND)
printf -- '- **FAN-OUT — message CLONED / multiplied here** (`OP SEND` emits an extra output copy mid-translation)%s.\n' "$when" ;; printf -- ' - FAN-OUT — message CLONED / multiplied here (OP SEND emits an extra output copy mid-translation)%s.\n' "$when" ;;
CONTINUE) CONTINUE)
printf -- '- **FAN-OUT — translation CONTINUES after a send** (`OP CONTINUE`, the companion that yields a second distinct message)%s.\n' "$when" ;; printf -- ' - FAN-OUT — translation CONTINUES after a send (OP CONTINUE, the companion that yields a second distinct message)%s.\n' "$when" ;;
esac esac
done <<< "$recs" done <<< "$recs"
printf '\n'
} }
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
@ -687,6 +736,7 @@ document_thread() {
# --- the specific route (TRXID/TYPE/XLATE/PRE/POST) that targets this outbound --- # --- 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 r_trxid="" r_type="" r_xlate="" r_pre="" r_post="" r_procs="" r_wild="" r_enabled=""
local US; US=$(printf '\037') local US; US=$(printf '\037')
local _dest_hit _d
if [ -n "$route_thr" ]; then if [ -n "$route_thr" ]; then
while IFS="$US" read -r dest trxid rtype xlate pre post procs wild enabled; do while IFS="$US" read -r dest trxid rtype xlate pre post procs wild enabled; do
# dest may be a single thread OR a space-joined multi-dest list { DEST {a b c} } # dest may be a single thread OR a space-joined multi-dest list { DEST {a b c} }
@ -740,7 +790,7 @@ document_thread() {
upoc_lines+=("$(_upoc_oneline "$proc" "$bf")") upoc_lines+=("$(_upoc_oneline "$proc" "$bf")")
rm -f "$bf" rm -f "$bf"
else else
upoc_lines+=("UPOC \`$proc\`_(proc .tcl not found under any site's tclprocs/)_") upoc_lines+=("UPOC $proc — (proc .tcl not found under any site's tclprocs/)")
fi fi
_register_appendix "$site" "$proc" _register_appendix "$site" "$proc"
done done
@ -762,71 +812,74 @@ document_thread() {
fi fi
# ─────────────────────────── render the section ─────────────────────────── # ─────────────────────────── render the section ───────────────────────────
printf '## %s\n\n' "$ob" # PLAIN TEXT throughout. The interface name is its own UPPERCASE underlined
# heading; subsections use light UPPERCASE sub-headings. No markdown anywhere.
_h2 "$ob"
# Description (prose seeded from deterministic facts; the model polishes this # Description (prose seeded from deterministic facts; the model polishes this
# into smoother prose when run WITH the API — no marker here). # into smoother prose when run WITH the API — no marker here). PLAIN TEXT: no
printf '### Description\n\n' # bold, no backticks.
_h3 "Description"
{ {
printf 'The **%s** interface delivers messages to `%s`' "$ob" "$ob" printf 'The %s interface delivers messages to %s' "$ob" "$ob"
[ -n "$dhost" ] && printf ' on **%s' "$dhost" [ -n "$dhost" ] && printf ' on %s' "$dhost"
[ -n "$dport" ] && printf ':%s' "$dport" [ -n "$dport" ] && printf ':%s' "$dport"
[ -n "$dhost" ] && printf '**' [ -n "$dproc" ] && printf ' (process %s)' "$dproc"
[ -n "$dproc" ] && printf ' (process `%s`)' "$dproc"
printf '.' printf '.'
if [ -n "$route_thr" ]; then if [ -n "$route_thr" ]; then
printf ' Routing and filtering happen on the inbound thread `%s`' "$route_thr" 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" [ -n "$in_proc" ] && printf ', whose inbound TRXID/TPS proc %s assigns the transaction id that the routes key on' "$in_proc"
printf '.' printf '.'
fi fi
if [ -n "$r_trxid" ]; then if [ -n "$r_trxid" ]; then
printf ' This delivery is selected by the TRXID filter `%s`' "$r_trxid" printf ' This delivery is selected by the TRXID filter %s' "$r_trxid"
[ "$r_wild" = "ON" ] && printf ' (wildcard match)' [ "$r_wild" = "ON" ] && printf ' (wildcard match)'
printf '.' printf '.'
fi fi
if [ -n "$r_xlate" ]; then if [ -n "$r_xlate" ]; then
printf ' Translation is done by the xlate `%s`' "$r_xlate" printf ' Translation is done by the xlate %s' "$r_xlate"
# ★ call out xlate-internal filtering / fan-out inline in the prose. # ★ call out xlate-internal filtering / fan-out inline in the prose.
if [ "${xlate_sup:-0}" -gt 0 ] && [ "${xlate_fan:-0}" -gt 0 ]; then if [ "${xlate_sup:-0}" -gt 0 ] && [ "${xlate_fan:-0}" -gt 0 ]; then
printf ', which both **suppresses (filters)** and **clones (fans out)** messages internally (see "Xlate filtering & fan-out" below)' printf ', which both suppresses (filters) and clones (fans out) messages internally (see "Xlate filtering & fan-out" below)'
elif [ "${xlate_sup:-0}" -gt 0 ]; then elif [ "${xlate_sup:-0}" -gt 0 ]; then
printf ', which **suppresses (drops/filters)** some messages internally (see "Xlate filtering & fan-out" below)' printf ', which suppresses (drops/filters) some messages internally (see "Xlate filtering & fan-out" below)'
elif [ "${xlate_fan:-0}" -gt 0 ]; then elif [ "${xlate_fan:-0}" -gt 0 ]; then
printf ', which **clones / fans out** messages internally (see "Xlate filtering & fan-out" below)' printf ', which clones / fans out messages internally (see "Xlate filtering & fan-out" below)'
fi fi
printf '.' printf '.'
elif [ "$r_type" = "raw" ]; then elif [ "$r_type" = "raw" ]; then
printf ' Messages are passed **raw** (no translation).' printf ' Messages are passed raw (no translation).'
fi fi
printf '\n\n' printf '\n\n'
} }
# Deterministically-surfaced UPOC bits stay INLINE in the description (always
# on). The verbose "Filter / translation logic ..." preamble line is removed;
# the bits sit under a simple plain sub-heading.
if [ "${#upoc_lines[@]}" -gt 0 ]; then if [ "${#upoc_lines[@]}" -gt 0 ]; then
printf 'Filter / translation logic (surfaced deterministically from the referenced UPOC procs):\n\n' _h3 "Filter / translation logic"
local l local l
for l in "${upoc_lines[@]}"; do printf -- '- %s\n' "$l"; done for l in "${upoc_lines[@]}"; do printf -- ' - %s\n' "$l"; done
printf '\n' printf '\n'
fi fi
# ★ Xlate filtering & fan-out subsection (only when the xlate actually changes # ★ Xlate filtering & fan-out subsection (only when the xlate actually changes
# the message count). Deterministic parse of the .xlt — NO API. # the message count). Deterministic parse of the .xlt — NO API.
if [ -n "$xlate_block" ]; then if [ -n "$xlate_block" ]; then
printf '#### Xlate filtering & fan-out\n\n' _h3 "Xlate filtering & fan-out"
printf '%s\n\n' "$xlate_block" printf '%s\n' "$xlate_block"
printf '\n'
fi fi
# Message Flow table. The middle "routing" row's wording adapts to whether the # ── Message Flow. The middle "routing" row's wording adapts to whether the
# chain actually crosses a site boundary (a `==>` hop): cross-site routing goes # chain actually crosses a site boundary (a `==>` hop): cross-site routing
# via a named destination block; an intra-site chain is a local DATAXLATE route. # goes via a named destination block; an intra-site chain is a local
# DATAXLATE route.
local is_cross=0 route_desc local is_cross=0 route_desc
case "$chain" in *' ==> '*) is_cross=1 ;; esac case "$chain" in *' ==> '*) is_cross=1 ;; esac
if [ "$is_cross" = "1" ]; then if [ "$is_cross" = "1" ]; then
route_desc="Cross-site route via destination block; inbound \`${route_thr:-?}\` keys TRXID and routes per delivery" route_desc="Cross-site route via destination block; inbound ${route_thr:-?} keys TRXID and routes per delivery"
else else
route_desc="Intra-site DATAXLATE route; inbound \`${route_thr:-?}\` keys TRXID and routes per delivery" route_desc="Intra-site DATAXLATE route; inbound ${route_thr:-?} keys TRXID and routes per delivery"
fi 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.
# "From" prefers the Bryan-curated inbound-systems label (deterministic, NOT # "From" prefers the Bryan-curated inbound-systems label (deterministic, NOT
# guessed); when no curated entry matches we fall back to the honest generic # guessed); when no curated entry matches we fall back to the honest generic
# "Epic (process <name>)" so the doc never fabricates a sender. # "Epic (process <name>)" so the doc never fabricates a sender.
@ -834,43 +887,87 @@ document_thread() {
if [ -n "$feed_label" ]; then if [ -n "$feed_label" ]; then
feed_from="$feed_label" feed_from="$feed_label"
else else
feed_from="$(printf 'Epic (process `%s`)' "${in_pname:-${dproc:-ADT}}")" feed_from="$(printf 'Epic (process %s)' "${in_pname:-${dproc:-ADT}}")"
fi fi
printf '| Epic | feed | Raw Epic feed entering the integrator | %s | `%s` |\n' \ local platform2 to3 final_desc
"$feed_from" "${feed_root:-}" platform2="Cloverleaf$( [ "$is_cross" = "1" ] && printf ' (cross-site)' || printf '' )"
# Row 2: Cloverleaf routing (the chain itself) to3="$ob$( [ -n "$dhost" ] && printf ' -> %s' "$dhost" )$( [ -n "$dport" ] && printf ':%s' "$dport" )"
printf '| Cloverleaf%s | message routing | %s | `%s` | `%s` |\n' \ final_desc="TRXID ${r_trxid:--} -> TYPE ${r_type:--}$( [ -n "$r_xlate" ] && printf ', xlate %s' "$r_xlate" )"
"$( [ "$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 _h3 "Message Flow"
printf '### Delivery breakdown — `%s`\n\n' "$ob" if [ "$ONENOTE_TABLE" = "1" ]; then
printf -- '- **Flow:** `%s`\n' "${chain:-}" # TAB-separated rows: header + one data row per hop. NO leading/trailing
printf -- '- **How received (inbound `%s`):** PROTOCOL TYPE `%s`' "${route_thr:-?}" "${in_type:-}" # pipes — paste into OneNote, select, Insert > Table to get real cells.
[ -n "$in_host" ] && printf ' · HOST `%s`' "$in_host" printf 'Platform%sAction%sDescription%sFrom%sTo\n' "$TAB" "$TAB" "$TAB" "$TAB"
[ -n "$in_port" ] && printf ' · PORT `%s`' "$in_port" printf 'Epic%sfeed%sRaw Epic feed entering the integrator%s%s%s%s\n' \
[ -n "$in_isserver" ] && printf ' · ISSERVER `%s`' "$in_isserver" "$TAB" "$TAB" "$TAB" "$feed_from" "$TAB" "${feed_root:--}"
[ -n "$in_iclport" ] && printf ' · ICLSERVERPORT `%s`' "$in_iclport" printf '%s%smessage routing%s%s%s%s%s%s\n' \
"$platform2" "$TAB" "$TAB" "$route_desc" "$TAB" "${chain:--}" "$TAB" "${route_thr:--}"
printf 'Final Delivery%soutbound to %s%s%s%s%s%s%s\n' \
"$TAB" "${dproc:-vendor}" "$TAB" "$final_desc" "$TAB" "${route_thr:--}" "$TAB" "$to3"
else
# DEFAULT: one indented label:value block per hop. Reads in ANY font, zero
# setup — Bryan's bulletproof default for sharing.
printf ' [1] Epic feed\n'
_kv "Platform" "Epic"
_kv "Action" "feed"
_kv "Description" "Raw Epic feed entering the integrator"
_kv "From" "$feed_from"
_kv "To" "${feed_root:--}"
printf '\n' printf '\n'
printf -- '- **Inbound TRXID/TPS proc:** `%s`\n' "${in_proc:-}" printf ' [2] %s routing\n' "$platform2"
printf -- '- **Route TRXID filter:** `%s`%s\n' "${r_trxid:-}" "$( [ "$r_wild" = "ON" ] && printf ' (WILDCARD ON)' )" _kv "Platform" "$platform2"
printf -- '- **Route TYPE:** `%s`\n' "${r_type:-}" _kv "Action" "message routing"
printf -- '- **UPOC PREPROCS:** `%s`\n' "${r_pre:-}" _kv "Description" "$route_desc"
printf -- '- **UPOC POSTPROCS:** `%s`\n' "${r_post:-}" _kv "From" "${chain:--}"
printf -- '- **XLATE:** `%s`%s\n' "${r_xlate:-}" \ _kv "To" "${route_thr:--}"
"$( if [ "${xlate_sup:-0}" -gt 0 ] || [ "${xlate_fan:-0}" -gt 0 ]; then printf '\n'
printf ' — internal: %d suppress (filter), %d send/continue (fan-out)' "${xlate_sup:-0}" "${xlate_fan:-0}" printf ' [3] Final delivery\n'
fi )" _kv "Platform" "Final Delivery"
printf -- '- **Destination:** `%s`%s%s · process `%s` · TYPE `%s`\n' \ _kv "Action" "outbound to ${dproc:-vendor}"
"${dhost:-}" "$( [ -n "$dport" ] && printf ':%s' "$dport" )" "" "${dproc:-}" "${dtype:-}" _kv "Description" "$final_desc"
_kv "From" "${route_thr:--}"
_kv "To" "$to3"
printf '\n'
fi
# ── Per-delivery breakdown (tabular). DEFAULT = label:value block;
# --onenote-table = TAB-separated field/value rows.
local in_recv="PROTOCOL TYPE ${in_type:--}"
[ -n "$in_host" ] && in_recv="$in_recv · HOST $in_host"
[ -n "$in_port" ] && in_recv="$in_recv · PORT $in_port"
[ -n "$in_isserver" ] && in_recv="$in_recv · ISSERVER $in_isserver"
[ -n "$in_iclport" ] && in_recv="$in_recv · ICLSERVERPORT $in_iclport"
local r_trxid_v="${r_trxid:--}$( [ "$r_wild" = "ON" ] && printf ' (WILDCARD ON)' )"
local xlate_v="${r_xlate:--}"
if [ "${xlate_sup:-0}" -gt 0 ] || [ "${xlate_fan:-0}" -gt 0 ]; then
xlate_v="$xlate_v — internal: ${xlate_sup:-0} suppress (filter), ${xlate_fan:-0} send/continue (fan-out)"
fi
local dest_v="${dhost:--}$( [ -n "$dport" ] && printf ':%s' "$dport" ) · process ${dproc:--} · TYPE ${dtype:--}"
_h3 "Delivery breakdown — $ob"
if [ "$ONENOTE_TABLE" = "1" ]; then
printf 'Field%sValue\n' "$TAB"
printf 'Flow%s%s\n' "$TAB" "${chain:--}"
printf 'How received (inbound %s)%s%s\n' "${route_thr:-?}" "$TAB" "$in_recv"
printf 'Inbound TRXID/TPS proc%s%s\n' "$TAB" "${in_proc:--}"
printf 'Route TRXID filter%s%s\n' "$TAB" "$r_trxid_v"
printf 'Route TYPE%s%s\n' "$TAB" "${r_type:--}"
printf 'UPOC PREPROCS%s%s\n' "$TAB" "${r_pre:--}"
printf 'UPOC POSTPROCS%s%s\n' "$TAB" "${r_post:--}"
printf 'XLATE%s%s\n' "$TAB" "$xlate_v"
printf 'Destination%s%s\n' "$TAB" "$dest_v"
else
_kv "Flow" "${chain:--}"
_kv "How received" "(inbound ${route_thr:-?}) $in_recv"
_kv "Inbound proc" "${in_proc:--}"
_kv "Route TRXID" "$r_trxid_v"
_kv "Route TYPE" "${r_type:--}"
_kv "UPOC PREPROCS" "${r_pre:--}"
_kv "UPOC POSTPROCS" "${r_post:--}"
_kv "XLATE" "$xlate_v"
_kv "Destination" "$dest_v"
fi
printf '\n' printf '\n'
} }
@ -933,22 +1030,24 @@ fi
# Compose the document # Compose the document
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
{ {
printf '# %s\n\n' "$TITLE" # PLAIN-TEXT document. Top-level title underlined with `=`; a plain lead-in
# line follows. No markdown anywhere.
_h1 "$TITLE"
if [ -n "$PATTERN" ]; then if [ -n "$PATTERN" ]; then
printf '_Cloverleaf interface documentation for the `%s` system — one section per matching delivery thread._\n\n' "$PATTERN" printf 'Cloverleaf interface documentation for the %s system — one section per matching delivery thread.\n\n' "$PATTERN"
else else
printf '_Cloverleaf interface documentation for `%s`._\n\n' "$THREAD_ARG" printf 'Cloverleaf interface documentation for %s.\n\n' "$THREAD_ARG"
fi fi
# Context block (human fill-ins kept from prior versions) # Context block (human fill-ins kept from prior versions) — plain label:value.
printf '## Context\n\n' _h2 "Context"
printf -- '- **Vendor POC:** %s\n' "${POC_VENDOR:-_(unfilled)_}" _kv "Vendor POC" "${POC_VENDOR:-(unfilled)}"
printf -- '- **Internal Owner:** %s\n' "${POC_INTERNAL:-_(unfilled)_}" _kv "Internal Owner" "${POC_INTERNAL:-(unfilled)}"
printf -- '- **Status:** %s\n' "${STATUS:-_(unfilled — production / test / decommissioning)_}" _kv "Status" "${STATUS:-(unfilled — production / test / decommissioning)}"
printf -- '- **Escalation:** %s\n' "${ESCALATION:-_(unfilled)_}" _kv "Escalation" "${ESCALATION:-(unfilled)}"
printf '\n' printf '\n'
if [ -n "$OPEN_ITEMS" ]; then printf '### Open items\n%s\n\n' "$OPEN_ITEMS"; fi if [ -n "$OPEN_ITEMS" ]; then _h3 "Open items"; printf '%s\n\n' "$OPEN_ITEMS"; fi
if [ -n "$NOTES" ]; then printf '### Notes\n%s\n\n' "$NOTES"; fi if [ -n "$NOTES" ]; then _h3 "Notes"; printf '%s\n\n' "$NOTES"; fi
# one section per delivery thread # one section per delivery thread
for line in "${TARGETS[@]}"; do for line in "${TARGETS[@]}"; do
@ -956,20 +1055,25 @@ fi
document_thread "$prot" "$site" "$nc" document_thread "$prot" "$site" "$nc"
done done
# Appendix — raw proc source (plainly labelled; NO "summarize" marker) # Appendix — raw proc source (OPT-IN via --raw-tcl). Plain text: an indented
# source dump rather than a markdown fenced code block, so OneNote shows the
# TCL verbatim with no stray backtick fences.
if [ "$WANT_APPENDIX" = "1" ] && [ "${#APPENDIX_LIST[@]}" -gt 0 ]; then if [ "$WANT_APPENDIX" = "1" ] && [ "${#APPENDIX_LIST[@]}" -gt 0 ]; then
printf '## Referenced proc source\n\n' _h2 "Referenced proc source"
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 'Raw TCL of every UPOC proc referenced above (the deterministic UPOC bits in each\n'
printf 'Description are extracted from these — included verbatim for audit).\n\n'
for rec in "${APPENDIX_LIST[@]}"; do for rec in "${APPENDIX_LIST[@]}"; do
IFS='|' read -r asite aproc apath <<< "$rec" IFS='|' read -r asite aproc apath <<< "$rec"
printf '### `%s` (site `%s`)\n\n' "$aproc" "$asite" # Keep the proc name in its REAL case (filenames are case-sensitive) — do
# not route through _h3's UPPERCASE transform. Underline with dashes.
printf 'proc: %s (site %s)\n' "$aproc" "$asite"
printf '%s\n\n' "$(printf '%*s' "$(( ${#aproc} + ${#asite} + 19 ))" '' | tr ' ' '-')"
if [ -n "$apath" ] && [ -f "$apath" ]; then if [ -n "$apath" ] && [ -f "$apath" ]; then
printf '_Source: `%s`_\n\n' "$apath" printf 'Source: %s\n\n' "$apath"
printf '```tcl\n'
cat "$apath" cat "$apath"
printf '\n```\n\n' printf '\n\n'
else else
printf '_(proc `%s.tcl` not found under any site tclprocs/)_\n\n' "$aproc" printf '(proc %s.tcl not found under any site tclprocs/)\n\n' "$aproc"
fi fi
done done
fi fi