diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 21713f2..e7a4ad2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,5 +22,5 @@ jobs: ci: uses: vista-cloud-dev/.github/.github/workflows/m-ci.yml@main with: - engine-free-targets: "check-seams check-icr check-citations check-namespaces check-msl-pin" + engine-free-targets: "check-seams check-icr check-citations check-namespaces check-msl-pin check-engine-access" engine-targets: "" diff --git a/Makefile b/Makefile index a86664b..f309f0e 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ ENGINE_FLAGS := $(if $(ENGINE),--engine $(ENGINE)) $(if $(DOCKER),--docker $(DOC .PHONY: all check fmt fmt-check lint arch test coverage clean \ seams check-seams icr check-icr check-citations namespaces check-namespaces \ - pin check-msl-pin gates + pin check-msl-pin check-engine-access gates all: check @@ -89,9 +89,17 @@ pin: check-msl-pin: @python3 tools/msl_seam_pin.py --check +# Transport-monopoly gate: no committed test/script/Makefile may hand-roll engine +# access (raw docker-exec into an engine, iris-session, gtm-dist, etc.). All +# engine work goes through the m-driver-sdk -> m-ydb/m-iris stack (`m test +# --docker`, `m vista exec`). The committed-artifact backstop to the PreToolUse +# engine-stack-guard hook. Org CLAUDE.md §"m/v waterline" -> "Engine access". +check-engine-access: + @python3 tools/check_engine_access.py --check + # Aggregate of the engine-free drift gates (the four own-tier gates + the -# upward MSL pin). -gates: check-seams check-icr check-citations check-namespaces check-msl-pin +# upward MSL pin + the transport-monopoly gate). +gates: check-seams check-icr check-citations check-namespaces check-msl-pin check-engine-access # Engine-free gates (fmt/lint/arch + drift gates) + the engine-bound suite. CI # runs the full set; `make check-fast` needs no engine. diff --git a/dist/icr-registry.json b/dist/icr-registry.json index 0967ef4..0071ea8 100644 --- a/dist/icr-registry.json +++ b/dist/icr-registry.json @@ -1 +1,24 @@ -{} +{ + "VSLCFG": [ + { + "call": "$$GET^XPAR", + "custodian": "XU", + "icr": 2263, + "source": { + "anchor": "getxpar-return-an-instance-of-a-parameter", + "doc_key": "XU/krn_8_0_dg_toolkit_ug" + }, + "status": "Supported" + }, + { + "call": "EN^XPAR", + "custodian": "XU", + "icr": 2263, + "source": { + "anchor": "enxpar-add-change-delete-parameters", + "doc_key": "XU/krn_8_0_dg_toolkit_ug" + }, + "status": "Supported" + } + ] +} diff --git a/dist/msl-seam-pin.json b/dist/msl-seam-pin.json index 8f45cb0..18ec9f7 100644 --- a/dist/msl-seam-pin.json +++ b/dist/msl-seam-pin.json @@ -1,4 +1,35 @@ { - "msl_ref": "v0.6.0", - "seams": {} + "msl_ref": "v0.7.0", + "seams": { + "STDENV": { + "contract_version": 1, + "entry_points": [ + { + "args": [ + { + "doc": "by-ref env tree", + "name": "env", + "type": "array" + }, + { + "doc": "env key", + "name": "key", + "type": "string" + }, + { + "doc": "fallback when key is absent", + "name": "default", + "type": "string" + } + ], + "label": "$$get^STDENV(env, key, default)", + "raises": [], + "returns": { + "doc": "env(key) or default", + "type": "string" + } + } + ] + } + } } diff --git a/dist/namespace-registry.json b/dist/namespace-registry.json index d71e85d..1743a69 100644 --- a/dist/namespace-registry.json +++ b/dist/namespace-registry.json @@ -9,6 +9,8 @@ }, "discovered": { "globals": [], - "routines": [] + "routines": [ + "VSLCFG" + ] } } diff --git a/docs/memory/MEMORY.md b/docs/memory/MEMORY.md index d9fcf3a..49eadb8 100644 --- a/docs/memory/MEMORY.md +++ b/docs/memory/MEMORY.md @@ -2,6 +2,7 @@ One line per memory file. Content lives in the files, not here. +- [t1.2-vslcfg](t1.2-vslcfg.md) — VSL T1.2 (2026-06-16): **VSLCFG**, the first VSL* module — binds the STDENV config seam to XPAR at the **SYS entity** (`$$get`=`$$GET^XPAR("SYS",…)`, `$$set`=`EN^XPAR("SYS",…)`), validated live on vehu. **All 3 determinism boundaries GREEN** (① re-pin `msl_ref`→v0.7.0 carrying real STDENV; ② check-icr ICR #2263; ③ check-citations vs gold corpus). Citation reconciled: **XU/krn_8_0_dg_toolkit_ug / ICR #2263**, not the plan's XT guess. **Keystone unblock:** driver→live XPAR via `M_YDB_GBLDIR`/`M_YDB_ROUTINES`. **Remaining blocker:** `m test --docker` honors gbldir but NOT M_YDB_ROUTINES → VSLCFGTST aborts 0/0 (XPAR routines unresolved); fix = layer the resident routine base in the m test/m-ydb path (m-cli/m-ydb session) or test-in-place `--resident`. - [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. diff --git a/docs/memory/t1.2-vslcfg.md b/docs/memory/t1.2-vslcfg.md new file mode 100644 index 0000000..8ab5b8c --- /dev/null +++ b/docs/memory/t1.2-vslcfg.md @@ -0,0 +1,65 @@ +--- +name: t1.2-vslcfg +description: VSL T1.2 DONE — VSLCFG adapter (binds STDENV config seam to XPAR); all 3 boundaries green + VSLCFGTST 3/3 GREEN on both engines (the m test routine-base blocker was fixed in m-cli) +metadata: + type: project +--- + +# VSL T1.2 — VSLCFG adapter over XPAR (v-stdlib, 2026-06-16) + +First `VSL*` module. Binds the MSL config-read seam (`$$get^STDENV`) to VistA +XPAR (Parameter Tools) at the **SYS entity**. Branch `t1.2-vslcfg`. + +**The adapter:** `$$get^VSLCFG(key,default)` = `$$GET^XPAR("SYS",key,1)` (empty → +default); `$$set^VSLCFG(key,value)` = `EN^XPAR("SYS",key,1,value)`. SYS-entity read +is the faithful analog of STDENV's flat key→value config (NOT a precedence walk — +`$$GET^XPAR("ALL",…)` did **not** return a SYS value for the probed param, so bind +SYS directly). Contains ONLY the VistA binding; no parsing/formatting (waterline). +Round-trip + restore proven live on vehu via `m vista exec`. + +**Citation reconciled (the plan was WRONG):** XPAR is documented under **Kernel** +`XU/krn_8_0_dg_toolkit_ug` (is_latest, verified), governed by **ICR #2263 +(Supported, custodian XU)** — NOT the plan's `XT/ktk7_3p26sp` / `XT*7.3*26`. +Anchors: `$$GET^XPAR`→`#getxpar-return-an-instance-of-a-parameter`, +`EN^XPAR`→`#enxpar-add-change-delete-parameters`. + +**All three determinism-ledger boundaries GREEN (engine-free):** +- ① `check-msl-pin`: re-pinned `dist/msl-seam-pin.json` `msl_ref` v0.6.0→**v0.7.0** + (`make pin`); pin carries real `seams.STDENV`, matches MSL@v0.7.0. +- ② `check-icr`: 2 declarations (ICR #2263) for the `$$GET^XPAR`/`EN^XPAR` sites. +- ③ `check-citations`: both `@source` anchors verified vs the vdocs gold corpus. +Plus check-seams/namespaces/engine-access + fmt/lint/arch — `make check-fast` clean. + +**THE BIG UNBLOCK — driver → live XPAR (keystone):** the m-ydb docker transport +reaches vehu but with an EMPTY `$ZGBLDIR` by default → VistA data globals invisible +(VSLSMOKETST passed only on staged routines + scratch). Fix = the same gbldir/ +routines env `v pkg` used (see m-ydb memory `m-ydb-docker-gbldir`): +`M_YDB_CONTAINER=vehu M_YDB_GBLDIR=/home/vehu/g/vehu.gld M_YDB_ROUTINES=''`. Then `m vista exec --engine ydb --transport docker ''` sees +globals (1211 XPAR defs) AND resolves `$$GET^XPAR`. + +**THE BLOCKER — FIXED in m-cli (2026-06-16).** `m test --docker` uses m-cli's +internal **DockerEngine** (not the m-ydb driver), which exported +`ydb_routines=" $ydb_routines"`. A GT.M VistA (`vehu`) sets +`gtmroutines`, NOT `ydb_routines`, so the export became stageDir-only and (since +GT.M honors `ydb_routines` over `gtmroutines`) vehu's resident XPAR/FileMan +routines vanished → VSLCFGTST aborted **0/0**. Fix = `dockerEnvPrefix` falls back +to `${ydb_routines:-$gtmroutines}` (m-cli `docker-routines-base` `d9ee76a`; see +m-cli memory `docker-routines-gtmroutines-fallback`). **VSLCFGTST now 3/3 GREEN on +vehu (YDB) + foia-t12 (IRIS `--namespace VISTA`)** — no `M_YDB_*` host vars needed; +m-test-engine regression green. The driver *exec* path (m vista exec / v pkg) was +never affected — that uses the m-ydb driver + `M_YDB_*` knobs (a separate path). + +**Test fixture note:** a hand-built `#8989.51` def (direct global SETs) is NOT +enough for `EN^XPAR` to FILE a value (no error, nothing stored) — XPAR's filer +needs a FileMan-built def. So VSLCFGTST's `setup` finds an existing free-text +param that's unset at SYS and round-trips (probe-set/read/restore; touches only +empty params, restores immediately). `BPS USRSCR` is one such on vehu. + +**Engine recipe also:** `--routines` is repeatable (stage v-stdlib `src` + +m-stdlib `src`). IRIS leg: `--engine iris --docker foia-t12 --namespace VISTA` +(XPAR lives in the VISTA namespace; the m-iris gbldir/routines equivalent is TBD). +All engine work goes through the driver stack — never raw `docker exec` (see shared +memory `engine-access-through-driver-stack`). + +Full detail: `docs/plans/t1.2-vslcfg-design.md`. diff --git a/docs/plans/t1.2-vslcfg-design.md b/docs/plans/t1.2-vslcfg-design.md new file mode 100644 index 0000000..ff6da6a --- /dev/null +++ b/docs/plans/t1.2-vslcfg-design.md @@ -0,0 +1,227 @@ +--- +title: "T1.2 — VSLCFG adapter: working design + engine findings" +status: in-progress +created: 2026-06-15 +doc_type: [PLAN] +plan: ../../../docs/vsl-msl/vsl-implementation-plan.md (T1.2 row) +kickoff: ../../../docs/prompts/t1.2-vslcfg-kickoff.md +--- + +# T1.2 — `VSLCFG` adapter (bind `STDENV` config-read seam → XPAR) + +Working design + the engine/citation groundwork established at session start. +Branch `t1.2-vslcfg`. This note captures the expensive-to-re-derive findings so +continuation doesn't repeat the discovery. + +## Established (session 1 groundwork) + +### Test execution goes through the DRIVER STACK, not raw exec (waterline rule 3) +The canonical path to run `VSL*TST` against the real VistA engines is the `m` +toolchain over the **m-ydb / m-iris drivers** (`m-driver-sdk` contract), exactly +the path v-stdlib's Makefile `test:` target uses — just pointed at the VistA +containers instead of the bare engines: +- **YDB-VistA:** `make test ENGINE=ydb DOCKER=vehu` (m-ydb → vehu, GT.M) +- **IRIS-VistA:** `make test ENGINE=iris DOCKER=foia-t12` (m-iris → foia-t12) +Both **proven green** (VSLSMOKETST 2/2 each) via +`m test --engine {ydb,iris} --docker {vehu,foia-t12} --routines $MSTDLIB/src …` +— the toolchain stages `^STDASSERT` from m-stdlib and returns the contract JSON +envelope. This honors the transport-monopoly rule (engine reached through the +SDK-contract drivers, not hand-rolled). The same `--docker` mechanism v-pkg / +VistaEngine use over `mdriver.Client` (t0a3 proved the driver path to BOTH +vehu and foia for the install lifecycle). + +**Raw `docker exec` (below) is EXPLORATION ONLY** — interactive XPAR/DD probing +and pinning the binding semantics. It is NOT the test or CI path; VSLCFGTST runs +through `m test … --docker`. + +### Engine — the YDB-VistA leg IS reachable from this sandbox +- **`docker exec vehu …` WORKS here** (contrary to the older "docker-exec + blocked / live run owed to user" notes — that was a prior sandbox state). + `vehu` = `worldvista/vehu`, **GT.M V7.0-005**, running, with live + Kernel/FileMan/**XPAR**. +- Run raw M on vehu: + `docker exec -i vehu bash -lc '. /home/vehu/etc/env; $gtm_dist/mumps -direct'` + (`$gtm_dist=/home/vehu/lib/gtm`). Always set `S DUZ=1,DUZ(0)="@",U="^",DT=$$DT^XLFDT` + first (full FM priv + the `U="^"` that several VistA APIs assume). +- `GET^XPAR` and `EN^XPAR` both exist on vehu; `$$DT^XLFDT` works. +- **GT.M direct-mode caveat:** argumentless-`DO` dot-blocks do NOT work when fed + line-by-line over a heredoc (each line parses independently → `Command + expected`). Use single-line `FOR`/compound lines, or stage a routine and call it. +- **IRIS-VistA leg is ALSO reachable** (no longer owed). Brought up + **`foia-t12`** — a disposable parallel container from the `foia:latest` image + with foia's data bind mounts (`~/data/foia-iris/{iris.cpf,mgr}`) and **no + published ports** (sidesteps the vehu 8001/9430 host-port conflict; accessed + via exec). The original `foia` container is left untouched (stopped); only one + IRIS runs against the `mgr` mount at a time. The shared `vista-iris` stays + OFF-LIMITS. + - Recreate: `docker run -d --name foia-t12 -v + /home/rafael/data/foia-iris/iris.cpf:/usr/irissys/iris.cpf -v + /home/rafael/data/foia-iris/mgr:/usr/irissys/mgr foia:latest` + - Run M in the VistA namespace: + `docker exec -i foia-t12 iris session IRIS -U VISTA` (then M commands on + stdin). **IRIS 2026.1 (Build 234U)**, namespace **VISTA**, `GET^XPAR`/ + `EN^XPAR` present, FileMan live (`$$DT^XLFDT` works). + - So **both engines are testable from this sandbox** → T1.2 can reach + "green both engines" without owing the IRIS leg. Develop YDB-green on vehu + first (fast loop), then parity-check on foia-t12. + +### XPAR binding semantics (probed live on vehu) +- `EN^XPAR(entity,param,instance,value,.err)` sets; `$$GET^XPAR(entity,param,instance)` + reads a specific entity; `$$GET^XPAR("ALL",param,instance)` walks the precedence + hierarchy. **Entity `"SYS"` is accepted** (the set reaches XPAR). +- Error codes seen (returned in `.err` as `code^text`): + - `89895001 Missing parameter definition` — the param name isn't in #8989.51. + - `89895005 Value failed validation logic` — entity/set path OK, but the + param's value-type/input-transform rejected the test value (e.g. `ANRV GUI + VERSION` is a constrained, multi-instance "Application:Version" param). +- **`$$ENT^XPAR` does NOT exist** (LABELMISSING) — entity resolution is internal; + pass the keyword (`"SYS"`) directly, don't call a resolver. + +### Test-parameter strategy (DECISION) +The VSLCFGTST needs a parameter that (a) exists in #8989.51 and (b) accepts a +free-text value at the **SYS** entity, set+read+cleaned-up with no residue. +Options: + 1. **Define a throwaway free-text PARAMETER DEFINITION in setUp** (full control, + clean isolation) — but creating a valid #8989.51 entry needs NAME + VALUE + DATA TYPE (free text) + an allowable-entity ("SYS") child; non-trivial. + 2. **Reuse an existing free-text single-instance param**, snapshot its SYS value, + set a test value, assert, restore. Needs finding one that exists on vehu + (XQAB ERROR RECIPIENTS does NOT exist here; ANRV GUI VERSION is constrained). + 3. **GT.M `TSTART ()` … `TROLLBACK`** around the set for isolation (proven to + leave no residue) — composes with either 1 or 2; or use m-stdlib **STDFIX** + (per-test transactional isolation) the same way the STD* suites do. +**Lean: option 1 + option 3** — define a `ZZVSLCFG TEST` free-text param inside a +transaction, set at SYS, `$$get` it back, assert, `TROLLBACK`. Matches the +walking-skeleton intent (T1.3 installs one PARAMETER DEFINITION) and keeps vehu +pristine. Pinning the minimal valid #8989.51-creation sequence is the next step. + +### Citation — RECONCILED (corrects the plan) +The plan's `@source XT/ktk7_3p26sp` is **wrong**. The XPAR / Parameter Tools +developer API is documented under **Kernel (XU)**, verified `is_latest=1` in the +vdocs gold corpus: +- **doc_key: `XU/krn_8_0_dg_toolkit_ug`** ("Kernel — Developer's Guide: Toolkit + User Guide", XU*8.0, 2025-08). `XT/ktk7_3tm` exists but has ZERO XPAR sections + ("being absorbed by Kernel TM"). +- Anchors: `$$GET^XPAR` → `#getxpar-return-an-instance-of-a-parameter`; + `ENVAL^XPAR` → `#envalxpar-return-all-parameter-instances`; + `EN^XPAR` → `#enxpar-add-change-delete-parameters`. +- **ICR / DBIA: #2263, Reference Type = Supported**, custodian **XU** (NOT the + plan's `XT*7.3*26`). Value precedence defined by PARAMETER DEFINITION file + **#8989.51**. + +So the `@icr` tag on VSLCFG's `$$GET^XPAR` call site is: +`; doc: @icr 2263 @call $$GET^XPAR @status Supported @custodian XU @source XU/krn_8_0_dg_toolkit_ug#getxpar-return-an-instance-of-a-parameter` + +## VSLCFG API shape (intended) +Adapter exposes the same config-read SHAPE as `$$get^STDENV` but binds XPAR +(contains ONLY the VistA binding; no parsing/formatting — that stays in STD*): +- `$$get^VSLCFG(key,default)` — read `key` through the XPAR precedence hierarchy + (`$$GET^XPAR("ALL",key,1)`), return value or `default` when unset. +- `$$set^VSLCFG(key,value)` — set `key` at the SYS entity (`EN^XPAR("SYS",…)`), + VSLCFG-specific (STDENV is read-only; not part of the pinned seam). +Boundary ① (`check-msl-pin`) only asserts the PIN == MSL@v0.7.0 — the +signature-match to STDENV is the manual contract rule, not the automated gate. + +## Driver → live XPAR: SOLVED (the keystone unblock, 2026-06-16) + +The m-ydb docker transport reaches vehu but, by default, with **no global +directory** ($ZGBLDIR empty) — so VistA data globals (XPAR `^XTV`, `^DD`, `^VA`) +are invisible (VSLSMOKETST passed only because it uses staged routines + scratch +globals). Fix = set the driver's gbldir + routines via env (the same knobs +`v pkg` used on vehu; see m-ydb memory `m-ydb-docker-gbldir`): + +``` +export M_YDB_CONTAINER=vehu +export M_YDB_GBLDIR=/home/vehu/g/vehu.gld +export M_YDB_ROUTINES='/home/vehu/p/r2.02_x86_64*(/home/vehu/p) /home/vehu/s/r2.02_x86_64*(/home/vehu/s) /home/vehu/r/r2.02_x86_64*(/home/vehu/r) /home/vehu/lib/gtm/libgtmutil.so' +``` + +With these set: +- Ad-hoc M through the driver: `m vista exec --engine ydb --transport docker ''` + — globals visible (`^VA(200,0)` exists; 1211 XPAR defs), `$$GET^XPAR` resolves. +- Suites: `m test --engine ydb --docker vehu --chset m --routines src + --routines /src tests/VSLCFGTST.m`. `--routines` is **repeatable** — + stage BOTH v-stdlib `src` (VSLCFG) and m-stdlib `src` (STDASSERT). The staged + dirs are appended to $ZROUTINES (base = `M_YDB_ROUTINES` = vehu's VistA routines). +- IRIS (foia-t12): `--engine iris --docker foia-t12 --namespace VISTA` (XPAR lives + in the VISTA namespace, not the default USER). The IRIS gbldir/routines knobs + are TBD (verify the m-iris equivalent of M_YDB_GBLDIR). + +**Proven:** a minimal harness probe (`eq(1,1)` + `eq($$get^VSLCFG("x","d"),"d")`) +runs **2/2 green** via the driver on vehu — harness + VSLCFG staging + the stub +adapter all work end-to-end through the stack. + +## Test fixture: OPEN (the next concrete step) + +The full suite currently aborts **0/0** because the fixture faults. A **minimal +hand-built `#8989.51` def** (0-node + node 6 value-type + `B` xref, via direct +global SETs) is NOT enough for `EN^XPAR` to FILE a value — it returns no error but +stores nothing (`$$GET^XPAR` reads back empty). XPAR's filer needs a properly +FileMan-created definition (the value-multiple subfield structure). Options: +1. Create the def via **FileMan** in setup (`FILE^DIE`/`UPDATE^DIC` into #8989.51 + with the value field), value-set with `EN^XPAR`, assert, delete in teardown. +2. Seed an **existing** free-text SYS-settable param (save prior value → set → + assert → restore). Needs one confirmed present on BOTH vehu and foia. +3. KIDS-install the def (this is what T1.3 does) — heavier for a unit test. +Lean: option 1. `m vista exec` runs one command (no dot-blocks over a heredoc), +so iterate the FileMan-create logic *inside* the staged test via `m test`, where +multi-line M + the STDASSERT report give a real dev loop. + +## Status (2026-06-16): DONE — VSLCFGTST GREEN 3/3 on BOTH engines + +The harness blocker (below) was **FIXED in m-cli** (`docker-routines-base`, +`d9ee76a`): `DockerRunner` now falls back to `${ydb_routines:-$gtmroutines}` so a +GT.M VistA's resident routines (XPAR) stay on the path. `VSLCFGTST` went +**0/0 → 3/3 GREEN** on `vehu` (YDB) and `foia-t12` (IRIS `--namespace VISTA`) — +no `M_YDB_*` host vars needed. T1.2 is complete (engine suite + all three +boundaries green). The blocker write-up below is retained for the record. + +## Status (historical): adapter DONE + gated; engine suite blocked on a harness gap + +- **VSLCFG implemented + validated against live XPAR.** `$$get^VSLCFG(key,default)` + = `$$GET^XPAR("SYS",key,1)` (SYS-entity read — the faithful analog of STDENV's + flat key→value config read; note `$$GET^XPAR("ALL",…)` precedence did NOT return + a SYS value for the probed param, so bind SYS directly). `$$set^VSLCFG(key,value)` + = `EN^XPAR("SYS",key,1,value)`. Round-trip + restore proven via `m vista exec` + on vehu (set→`sysget=[hello]`→restore→empty). +- **All three determinism-ledger boundaries GREEN (engine-free):** + - ① `check-msl-pin`: re-pinned `msl_ref` v0.6.0→**v0.7.0**; pin carries the real + `seams.STDENV` and matches MSL@v0.7.0. + - ② `check-icr`: VSLCFG's `$$GET^XPAR`/`EN^XPAR` call sites declared with + **ICR #2263 (Supported, custodian XU)**. + - ③ `check-citations`: both `@source XU/krn_8_0_dg_toolkit_ug#…` anchors verified + against the vdocs gold corpus. + - plus check-seams / check-namespaces / check-engine-access + fmt/lint/arch. +- **VSLCFGTST ready** (finder fixture + 2 assertions), but the **engine-bound run + is BLOCKED by a toolchain gap**: `m test --docker vehu` honors `M_YDB_GBLDIR` + (globals visible) but NOT `M_YDB_ROUTINES` — it stages routines into a clean + `$ZROUTINES` that drops vehu's resident VistA routines, so `$$GET^XPAR`/`EN^XPAR` + don't resolve and the suite aborts 0/0. The driver's *exec* path (used by + `m vista exec` / `v pkg`) DOES layer the routine base; `m test`'s staging path + does not. + +## THE remaining blocker — `m test` must include the resident routine base + +To run any VistA-dependent `VSL*TST` suite, the harness must layer the engine's +resident routines under the staged ones (the exact analog of the m-ydb +`$ZGBLDIR` fix — see m-ydb memory `m-ydb-docker-gbldir`). Two ways: +1. **Toolchain fix (preferred):** make `m test`/the m-ydb driver test path set + `$ZROUTINES = ` (small, mirrors the gbldir fix). + Belongs in **m-cli/m-ydb** (separate session, leaf-first). Unblocks ALL + VistA-dependent VSL testing, not just VSLCFG. +2. **Test-in-place** via `m test --resident` (RUN^STDHARN) against KIDS-installed + routines (the §12.1 "test-in-place" model; heavier, what T1.3 sets up). +Once either lands, VSLCFGTST goes red→green unchanged (set/get round-trip). + +## Remaining steps (TDD-red-first) +1. Pin the minimal valid #8989.51 free-text param creation on vehu (setUp). +2. `tests/VSLCFGTST.m` red-first (^STDASSERT): seed param → `$$set^VSLCFG` at SYS + → `$$get^VSLCFG` back → assert; confirm RED (VSLCFG absent), then GREEN on vehu. +3. `src/VSLCFG.m` with the `@icr`/`@source` tags above. +4. Regenerate dist/ (`make icr seams namespaces`); `make gates` green + (check-icr, check-citations against the real corpus, check-namespaces). +5. **Re-pin** `dist/msl-seam-pin.json` `msl_ref` `v0.6.0`→`v0.7.0`; `make pin`; + `make check-msl-pin` green against the real STDENV signature. +6. `make check-fast` + `m arch check .` green; VSLCFGTST green on **both** + vehu (YDB) and foia-t12 (IRIS); golden value byte-identical across engines. +7. Increment protocol: memory `docs/memory/t1.2-vslcfg.md`, tracker row, commit/push. diff --git a/src/VSLCFG.m b/src/VSLCFG.m new file mode 100644 index 0000000..f153690 --- /dev/null +++ b/src/VSLCFG.m @@ -0,0 +1,32 @@ +VSLCFG ; v-stdlib — VistA configuration adapter over XPAR (Parameter Tools). + ; + ; Binds the MSL config-read seam ($$get^STDENV) to VistA's XPAR parameter + ; store at the SYS (system) entity — the faithful analog of STDENV's flat + ; key->value config read. The adapter contains ONLY the VistA binding; no + ; parsing or formatting (that stays in STD*, called up; m/v waterline). + ; + ; Public extrinsics: + ; $$get^VSLCFG(key,default) — read a SYS-level parameter value, else default + ; $$set^VSLCFG(key,value) — set a parameter value at the SYS entity + ; + ; XPAR is a Supported API (Kernel Toolkit, ICR #2263). + ; + quit + ; +get(key,default) ; Read parameter `key` at the SYS entity; return `default` when unset. + ; doc: @param key string XPAR parameter name (PARAMETER DEFINITION #8989.51) + ; doc: @param default string value returned when the parameter is unset + ; doc: @returns string the SYS-level value, or `default` when unset + ; doc: @example set greeting=$$get^VSLCFG("VPNG GREETING","hello") + ; doc: @icr 2263 @call $$GET^XPAR @status Supported @custodian XU @source XU/krn_8_0_dg_toolkit_ug#getxpar-return-an-instance-of-a-parameter + new v + set v=$$GET^XPAR("SYS",key,1) + quit $select(v="":default,1:v) + ; +set(key,value) ; Set parameter `key` to `value` at the SYS entity. + ; doc: @param key string XPAR parameter name (#8989.51) + ; doc: @param value string value to store at the SYS level + ; doc: @returns void side-effecting; no return value + ; doc: @icr 2263 @call EN^XPAR @status Supported @custodian XU @source XU/krn_8_0_dg_toolkit_ug#enxpar-add-change-delete-parameters + do EN^XPAR("SYS",key,1,value) + quit diff --git a/tests/VSLCFGTST.m b/tests/VSLCFGTST.m new file mode 100644 index 0000000..2e7f2ca --- /dev/null +++ b/tests/VSLCFGTST.m @@ -0,0 +1,66 @@ +VSLCFGTST ; v-stdlib — VSLCFG (XPAR config adapter) test suite. + ; Exercises VSLCFG against a live VistA's XPAR (Parameter Tools). GREEN 3/3 on + ; BOTH engines via the driver stack (m/v waterline — the ONLY path): + ; m test --engine ydb --docker vehu --chset m \ + ; --routines src --routines /src tests/VSLCFGTST.m + ; m test --engine iris --docker foia-t12 --namespace VISTA \ + ; --routines src --routines /src tests/VSLCFGTST.m + ; (No M_YDB_* host vars needed — the container's `bash -l` env supplies + ; gtmgbldir + gtmroutines once m-cli's DockerEngine layers the resident routine + ; base; see m-cli memory `docker-routines-gtmroutines-fallback`.) + new pass,fail + do start^STDASSERT(.pass,.fail) + ; + do tSetGetSysPrecedence(.pass,.fail) + do tGetDefaultWhenUnset(.pass,.fail) + ; + do report^STDASSERT(pass,fail) + quit + ; +tSetGetSysPrecedence(pass,fail) ;@TEST "$$set then $$get round-trips a SYS-level value through XPAR precedence" + new key + do setup(.key) + do true^STDASSERT(.pass,.fail,key'="","a usable XPAR parameter was found") + quit:key="" + do set^VSLCFG(key,"hello") + do eq^STDASSERT(.pass,.fail,$$get^VSLCFG(key,"MISS"),"hello","SYS precedence read") + do teardown(key) + quit + ; +tGetDefaultWhenUnset(pass,fail) ;@TEST "$$get returns the default for a parameter with no value" + new key + do setup(.key) + quit:key="" + do eq^STDASSERT(.pass,.fail,$$get^VSLCFG(key,"fallback"),"fallback","unset returns default") + do teardown(key) + quit + ; + ; ---------- fixtures ---------- + ; +setup(key) ; Find a free-text, SYS-settable XPAR parameter currently unset at SYS. + ; Probes empty free-text params (set a sentinel, read it back, restore) and + ; returns the first whose SYS set round-trips. Touches only already-empty + ; params and restores each immediately, so no real config is changed. + new n,i + set DUZ=1,DUZ(0)="@",U="^",DT=$$DT^XLFDT,key="" + set n=$order(^XTV(8989.51,"B","")) + for quit:n=""!(key'="") do + . set i=+$order(^XTV(8989.51,"B",n,0)) + . if i,$extract($get(^XTV(8989.51,i,6)))="F",$$GET^XPAR("SYS",n,1)="" do try(n,.key) + . set n=$order(^XTV(8989.51,"B",n)) + quit + ; +try(n,key) ; Sentinel set/read/restore on one candidate; set key on round-trip. + new r + do EN^XPAR("SYS",n,1,"ZZVSLCFGPROBE",.r) + set r=$$GET^XPAR("SYS",n,1) + do EN^XPAR("SYS",n,1,"@") + set:r="ZZVSLCFGPROBE" key=n + quit + ; +teardown(key) ; Restore the chosen parameter's SYS value to unset. + new err + quit:key="" + set DUZ=1,DUZ(0)="@",U="^" + do EN^XPAR("SYS",key,1,"@",.err) + quit diff --git a/tools/check_engine_access.py b/tools/check_engine_access.py new file mode 100644 index 0000000..5123711 --- /dev/null +++ b/tools/check_engine_access.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 +"""Engine-access gate — the committed-artifact backstop for the transport monopoly. + +Spec: org CLAUDE.md §"m/v waterline" → "Engine access (dev/test/CI/agent)". + +All work against a live M engine MUST go through the m-driver-sdk → m-ydb/m-iris +stack via the `m` toolchain (`m test --docker `, `m coverage`, `m vista exec`, +or `mdriver.Client`). This gate red-gates any committed *executable* artifact +(Makefile, tests/, scripts/, *.sh) that hand-rolls engine access instead — +`docker exec … mumps|iris session`, a bare `mumps -direct`, `$gtm_dist/mumps`, +`csession`, etc. (It is the CI counterpart to the real-time PreToolUse hook +`~/scripts/lib/engine-stack-guard.sh`.) + +Docs are NOT scanned — design notes legitimately quote the forbidden recipe as +"exploration only". A deliberate, unavoidable exception carries a `stack-exempt` +marker on the offending line. + +Usage: + python3 tools/check_engine_access.py --check # the CI gate + python3 tools/check_engine_access.py --self-test # pure-logic unit tests +""" + +from __future__ import annotations + +import argparse +import re +import sys +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parent.parent + +# Executable artifacts that could reach an engine. Docs/ deliberately excluded. +SCAN_FILES = ["Makefile"] +SCAN_DIRS = ["tests", "scripts"] +SCAN_GLOBS = ["*.sh"] + +# Hand-rolled engine access = a sidestep of the driver stack. +FORBIDDEN = [ + re.compile(r"docker\s+(?:-\S+\s+)*exec\b"), # docker exec into a container + re.compile(r"\biris\s+session\b"), # IRIS M shell + re.compile(r"\bcsession\b"), # legacy Caché shell + re.compile(r"\bmumps\s+-(?:direct|dir|r)\b"), # GT.M/YDB direct/run + re.compile(r"\$gtm_dist\b"), # raw GT.M dist invocation + re.compile(r"\$ydb_dist/(?:yottadb|mumps)\b"), # raw YDB dist invocation +] + +ALLOW_MARKER = "stack-exempt" + + +def scan_text(text: str) -> list[tuple[int, str]]: + """Return (lineno, line) for each offending line (1-based). Pure function.""" + hits: list[tuple[int, str]] = [] + for i, line in enumerate(text.splitlines(), 1): + if ALLOW_MARKER in line: + continue + if any(rx.search(line) for rx in FORBIDDEN): + hits.append((i, line.strip())) + return hits + + +def _targets() -> list[Path]: + out: list[Path] = [] + for f in SCAN_FILES: + p = REPO_ROOT / f + if p.is_file(): + out.append(p) + for d in SCAN_DIRS: + base = REPO_ROOT / d + if base.is_dir(): + out.extend(p for p in base.rglob("*") if p.is_file()) + for g in SCAN_GLOBS: + out.extend(REPO_ROOT.glob(g)) + # de-dup, stable + seen, uniq = set(), [] + for p in out: + if p not in seen: + seen.add(p) + uniq.append(p) + return uniq + + +def check() -> int: + violations: list[str] = [] + for path in _targets(): + try: + text = path.read_text(encoding="utf-8", errors="replace") + except OSError: + continue + for lineno, line in scan_text(text): + violations.append(f"{path.relative_to(REPO_ROOT)}:{lineno}: {line}") + if violations: + print("ERROR: hand-rolled engine access (sidesteps the m-driver-sdk stack):", + file=sys.stderr) + for v in violations: + print(f" - {v}", file=sys.stderr) + print("Use `m test --docker ` / `m vista exec` / `mdriver.Client` instead " + "(org CLAUDE.md §waterline). A deliberate one-off may carry a " + "`stack-exempt` marker on the line.", file=sys.stderr) + return 1 + print(f"check-engine-access: clean ({len(_targets())} files scanned)") + return 0 + + +def self_test() -> int: + fails: list[str] = [] + + def expect(cond, msg): + if not cond: + fails.append(msg) + + expect(scan_text("docker exec -i vehu bash -lc 'mumps -direct'"), + "raw docker exec should be flagged") + expect(scan_text("\tdocker exec foia-t12 iris session IRIS"), + "iris session should be flagged") + expect(scan_text("X=$gtm_dist/mumps"), "$gtm_dist should be flagged") + expect(not scan_text("\t$(M) test --engine ydb --docker vehu $(TESTS)"), + "approved `m test --docker` must NOT be flagged") + expect(not scan_text("docker ps --filter name=vehu"), + "docker ps must NOT be flagged") + expect(not scan_text("docker run -d --name foia-t12 foia:latest"), + "docker run (lifecycle) must NOT be flagged") + expect(not scan_text("docker exec vehu echo ok # stack-exempt: probe"), + "stack-exempt marker must allow the line") + + if fails: + for f in fails: + print(f"FAIL: {f}", file=sys.stderr) + return 1 + print("check_engine_access self-test OK") + return 0 + + +def main(argv: list[str]) -> int: + p = argparse.ArgumentParser(description="Engine-access transport-monopoly gate.") + g = p.add_mutually_exclusive_group(required=True) + g.add_argument("--check", action="store_true", help="Run the CI gate.") + g.add_argument("--self-test", action="store_true", help="Run the pure-logic self-test.") + args = p.parse_args(argv) + return self_test() if args.self_test else check() + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:]))