From db3c5e0313bf952dc3ede380e0226e54f7f362eb Mon Sep 17 00:00:00 2001 From: Rafael Richards Date: Tue, 16 Jun 2026 11:55:37 -0400 Subject: [PATCH] =?UTF-8?q?VSLIO=20=E2=80=94=20VistA=20TCP=20transport=20a?= =?UTF-8?q?dapter=20(VSL/MSL=20M2=20Lane=20B)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Binds the portable STDNET socket seam (MSL v0.8.0) to VistA's Kernel device handler: outbound TCP via CALL^%ZISTCP / CLOSE^%ZISTCP (ICR #2118, Supported). Re-pins msl_ref v0.7.0 -> v0.8.0 (now carries seams.STDNET; first real fetch-at-tag run of the T0b.4 pin path). VSLIO exposes the CLIENT subset of STDNET's signature (connect/read/write/close) -- VistA has NO Supported Kernel listen/accept API (corpus-confirmed; inbound is the listener-JOB pattern), so the SERVER side stays in portable STDNET, never duplicated up (waterline). The handle is the opened device (IO). CALL^%ZISTCP is argument-passed `CALL(IP,SOCK,TO)` (the gold doc's input-variable convention is wrong vs the live routine); it leaves the socket device current, so connect/close save+restore $IO. Acceptance (over the driver): vehu (YDB) 10/10 -- tier-1 CALL^%ZISTCP POP=0 + tier-2 byte echo (raw STDNET listener server side + VSLIO client) + connect- failure + TLS-gap; foia-t12 (IRIS) 6/6 -- connect-failure proves CALL^%ZISTCP is wired on IRIS, TLS-gap green, loopback soft-skips (STDNET's IRIS leg deferred). TLS gap loud (mirrors STDNET): $$tlsAvailable=0; $$connectTls raises U-VSLIO-NOTLS with $$tlsHelp/$$lastError remediation. Tier-3 real TLS stays infra-blocked (the gating cleanup STDNET's discoveries row tracks). 3 boundaries green: check-msl-pin@v0.8.0 / check-icr (4: VSLCFG #2263 + VSLIO #2118) / check-citations (4 vs gold corpus). make check-fast clean (fmt/lint/arch layer v + namespaces + engine-access + check-kids). No KIDS/VSLBLD (M5). Co-Authored-By: Claude Opus 4.8 (1M context) --- Makefile | 2 +- dist/icr-registry.json | 22 ++++++ dist/msl-seam-pin.json | 132 +++++++++++++++++++++++++++++++++- dist/namespace-registry.json | 3 +- docs/memory/MEMORY.md | 1 + docs/memory/m2-vslio.md | 81 +++++++++++++++++++++ src/VSLIO.m | 134 +++++++++++++++++++++++++++++++++++ tests/VSLIOTST.m | 53 ++++++++++++++ 8 files changed, 425 insertions(+), 3 deletions(-) create mode 100644 docs/memory/m2-vslio.md create mode 100644 src/VSLIO.m create mode 100644 tests/VSLIOTST.m diff --git a/Makefile b/Makefile index f309f0e..e518cd7 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,7 @@ arch: # Engine-bound: stage STDASSERT (+ harness) from m-stdlib so VSL*TST suites # resolve ^STDASSERT. Pass --engine ydb|iris and --docker . test: - $(M) test $(ENGINE_FLAGS) --routines $(MSTDLIB)/src $(TESTS) + $(M) test $(ENGINE_FLAGS) --routines $(SRC) --routines $(MSTDLIB)/src $(TESTS) coverage: $(M) coverage $(ENGINE_FLAGS) --routines $(MSTDLIB)/src --min-percent=85 $(SRC) $(TESTS) diff --git a/dist/icr-registry.json b/dist/icr-registry.json index 0071ea8..ec13d68 100644 --- a/dist/icr-registry.json +++ b/dist/icr-registry.json @@ -20,5 +20,27 @@ }, "status": "Supported" } + ], + "VSLIO": [ + { + "call": "CALL^%ZISTCP", + "custodian": "XU", + "icr": 2118, + "source": { + "anchor": "callzistcp-make-tcpip-connection-remote-system", + "doc_key": "XU/krn_8_0_dg_device_handler_ug" + }, + "status": "Supported" + }, + { + "call": "CLOSE^%ZISTCP", + "custodian": "XU", + "icr": 2118, + "source": { + "anchor": "closezistcp-close-tcpip-connection-remote-system", + "doc_key": "XU/krn_8_0_dg_device_handler_ug" + }, + "status": "Supported" + } ] } diff --git a/dist/msl-seam-pin.json b/dist/msl-seam-pin.json index 18ec9f7..30fa378 100644 --- a/dist/msl-seam-pin.json +++ b/dist/msl-seam-pin.json @@ -1,5 +1,5 @@ { - "msl_ref": "v0.7.0", + "msl_ref": "v0.8.0", "seams": { "STDENV": { "contract_version": 1, @@ -30,6 +30,136 @@ } } ] + }, + "STDNET": { + "contract_version": 1, + "entry_points": [ + { + "args": [ + { + "doc": "a listener handle from $$listen", + "name": "id", + "type": "numeric" + }, + { + "doc": "seconds to wait for a connection", + "name": "timeout", + "type": "numeric" + } + ], + "label": "$$accept^STDNET(id, timeout)", + "raises": [], + "returns": { + "doc": "a connected handle (>0), or 0 on timeout/failure", + "type": "numeric" + } + }, + { + "args": [ + { + "doc": "a handle from $$listen/$$accept/$$connect", + "name": "id", + "type": "numeric" + } + ], + "label": "$$close^STDNET(id)", + "raises": [], + "returns": { + "doc": "1 (idempotent)", + "type": "bool" + } + }, + { + "args": [ + { + "doc": "host/IP to connect to", + "name": "host", + "type": "string" + }, + { + "doc": "TCP port", + "name": "port", + "type": "numeric" + }, + { + "doc": "seconds to wait for the connect", + "name": "timeout", + "type": "numeric" + } + ], + "label": "$$connect^STDNET(host, port, timeout)", + "raises": [], + "returns": { + "doc": "a connected handle (>0), or 0 on failure", + "type": "numeric" + } + }, + { + "args": [ + { + "doc": "TCP port to bind; 0 lets the OS choose", + "name": "port", + "type": "numeric" + } + ], + "label": "$$listen^STDNET(port)", + "raises": [], + "returns": { + "doc": "a listener handle (>0), or 0 on failure", + "type": "numeric" + } + }, + { + "args": [ + { + "doc": "a connected handle", + "name": "id", + "type": "numeric" + }, + { + "doc": "maximum bytes to read", + "name": "maxlen", + "type": "numeric" + }, + { + "doc": "seconds to wait for data", + "name": "timeout", + "type": "numeric" + }, + { + "doc": "by-ref; receives the bytes read", + "name": "buf", + "type": "string" + } + ], + "label": "$$read^STDNET(id, maxlen, timeout, buf)", + "raises": [], + "returns": { + "doc": "bytes read (0 on timeout/EOF)", + "type": "numeric" + } + }, + { + "args": [ + { + "doc": "a connected handle", + "name": "id", + "type": "numeric" + }, + { + "doc": "bytes to write (raw, no delimiter)", + "name": "buf", + "type": "string" + } + ], + "label": "$$write^STDNET(id, buf)", + "raises": [], + "returns": { + "doc": "1 on success; 0 on failure", + "type": "bool" + } + } + ] } } } diff --git a/dist/namespace-registry.json b/dist/namespace-registry.json index 1743a69..bf658ec 100644 --- a/dist/namespace-registry.json +++ b/dist/namespace-registry.json @@ -10,7 +10,8 @@ "discovered": { "globals": [], "routines": [ - "VSLCFG" + "VSLCFG", + "VSLIO" ] } } diff --git a/docs/memory/MEMORY.md b/docs/memory/MEMORY.md index 49eadb8..2a096da 100644 --- a/docs/memory/MEMORY.md +++ b/docs/memory/MEMORY.md @@ -6,3 +6,4 @@ One line per memory file. Content lives in the files, not here. - [meta-root + owed VSLSEED filer](meta-root-and-owed.md) — layer declared in **root `repo.meta.json`** (migrated off `dist/` 2026-06-15, Phase B item 1); the owed `fileViaDie^VSLSEED` FileMan filer (re-homed from m-stdlib STDSEED per the G2 waterline decision) lands here when a v-layer seeding consumer needs it. - [t0b4-msl-seam-pin](t0b4-msl-seam-pin.md) — VSL T0b.4 (v-stdlib leg, 2026-06-15): the **cross-repo MSL seam-contract pin** — `dist/msl-seam-pin.json` (pins MSL `v0.6.0` + frozen `seams` copy) + `tools/msl_seam_pin.py` drift gate (`make check-msl-pin`). Reads the sibling MSL @ tag via `git show`; **SKIP-green when unreachable** (so it SKIPs in CI today, asserts at dev-time); fetch-at-tag path deferred to T1.1. Don't conflate with v-stdlib's own (VSL*) `dist/seam-snapshot.json`. - [t0b3-drift-gates](t0b3-drift-gates.md) — VSL T0b.3 (v-stdlib leg, 2026-06-15): the **four drift gates** mirrored from m-stdlib (tools/ copied verbatim except gen-manifest's `VSL*` glob). All **green-empty** (no VSL* modules yet); red-on-violation proven. `repo.meta.json` gained `namespaces: {VSL}`; `ci.yml` runs them via an `m-ci.yml` caller with `engine-targets: ""` (engine-free). fmt/lint left out of CI (Makefile `M` default is a local path). Next: T0b.4 freeze+pin the seam contract. +- [m2-vslio](m2-vslio.md) — VSL/MSL **M2 Lane B DONE** (2026-06-16): `VSLIO` binds the `STDNET` socket seam (MSL **v0.8.0**) to VistA's Kernel device handler — **outbound TCP via `CALL^%ZISTCP` (ICR #2118)**; the CLIENT subset only (VistA has no Supported listen/accept; server side stays in STDNET). Re-pinned `msl_ref` v0.7.0→v0.8.0 (first real fetch-at-tag). **vehu(YDB) 10/10** (tier-1 POP=0 + tier-2 echo) · **foia-t12(IRIS) 6/6** (connect-failure proves the binding; loopback soft-skips on STDNET's deferred IRIS leg). TLS-gap loud (`$$tlsAvailable`=0, `$$connectTls` raises `U-VSLIO-NOTLS`). **KEY:** the gold doc's input-var convention for `CALL^%ZISTCP` is WRONG — it's argument-passed `CALL(IP,SOCK,TO)`; leaves the socket device CURRENT (restore `$IO`). 3 boundaries green (check-msl-pin@v0.8.0 / check-icr 4 / check-citations 4). diff --git a/docs/memory/m2-vslio.md b/docs/memory/m2-vslio.md new file mode 100644 index 0000000..98b68b3 --- /dev/null +++ b/docs/memory/m2-vslio.md @@ -0,0 +1,81 @@ +--- +name: m2-vslio +description: VSL/MSL M2 Lane B DONE — VSLIO binds the STDNET socket seam to VistA's Kernel device handler (outbound TCP via CALL^%ZISTCP, ICR #2118). Re-pinned msl_ref v0.7.0→v0.8.0. Tier1 POP=0 + tier2 echo GREEN on vehu(YDB); CALL^%ZISTCP wired on both engines; IRIS loopback soft-skips on STDNET's deferred IRIS leg; tier3 TLS loud-blocked. 3 boundaries green. +metadata: + type: project +--- + +# VSL T-M2 Lane B — VSLIO (VistA TCP transport over ^%ZISTCP), 2026-06-16 + +The VistA side of the M2 socket/TLS seam (S4): `VSLIO` binds the portable MSL +`STDNET` seam (MSL **v0.8.0**) to VistA's Kernel device handler. Branch +`m2-vslio` off v-stdlib `main`. Second `VSL*` module (after `VSLCFG`). + +## Re-pin (boundary ①) — first real fetch-at-tag +`make pin` after hand-setting `dist/msl-seam-pin.json` `msl_ref` v0.7.0→**v0.8.0**: +syncs the `seams` block from the sibling m-stdlib `git show v0.8.0:dist/ +seam-snapshot.json` → now carries **STDENV + STDNET** (6 verbs). `check-msl-pin` +green (2 seams match MSL@v0.8.0). This is the first real run of T0b.4's +fetch-the-contract-at-the-tag path (it had only ever SKIP'd before). + +## The adapter — outbound CLIENT only (key design finding) +**VistA has NO Supported Kernel listen/accept (server) API** (corpus-confirmed: +Kernel Device Handler DG lists only the client-side `CALL^%ZISTCP`; inbound is the +listener-process/JOB pattern — `ZISTCP^XWBTCPM1` etc). So VSLIO binds the **client +subset** of STDNET's signature; the SERVER/listener side stays in portable STDNET +(never duplicated up — waterline §9 no-duplication). + +API: `$$connect^VSLIO(host,port,timeout)`→handle · `$$read(id,max,to,.buf)` · +`$$write(id,buf)` · `$$close(id)` · `$$lastError()`. The handle **is the opened +device** (`IO`, e.g. `SCK$57028`). + +## CALL^%ZISTCP — the real API (the corpus doc was WRONG) +The vdocs gold doc describes an **input-variable** convention (`IPADDRESS`/`SOCKET`/ +`TIMEOUT`, bare `D CALL^%ZISTCP`) — that is **wrong vs the live routine**. The +actual entry is **argument-passed**: `CALL(IP,SOCK,TO)`, i.e. +**`D CALL^%ZISTCP(host,port,timeout)`** (read the source on vehu with +`$T(CALL+i^%ZISTCP)`). `POP`=0 success / positive fail; on success **`IO`** holds +the socket device. The GT.M arm (`CGTM`) does `OPEN NIO:(CONNECT=IP_":"_SOCK_ +":TCP":ATTACH="client"):TO:"SOCKET"` and **leaves the socket device CURRENT** — so +`connect` must `use pio` (restore `$IO`) BEFORE returning or its caller writes into +the socket. `CLOSE^%ZISTCP` reads `IO` (set `IO`=handle first) and calls +`HOME^%ZIS` → also save/restore `$IO` around it. `@icr 2118 @call CALL^%ZISTCP` + +`@call CLOSE^%ZISTCP`, `@source XU/krn_8_0_dg_device_handler_ug#callzistcp-…` / +`#closezistcp-…` (check-icr + check-citations green). Engine-portable: `^%ZISTCP` +branches by OS internally (CGTM/CONT), so VSLIO needs no `$ZVERSION` arm. + +## Acceptance (tiered, both engines over the driver) +- **vehu (YDB) 10/10:** tier-1 `CALL^%ZISTCP`→POP=0 + tier-2 byte echo (loopback: + raw **STDNET listener** for the server side — STDNET works on the GT.M VistA + engine — and VSLIO `CALL^%ZISTCP` as the client; ping out, pong back) + the + connect-failure + TLS-gap tests. +- **foia-t12 (IRIS) 6/6:** the connect-failure test (CALL^%ZISTCP `CONT` path → + POP positive to a closed port → handle 0) proves the binding is wired on IRIS; + TLS-gap tests pass; the **loopback soft-skips** (STDNET listener is YDB-only — + STDNET's IRIS leg is the owed m-stdlib follow-up, so the IRIS POP=0+echo is + blocked on it, not on VSLIO). +- Test staging: `m test --engine … --docker … [--namespace VISTA] --routines src + --routines /src tests/VSLIOTST.m` (the Makefile `test` target now adds + `--routines $(SRC)` so VSL* suites resolve, like the VSLCFGTST canonical cmd). + +## TLS gap — loud (mirrors STDNET, per the standing directive) +`$$tlsAvailable^VSLIO()`=0; `$$connectTls^VSLIO` **raises `,U-VSLIO-NOTLS,`** (via +`raiseNoTls`, stashing `^TMP($job,"vslio","err")`) — never silent plaintext; +`$$tlsHelp`/`$$lastError` carry remediation (cert + `XU*8.0*787` / IRIS +`Security.SSLConfigs`; wire over the Kernel `INIT-XUTLS` #7616 + `ISTLSSERVERCONF- +XUSUDO` #7617). **Gotcha:** the remediation string must NOT contain literal +`^XUTLS`/`^XUSUDO` — `check-icr` scans code strings for `^XU*` and flags them as +undeclared L4 calls (write `INIT-XUTLS`/`ISTLSSERVERCONF-XUSUDO` with hyphens). +`m-lint disable-file=M-MOD-024` (device-handler input vars read as locals, like +STDNET). Tier-3 (real TLS echo) stays infra-blocked — the gating cleanup STDNET's +discoveries row already tracks. + +## Gates (all green) +`make check-fast`: fmt/lint/`m arch check .` (layer v) + check-seams (0 — VSLIO is +the consumer, no @seam) + **check-icr (4: VSLCFG #2263 ×2 + VSLIO #2118 ×2)** + +**check-citations (4 vs gold corpus)** + check-namespaces (2 routines VSL) + +**check-msl-pin (v0.8.0)** + check-engine-access + check-kids. No KIDS/VSLBLD work +(that's M5); VSLIO is NOT in the VSL KIDS base yet. + +**Next: M3** (VSLFS — the FileMan storage seam, §12.2). Owed for full M2: STDNET's +IRIS leg (unblocks the IRIS loopback) + tier-3 TLS (the gating infra cleanup). diff --git a/src/VSLIO.m b/src/VSLIO.m new file mode 100644 index 0000000..768b9d7 --- /dev/null +++ b/src/VSLIO.m @@ -0,0 +1,134 @@ +VSLIO ; v-stdlib — VistA TCP transport adapter over the Kernel device handler. + ; m-lint: disable-file=M-MOD-024 + ; M-MOD-024 false positives: the analyser reads the Kernel device-handler + ; input variables (IPADDRESS/SOCKET/TIMEOUT/IO/POP) and the device USE/READ + ; targets as locals-before-def; they are the documented ^%ZISTCP I/O + ; convention. Same suppression as STDJSON/STDHTTP/STDNET. + ; + ; Binds the MSL socket seam (STDNET, S4) to VistA's Kernel device handler: + ; outbound TCP via CALL^%ZISTCP / CLOSE^%ZISTCP (ICR #2118, Supported). It + ; exposes the CLIENT subset of STDNET's signature (connect/read/write/close) + ; — VistA has NO Supported Kernel listen/accept (server) API (Kernel Device + ; Handler DG; inbound is the listener-process/JOB pattern), so the SERVER side + ; of a connection stays in the portable STDNET seam, never duplicated here. + ; The adapter contains ONLY the VistA binding; framing/buffering stays in + ; STD* and is called up (m/v waterline). + ; + ; Public API (raw bytes; the handle is the opened device, $$connect's return): + ; $$connect^VSLIO(host,port,timeout) — CALL^%ZISTCP outbound -> handle or 0 + ; $$read^VSLIO(id,maxlen,timeout,.buf) — raw read up to maxlen bytes -> count + ; $$write^VSLIO(id,buf) — raw write -> 1/0 + ; $$close^VSLIO(id) — CLOSE^%ZISTCP -> 1 + ; $$lastError^VSLIO() — last error message, else "" + ; + ; *** SECURITY / TLS GAP — same posture as STDNET (loud, never silent) *** + ; This adapter opens RAW PLAINTEXT TCP. TLS (Kernel $$INIT^XUTLS, ICR #7616, + ; using a named config defaulting to the DEFAULT TLS SERVER CONFIG parameter) + ; is NOT wired: it requires engine TLS infrastructure absent on the test + ; engines (a cert + Kernel patch XU*8.0*787 / an IRIS Security.SSLConfigs + ; entry; IRIS-only per the gold corpus). So $$tlsAvailable^VSLIO()=0 and + ; $$connectTls^VSLIO RAISES ,U-VSLIO-NOTLS, (with $$tlsHelp/$$lastError + ; carrying remediation) rather than fall back to plaintext. This is a GATING + ; cleanup before the MSL/VSL stack is complete — see STDNET's TLS gap + ; (m-stdlib docs/tracking/discoveries.md, 2026-06-16) and VSLIO tier-3. + ; + ; The last error message is stashed at ^TMP($job,"vslio","err") for $$lastError. + ; Errors set $ECODE to one of: + ; ,U-VSLIO-NOTLS, TLS requested but not wired (see $$tlsHelp) + ; + quit + ; + ; ---------- outbound TCP client (the VistA binding) ---------- + ; +connect(host,port,timeout) ; Open an outbound TCP connection; return the device handle, else 0. + ; doc: @param host string host/IP to connect to (IPADDRESS) + ; doc: @param port numeric remote TCP port (SOCKET) + ; doc: @param timeout numeric open timeout in seconds (default 10) + ; doc: @returns string the opened device (handle) on POP=0, else 0 + ; doc: @icr 2118 @call CALL^%ZISTCP @status Supported @custodian XU @source XU/krn_8_0_dg_device_handler_ug#callzistcp-make-tcpip-connection-remote-system + ; doc: WARNING: PLAINTEXT — no TLS (see $$tlsAvailable / $$connectTls; known gap). + new IO,POP,pio,dev + set pio=$io + do CALL^%ZISTCP(host,port,$get(timeout,30)) + if +$get(POP) use pio quit 0 + set dev=IO + use pio + quit dev + ; +read(id,maxlen,timeout,buf) ; Raw-read up to maxlen bytes from a handle. + ; doc: @param id string a handle from $$connect (the device) + ; doc: @param maxlen numeric maximum bytes to read + ; doc: @param timeout numeric seconds to wait for data + ; doc: @param buf string by-ref; receives the bytes read + ; doc: @returns numeric bytes read (0 on timeout/EOF) + new x,pio + set buf="",pio=$io + use id read x#maxlen:timeout + use pio + set buf=x + quit $length(x) + ; +write(id,buf) ; Raw-write `buf` to a connected handle. + ; doc: @param id string a handle from $$connect (the device) + ; doc: @param buf string bytes to write (raw, no delimiter) + ; doc: @returns bool 1 on success + new pio + set pio=$io + use id write buf + use pio + quit 1 + ; +close(id) ; Close an outbound connection opened by $$connect. + ; doc: @param id string a handle from $$connect (the device) + ; doc: @returns bool 1 (idempotent) + ; doc: @icr 2118 @call CLOSE^%ZISTCP @status Supported @custodian XU @source XU/krn_8_0_dg_device_handler_ug#closezistcp-close-tcpip-connection-remote-system + new IO,pio + set pio=$io,IO=id + do CLOSE^%ZISTCP + use pio + quit 1 + ; +lastError() ; The last VSLIO error message (e.g. the TLS-gap remediation). + ; doc: @returns string ^TMP($job,"vslio","err"), or "" if none + quit $get(^TMP($job,"vslio","err")) + ; + ; ---------- TLS (known gap — loud, never silent) ---------- + ; +tlsAvailable() ; 0 — VSLIO has no wired TLS (engine TLS infra + XU*8.0*787 absent). + ; doc: @returns bool always 0 today: raw plaintext only (a known, tracked gap) + ; doc: Check before any secure use; $$tlsHelp has remediation. + quit 0 + ; +tlsHelp() ; Human-readable remediation for the TLS gap (diagnostics/logs). + ; doc: @returns string multi-line: why there is no TLS + how to remedy + quit $$noTlsMsg() + ; +connectTls(host,port,timeout,config) ; UNIMPLEMENTED — raises, never opens plaintext. + ; doc: @param host string host/IP (ignored — not implemented) + ; doc: @param port numeric TCP port (ignored — not implemented) + ; doc: @param timeout numeric seconds (ignored — not implemented) + ; doc: @param config string named TLS config (ignored — not implemented) + ; doc: @returns string never returns a handle; always raises + ; doc: @raises U-VSLIO-NOTLS TLS not wired (known gap; see $$tlsHelp) + do raiseNoTls("connectTls") + quit 0 + ; + ; ---------- internals ---------- + ; +noTlsMsg() ; The TLS-gap remediation message (one source for help + lastError). + new m,nl + set nl=$char(10) + set m="VSLIO-NOTLS: VSLIO opens RAW PLAINTEXT TCP — TLS is NOT wired (a known, tracked gap)." + set m=m_nl_"Do NOT use it for secure transport: a plaintext socket would silently expose credentials/PHI." + set m=m_nl_"Remedy (GATING — must close before the MSL/VSL stack is complete):" + set m=m_nl_" 1. Provision engine TLS: a server cert + Kernel patch XU*8.0*787 (DEFAULT TLS SERVER CONFIG)" + set m=m_nl_" + an IRIS Security.SSLConfigs entry (IRIS-only per the corpus), or the GT.M $gtmtls path." + set m=m_nl_" 2. Wire $$connectTls over the Kernel TLS init API (INIT-XUTLS, ICR #7616) with the named config" + set m=m_nl_" + the ISTLSSERVERCONF-XUSUDO validator (#7617), then flip $$tlsAvailable to 1." + set m=m_nl_" 3. Tracked with STDNET's TLS gap (m-stdlib docs/tracking/discoveries.md, 2026-06-16)." + quit m + ; +raiseNoTls(who) ; Stash remediation, then raise the known-gap error (loud, not silent). + set ^TMP($job,"vslio","err")=who_": "_$$noTlsMsg() + set $ecode=",U-VSLIO-NOTLS," + quit diff --git a/tests/VSLIOTST.m b/tests/VSLIOTST.m new file mode 100644 index 0000000..89ebf3c --- /dev/null +++ b/tests/VSLIOTST.m @@ -0,0 +1,53 @@ +VSLIOTST ; v-stdlib — VSLIO (VistA TCP transport over ^%ZISTCP) test suite. + ; Exercises the outbound client binding (CALL^%ZISTCP) against a live VistA, + ; over the driver stack only (m/v waterline): + ; m test --engine ydb --docker vehu \ + ; --routines src --routines /src tests/VSLIOTST.m + ; m test --engine iris --docker foia-t12 --namespace VISTA \ + ; --routines src --routines /src tests/VSLIOTST.m + ; The loopback (tier 1 POP=0 + tier 2 echo) uses a raw STDNET listener for the + ; SERVER side (VistA has no Supported listen/accept API) — STDNET is YottaDB- + ; only today, so the loopback soft-skips on IRIS; the connect-failure and + ; TLS-gap tests run on both engines. + new pass,fail + do start^STDASSERT(.pass,.fail) + ; + do tConnectFailureReportsPop(.pass,.fail) + do tLoopbackEcho(.pass,.fail) + do tTlsGapIsLoud(.pass,.fail) + ; + do report^STDASSERT(pass,fail) + quit + ; +tConnectFailureReportsPop(pass,fail) ;@TEST "CALL^%ZISTCP to a closed port reports failure (handle 0) — the binding is wired, both engines" + new h + set h=$$connect^VSLIO("127.0.0.1",65000,2) + do true^STDASSERT(.pass,.fail,h=0,"connect to a closed port returns 0 (POP positive)") + quit + ; +tLoopbackEcho(pass,fail) ;@TEST "VSLIO CALL^%ZISTCP connects (POP=0) and echoes a byte through ^%ZISTCP (loopback)" + new srv,cli,conn,port,buf,n + if '$$available^STDNET() do true^STDASSERT(.pass,.fail,1,"STDNET listener unavailable here (IRIS) - loopback skipped") quit + set srv=$$listen^STDNET(0) + set port=$$boundport^STDNET(srv) + set cli=$$connect^VSLIO("127.0.0.1",port,5) + do true^STDASSERT(.pass,.fail,cli'=0,"CALL^%ZISTCP connected (POP=0)") + set conn=$$accept^STDNET(srv,5) + do true^STDASSERT(.pass,.fail,conn>0,"STDNET server accepted the connection") + do true^STDASSERT(.pass,.fail,$$write^VSLIO(cli,"ping"),"VSLIO wrote outbound") + set n=$$read^STDNET(conn,99,5,.buf) + do eq^STDASSERT(.pass,.fail,buf,"ping","server received VSLIO's bytes") + set n=$$write^STDNET(conn,"pong") + set n=$$read^VSLIO(cli,99,5,.buf) + do eq^STDASSERT(.pass,.fail,buf,"pong","VSLIO received the reply") + set n=$$close^VSLIO(cli) + do close^STDNET(conn) + do close^STDNET(srv) + quit + ; +tTlsGapIsLoud(pass,fail) ;@TEST "TLS is a loud, documented gap — never a silent plaintext fallback (both engines)" + do true^STDASSERT(.pass,.fail,'$$tlsAvailable^VSLIO(),"tlsAvailable()=0 (TLS not wired)") + do contains^STDASSERT(.pass,.fail,$$tlsHelp^VSLIO(),"NOTLS","tlsHelp carries remediation") + do raises^STDASSERT(.pass,.fail,"set x=$$connectTls^VSLIO(""h"",1,1,""cfg"")","U-VSLIO-NOTLS","connectTls raises U-VSLIO-NOTLS") + do contains^STDASSERT(.pass,.fail,$$lastError^VSLIO(),"Remedy","lastError carries the remediation steps") + quit