diff --git a/dist/stdlib-manifest.json b/dist/stdlib-manifest.json index 662765f..24e1708 100644 --- a/dist/stdlib-manifest.json +++ b/dist/stdlib-manifest.json @@ -4812,7 +4812,7 @@ }, "STDFIX": { "synopsis": "m-stdlib — fixture lifecycle and per-test isolation.", - "description": "YDB nested-transaction-based isolation. YDB enforces TPQUIT:\n``tstart`` and the matching ``trollback`` MUST live in the same\nroutine frame. STDFIX therefore exposes only one-shot wrappers —\n``with`` and ``invoke`` open AND close their scope before\nreturning. There is no standalone setup() / teardown() pair.\n\nPublic labels:\n with(tag,code) ; XECUTEs code inside an auto-managed\n transaction scope; rolls back on exit\n and re-raises any error code raised\n inside ``code``.\n $$active() ; → 1 if any nested transaction is open\n (any TSTART, not just STDFIX-owned).\n register(tag,setupCode,teardownCode) ; declarative fixture\n invoke(tag,code) ; fixture-aware variant of with(): runs\n registered setup hook, then code, then\n registered teardown hook — all inside\n one rolled-back scope.\n cleanup ; idempotent rollback of any leaked\n transaction scope; safe at $tlevel=0.\n\nState layout (under ^STDLIB($job,\"FIX\",...)):\n STACK,$tlevel = tag ; one entry per open scope, set inside\n the transaction so TROLLBACK erases it.\n REG,tag,\"SETUP\" ; registered setup code\n REG,tag,\"TEARDOWN\" ; registered teardown code\n\n``trollback $tlevel-1`` rolls back exactly the level this frame\nopened, so nested with()/invoke() pairs roll back inner-only —\nbare ``trollback`` (which targets level 0) would unbalance every\nouter transaction.\n\nErrors set $ECODE to one of:\n ,U-STDFIX-EMPTY-TAG,\n ,U-STDFIX-UNREGISTERED-TAG,", + "description": "YDB nested-transaction-based isolation. YDB enforces TPQUIT:\n``tstart`` and the matching ``trollback`` MUST live in the same\nroutine frame. STDFIX therefore exposes only one-shot wrappers —\n``with`` and ``invoke`` open AND close their scope before\nreturning. There is no standalone setup() / teardown() pair.\n\nPublic labels:\n with(tag,code) ; XECUTEs code inside an auto-managed\n transaction scope; rolls back on exit\n and re-raises any error code raised\n inside ``code``.\n $$active() ; → 1 if any nested transaction is open\n (any TSTART, not just STDFIX-owned).\n register(tag,setupCode,teardownCode) ; declarative fixture\n invoke(tag,code) ; fixture-aware variant of with(): runs\n registered setup hook, then code, then\n registered teardown hook — all inside\n one rolled-back scope.\n cleanup ; idempotent rollback of any leaked\n transaction scope; safe at $tlevel=0.\n\nState layout (under ^STDLIB($job,\"FIX\",...)):\n STACK,$tlevel = tag ; one entry per open scope, set inside\n the transaction so TROLLBACK erases it.\n REG,tag,\"SETUP\" ; registered setup code\n REG,tag,\"TEARDOWN\" ; registered teardown code\n\n``trollback target`` (target = the pre-tstart $tlevel) rolls back\nexactly the level this frame opened, so nested with()/invoke() pairs\nroll back inner-only — bare ``trollback`` (which targets level 0) would\nunbalance every outer transaction.\n\nEngine portability (YDB + IRIS): YDB ``trollback n`` rolls back TO level\nn; IRIS ``trollback n`` rolls back n LEVELS (opposite meaning), so the\nrollback is engine-split — IRIS uses ``trollback $tlevel-target``. Full\npartial-rollback fidelity holds on both (nested inner-only rollback works\non IRIS too). REQUIREMENT: IRIS needs the namespace's database to be\nJOURNALED for ``TSTART`` (an unjournaled db faults ````);\nSTDFIX is therefore usable only on a journaled IRIS namespace. (TSTART is\nalso forbidden lexically inside an IRIS ``try{}``/``xecute``, but STDFIX's\nown tstart is a direct command, so this never bites here.)\n\nErrors set $ECODE to one of:\n ,U-STDFIX-EMPTY-TAG,\n ,U-STDFIX-UNREGISTERED-TAG,", "errors": [ "U-STDFIX-EMPTY-TAG", "U-STDFIX-UNREGISTERED-TAG" @@ -4858,7 +4858,7 @@ "description": "Opens a YDB transaction, sets the scope tag in the stack,\nXECUTEs code, then ``trollback $tlevel-1`` to roll back\nexactly this scope. If code raises, the trap rolls back the\nscope and re-raises so the caller can observe the original\n$ECODE.", "source": { "file": "src/STDFIX.m", - "line": 43 + "line": 53 } }, "active": { @@ -4885,7 +4885,7 @@ "description": "STDFIX-managed scopes also appear in ^STDLIB($job,\"FIX\",\"STACK\",N)\nfor callers that need to distinguish their own from foreign transactions.", "source": { "file": "src/STDFIX.m", - "line": 68 + "line": 84 } }, "register": { @@ -4931,7 +4931,7 @@ "description": "", "source": { "file": "src/STDFIX.m", - "line": 78 + "line": 94 } }, "invoke": { @@ -4973,7 +4973,7 @@ "description": "Looks up registered setup/teardown for tag, then runs setup\n(if any), code, teardown (if any) — all inside one transaction\nthat rolls back on exit.", "source": { "file": "src/STDFIX.m", - "line": 92 + "line": 108 } }, "cleanup": { @@ -4997,7 +4997,7 @@ "description": "TROLLBACKs all the way to $tlevel=0 if any transaction is\nopen; safe to call at $tlevel=0 (no-op). Useful as a\ndefensive between-tests reset in runner code. Note: this\nrolls back NON-STDFIX transactions too — call only at a\ntop-level frame that owns no enclosing tstart.", "source": { "file": "src/STDFIX.m", - "line": 121 + "line": 141 } } }, diff --git a/docs/tracking/discoveries.md b/docs/tracking/discoveries.md index 8f68d7b..de0de2f 100644 --- a/docs/tracking/discoveries.md +++ b/docs/tracking/discoveries.md @@ -65,8 +65,8 @@ requires "no open P0/P1 entries against those subjects." | 2026-06-12 | P2 | m-stdlib | STDUUID / STDUUIDTST (IRIS) | **One STDUUIDTST assertion fails on IRIS** (`##END-HARNESS suites=1 pass=130 fail=1`) while it is green on YDB — an as-yet-uncharacterised IRIS behavior difference in STDUUID (likely a byte/charset or `$ZF`/`$R` assumption). Surfaced on the T0b.2 IRIS leg. | **m-stdlib:** characterise + fix (run `STDUUIDTST` on IRIS, find the failing label). Lower priority than the `raises` crash. | **resolved 2026-06-12 (s9)** — STDUUIDTST now **131/131 on IRIS** (m-test-iris, isolated run) after the `raises` port. The "1 fail" was a **side-effect of the `raises` abort corrupting the harness frame**, not a real STDUUID bug (no STDUUID source change). If it recurs on the foia test-in-place leg, re-characterise there. | | 2026-06-12 | P2 | m-stdlib / m-iris | IRIS test-in-place: remaining 0/0 suites are **non-`raises`** | After the `raises` port (P1 above), the suites still crashing 0/0 on IRIS (m-test-iris CE 2026.1) split into two non-raises causes, confirmed by `grep -c 'raises^STDASSERT'` = **0** on the first three: **(a) wide-char output** — STDJSONTST/STDXMLTST/STDCSVTST (and STDURLTST/STDREGEXTST per the m-iris row above) emit non-ASCII PASS-line text, hitting the m-iris `GetOut` `` fault; **(b) file-OPEN portability** — STDSEEDTST crashes at its **first** test (`tParseEmptyManifest`, no raises) on `open path:(newversion):0` (IRIS deviceparam/temp-file open differs from YDB), reached before its lone `raises` device-context test; STDCSVTST also does file I/O. | (a) → the **m-iris `GetOut` wide-char fix** (P1 row above, m-iris lane). (b) → **m-stdlib**: make STDSEED/STDCSV's test-fixture `open` IRIS-portable (deviceparams / temp path), or guard the suites' file I/O per engine. Both are independent of the (now-closed) `raises` crash. | **(b) resolved 2026-06-13 (s10)** via the STDFS portable file-I/O facade (next row); **(a) still open** (m-iris GetOut wide-char — but see s10 row: the m-iris fix landed for the REMOTE transport; `m test --docker` uses the SESSION transport, a separate path). | | 2026-06-13 | P1 | m-stdlib | File I/O is YDB-only across STDFS + 5 consumers (`open …:(readonly/newversion/append)`, `for read line quit:$zeof`) | **All SEQ-device file I/O used YDB deviceparam mnemonics + `$ZEOF`, which IRIS doesn't share** — STDFS was explicitly "YDB-only by design", and STDJSON/STDCSV/STDSEED/STDLOG each hand-rolled the same YDB `open`/read-loop. IRIS needs mode strings (`readonly→"R"`, `newversion:stream:nowrap→"WNS"`, `append→"WA"`, `close:(delete)→close:"D"`) and **catches `` where YDB sets `$ZEOF`**. Also `$$env^STDOS` used `$ztrnlnm` (YDB intrinsic → crash on IRIS), blocking STDFS's callout-gating. Surfaced finishing the T0b.2 IRIS leg (Variant B). | **Resolved (file I/O) 2026-06-13 (s10):** added a portable facade to **STDFS** — public `$$openRead/$$openWrite/$$openAppend` (engine-branched, `xecute`-hide the off-engine syntax so each compiler only parses its own — same trick as STDHARN's ZGOTO-in-`$etrap`) + private `readLn` (portable EOF) / `closeDelete` / `sizeIris` (`%File.GetFileSize`); writeFile finalises the last record with LF on IRIS to match YDB's stream-close (byte-identical on-disk size). **STDOS.env** got a `$system.Util.GetEnviron` IRIS arm. The 5 consumers now route through STDFS (`readFile`/`readLines`/`writeFile`/`writeLines`/`openAppend`); test fixtures (STDSEEDTST/STDCSVTST) too. **STDFSTST 50/50 BOTH engines; YDB full 2098/0 (no regression).** Gotchas hit: `'$zversion["IRIS"` parses as `('$zversion)["IRIS"` (unary `'` precedence) → use the positive `if $zversion["IRIS"` form; readonly-OPEN of a MISSING file *raises* DEVOPENFAIL on YDB (timeout doesn't catch it) so `openRead` keeps a ZGOTO trap; STDCSPRNG's binary `/dev/urandom` read keeps `(readonly:nowrap)` (NOT migrated — `nowrap` is load-bearing for `read *b`, dropping it 0/0'd YDB). | **resolved (file I/O) 2026-06-13** | -| 2026-06-13 | P2 | m-stdlib | Library-wide dual-engine sweep (post-T0b.2): 4 non-base modules still YDB-only | Ran the full pure-M stack on BOTH engines after T0b.2. **YDB (m-test-engine): 33 suites 2043/0** (the callout suites STDCSPRNG/STDCRYPTO/STDCOMPRESS/STDHTTP need their `.so`/byte-mode — the documented optional tier). **IRIS (foia/remote via `run^STDHARN`): 25 of 29 suites GREEN** — the base-17 plus **6 non-base modules already IRIS-clean** (STDCACHE 48/48, STDENV 46/46, STDLOG 62/62, STDSEMVER 99/99, STDSNAP 30/30, STDXFRM 38/38) + STDASSERT/STDHARN infra. **4 non-base modules still crash on IRIS**, each a class already solved in the base: **STDSEED** + **STDMOCK** = `do @callback@(args)` argument indirection (→ the STDCSV `xecute`-dispatch fix); **STDFIX** = `set $etrap` transaction/fixture unwind (→ the STDASSERT.raises `$ZVERSION[IRIS` try/catch fix); **STDPROF** = `$ZHOROLOG` timing (→ the STDDATE/STDUUID `$ZTIMESTAMP` arm). | **RESOLVED 2026-06-13 (resume session): 3 of 4 fixed; STDFIX is runner-blocked, not a code defect.** STDPROF (`$ZTIMESTAMP` arm + an undef-safe min/max — M has no boolean short-circuit, so `'$data(x)!(e` on IRIS-strict), STDMOCK + STDSEED (`xecute`-built dispatch) all dual-engine green. **STDFIX needs NO code change** — its `set $etrap` was a red herring: IRIS fully supports transactions incl. partial `trollback N` (inner-only rollback) when run *directly*; STDFIX is IRIS-correct. It only fails because the **m-iris runner** executes code inside `try{}` and IRIS forbids `TSTART` there (see the m-iris row below). So 28/29 non-callout suites are dual-engine green; STDFIX is IRIS-ready but unverifiable via the toolchain until the m-iris runner is fixed. | **partly closed** — STDPROF/STDMOCK/STDSEED done; STDFIX → m-iris runner follow-up | -| 2026-06-13 | P2 | m-iris | runner wraps executed code in `try{}` → IRIS forbids `TSTART` there → blocks all transaction code (e.g. STDFIX) | `m.iris.Runner.cls` `RunRef` does `try { do @ref }` and `Eval` does `try { xecute cmd }` (error capture). IRIS prohibits `TSTART` inside a `try{}` block (``), so ANY M code doing `tstart` through the m-iris toolchain's IRIS exec path (`m test --docker=m-test-iris`, `$DRV exec eval/run`, STDHARN-via-runner) faults — even though IRIS transactions + partial rollback work perfectly when run *directly* (`iris session`). Surfaced finishing the library-wide IRIS pass: STDFIX (the only `tstart` module) can't be verified on IRIS via the runner, though its code is IRIS-correct. (Earlier mis-attributed to "IRIS has no transactions / enable journaling" — wrong; journaling is on, transactions work directly.) | **m-iris lane (filed):** the runner must capture errors with `$ETRAP`/`$ZTRAP` instead of `try/catch` so executed code may use `TSTART`/`TROLLBACK`. Until then STDFIX (and any transaction-using M) is unrunnable on IRIS via the toolchain. Filed: **vista-cloud-dev/m-iris#3**. | open (m-iris#3) | +| 2026-06-13 | P2 | m-stdlib | Library-wide dual-engine sweep (post-T0b.2): 4 non-base modules still YDB-only | Ran the full pure-M stack on BOTH engines after T0b.2. **YDB (m-test-engine): 33 suites 2043/0** (the callout suites STDCSPRNG/STDCRYPTO/STDCOMPRESS/STDHTTP need their `.so`/byte-mode — the documented optional tier). **IRIS (foia/remote via `run^STDHARN`): 25 of 29 suites GREEN** — the base-17 plus **6 non-base modules already IRIS-clean** (STDCACHE 48/48, STDENV 46/46, STDLOG 62/62, STDSEMVER 99/99, STDSNAP 30/30, STDXFRM 38/38) + STDASSERT/STDHARN infra. **4 non-base modules still crash on IRIS**, each a class already solved in the base: **STDSEED** + **STDMOCK** = `do @callback@(args)` argument indirection (→ the STDCSV `xecute`-dispatch fix); **STDFIX** = `set $etrap` transaction/fixture unwind (→ the STDASSERT.raises `$ZVERSION[IRIS` try/catch fix); **STDPROF** = `$ZHOROLOG` timing (→ the STDDATE/STDUUID `$ZTIMESTAMP` arm). | **CLOSED 2026-06-13 (resume session): all 4 fixed.** STDPROF (`$ZTIMESTAMP` arm + an undef-safe min/max — M has no boolean short-circuit, so `'$data(x)!(e` on IRIS-strict), STDMOCK + STDSEED (`xecute`-built dispatch — IRIS has no argument indirection), **STDFIX** (the real bug: YDB `trollback N` rolls back TO level n, IRIS rolls back n LEVELS — opposite semantics; `trollback target` → `trollback 0` `` on IRIS; engine-split to `trollback $tlevel-target`). All dual-engine green; STDFIX keeps full partial-rollback fidelity (nesting tests pass unchanged). **NB the earlier "runner-blocked / no IRIS transactions / enable journaling" reads were ALL wrong** — see the corrected m-iris row. **Caveat:** STDFIX needs a JOURNALED IRIS namespace (IRIS requires journaling for `TSTART`); verified on m-test-iris (USER, journaled); foia's VISTA *data* db is unjournaled so STDFIX's transactions are `` there until journaling is enabled on that db — a deployment-config detail, not a code issue. | **closed (m-stdlib)** — all 4 dual-engine green on a journaled IRIS | +| 2026-06-13 | P3 | m-iris | (CORRECTED) the runner does NOT block transactions — `TSTART` is forbidden only *lexically* in try{}/xecute | Earlier mis-diagnosed (and filed as m-iris#3, since **closed as invalid**): the `try { do @ref }` in `RunRef` does NOT block `TSTART`. IRIS forbids `TSTART` only when it appears *lexically inside* a `try{}` or an `xecute`'d string — **not** when reached through a `do`-chain. Proven: `$$f()` where `f` does `tstart`, called from inside `try{ … }`, works (tl=1). So STDHARN-run suites (`try { do RUN^STDHARN }` → … → `do with^STDFIX` → `tstart`) are fine; STDFIXTST is **28/28 via the runner** after the STDFIX `trollback`-semantics fix above. The whole "runner / journaling" investigation was a red herring — the only real bug was STDFIX's YDB-vs-IRIS `TROLLBACK n` semantics. Minor true caveat: `m-iris exec eval 'tstart …'` (the Eval path `xecute cmd`) still can't `tstart` because the cmd is `xecute`'d — but that's a rare one-line-eval path, not how suites run. | **no m-iris change needed.** Documented so the next reader doesn't re-chase it. | resolved (no-op) | | 2026-06-13 | P3 | m-iris | docker/session transport truncates large output frames | While certifying T0b.2 on IRIS: the m-iris **docker** transport (`iris session` stdout-marker capture, used by `m test --docker=m-test-iris`) returns only a partial frame for large suites — STDJSONTST came back with 84 of 209 assertions, STDOSTST/STDUUID also truncated — whereas the **remote/Atelier** transport (GetOut, used against foia) returns the full frame (209/209). So per-suite IRIS validation during this work used foia/remote as ground truth and treated `--docker` only for quick compile/error probing. The earlier m-iris GetOut wide-char fix (`49a5b00`) is on the remote path; the docker session-capture path is separate. | **m-iris lane** (not m-stdlib): the `iris session` stdout-marker capture should stream/chunk large output rather than truncate. Out of scope for T0b.2 (foia/remote is the cert target and is whole). | open (m-iris lane) | | 2026-06-13 | P2 | m-stdlib | Consumer SUITES stay IRIS-red for **non-file** reasons (byte-mode · `@cb@` indirection · wide-char) | After the file-I/O facade, the consumer suites still 0/0 on IRIS — **the file I/O was only part of why.** Confirmed by isolated probes: **STDJSON** — `$$parse^STDJSON` (no file) crashes on IRIS = the **byte-mode** assumption (STDJSON/STDB64/STDHEX/STDCSPRNG treat 1 char == 1 byte; IRIS strings are 16-bit Unicode) — documented charset constraint; **STDCSV** — `do @callback@(rownum,.fields)` (indirection-with-args) crashes on IRIS even with ASCII (`parseFile` callback dispatch); **STDCSV/STDSEED/STDLOG/STDXML** descriptions also carry non-ASCII (wide-char output, the m-iris GetOut/session-capture lane). STDCSV's *parser core* (`$$parse` of a string) DOES pass on IRIS (2/2), so it's the callback + wide-char, not the parser. | Out of file-I/O scope — separate follow-ups: byte-mode portability (big; affects the byte-oriented modules), an IRIS-portable callback-dispatch idiom for `parseFile^STDCSV` (replace `@cb@(args)` with an `xecute`d call or a fixed dispatch), and the wide-char capture path. Tracked so the next session knows the file-I/O refactor alone does NOT green these suites. | **superseded 2026-06-13 (s11)** — the "STDJSON byte-mode" half was a MISDIAGNOSIS (see s11 row below); the `@cb@` + wide-char halves stand (wide-char now fixed). | | 2026-06-13 | P1 | m-stdlib | **RE-BASELINE (s11): T0b.2 IRIS leg is 10/15; the gap is 4 code fixes, NONE byte-mode** | Rebuilt `m-iris/dist/m-iris` from `m-iris-driver@49a5b00` (the GetOut wide-char fix; the prior dist binary predated it) and ran `kids-test-in-place.sh iris` on foia. **10/15** (was 6/15). **The GetOut wide-char fix works on remote** — STDURL 150/0, STDREGEX 102/0, STDFMT 62/0 all green now. **Crucially STDB64 (55/0) + STDHEX (49/0) — the byte family — PASS on IRIS**, so there is **NO byte-mode blocker**; the s10 "STDJSON byte-mode" claim was wrong (it never isolated the crash). The 5 reds, each a DISTINCT cause: **(1) STDJSON** crash ` parse+12` for ALL inputs = the **unguarded `zgoto`-`$etrap`** idiom (parse + encode) — IRIS rejects YDB `zgoto LEVEL:label`; STDFS/STDHARN/STDASSERT.raises already guard the same idiom with an `if $zversion["IRIS" … quit` arm, STDJSON is the only base module that didn't. **(2) STDXML** crash ` parseElement+20 myNs("")` = **null/empty-string subscripts** (`myNs("")`/`nsMap("")` for the default namespace) — IRIS rejects null subscripts, YDB allows. **(3) STDCSV** won't COMPILE on IRIS: ` #5475 Expected end of line : '@callback@(curRow,.fields)'` — **IRIS has no ARGUMENT indirection** (`do @cb@(args)`), only name-indirection; whole routine fails → 0/0. **(4) STDDATE** 1 fail "year in plausible range": `now()` → `3567-05-6.157218T…` because it reads `$ZHOROLOG` as YDB's 4-comma `d,s,u,t` but **IRIS `$ZHOROLOG` is single elapsed-seconds**. **(5) STDUUID** 2 fails were COLLATERAL from the crashers in the same sequential process — source AND installed both 131/131 in isolation; no fix. | **4 code fixes (this session, TDD dual-engine, YDB byte-identical):** #1 STDJSON IRIS try/catch arm (parse+encode); #2 STDXML null-subscript fix [USER FORK → **sentinel-key in code**, chosen 2026-06-13] **[LANDED — `$$dfltNsKey()`=single space; STDXMLTST 209/209 both engines]**; #3 STDCSV `xecute`-built callback dispatch **[LANDED — + a latent STDFS readLn `$ECODE`-on-EOF bug; STDCSVTST 59/59 & STDFSTST 50/50 both engines]**; #4 STDDATE `$ZTIMESTAMP` IRIS arm in now() **[LANDED — STDDATETST 66/66 both engines]**. #5 STDUUID none. Then re-run on foia → expect 15/15. **#1 COMPLETE — STDJSONTST 209/209 BOTH engines (YDB + foia remote).** Beyond the etrap port: fixed TWO latent UTF-8 OPERATOR-PRECEDENCE bugs in emitUtf8 + the surrogate combine (M has no precedence — `$char(192+cp\\64)` evaluated as `(192+cp)\\64`; garbage on BOTH engines, latent because old tests used literal-byte passthrough); rewrote the 2 byte tests to `\u`-escapes+`$char`; graceful empty-key reject on IRIS (user decision — null local subscript is unconditional on IRIS incl. foia; documented in-code + stdjson.md + users-guide); fixed irisParse `$ECODE`-on-failure pollution (same class as STDFS readLn EOF); migrated STDJSONTST file tests off raw YDB `open:(newversion)` to the STDFS facade — **but STDJSONTST has 2 further IRIS tail issues exposed once the crash clears: (a) byte-exact UTF-8 tests use `$zchar` (unsupported on IRIS) + literal-multibyte source (byte-mode boundary) → needs the byte-mode decision; (b) `tParseObjectEmptyKeyAllowed` stores `root("")` = null subscript → same sentinel-key class as STDXML.** Lesson: each crashing suite stacks several IRIS issues; 10/15 undercounts per-suite work. | **CLOSED (s11): IRIS leg 17/15→17/17 on foia** (`suites=17 pass=1483 fail=0`). All 4 code fixes + STDUUID `unixMs` `$ztimestamp` IRIS arm (v7 time prefix was loosely-monotonic on IRIS via the YDB `$zhorolog` assumption) landed; STDFS+STDOS added to the base (STDOS ported to IRIS dual-engine first). YDB per-suite green; vehu in-place loop re-run owed. | diff --git a/src/STDFIX.m b/src/STDFIX.m index c4514ef..1bdcb84 100644 --- a/src/STDFIX.m +++ b/src/STDFIX.m @@ -27,10 +27,20 @@ ; REG,tag,"SETUP" ; registered setup code ; REG,tag,"TEARDOWN" ; registered teardown code ; - ; ``trollback $tlevel-1`` rolls back exactly the level this frame - ; opened, so nested with()/invoke() pairs roll back inner-only — - ; bare ``trollback`` (which targets level 0) would unbalance every - ; outer transaction. + ; ``trollback target`` (target = the pre-tstart $tlevel) rolls back + ; exactly the level this frame opened, so nested with()/invoke() pairs + ; roll back inner-only — bare ``trollback`` (which targets level 0) would + ; unbalance every outer transaction. + ; + ; Engine portability (YDB + IRIS): YDB ``trollback n`` rolls back TO level + ; n; IRIS ``trollback n`` rolls back n LEVELS (opposite meaning), so the + ; rollback is engine-split — IRIS uses ``trollback $tlevel-target``. Full + ; partial-rollback fidelity holds on both (nested inner-only rollback works + ; on IRIS too). REQUIREMENT: IRIS needs the namespace's database to be + ; JOURNALED for ``TSTART`` (an unjournaled db faults ````); + ; STDFIX is therefore usable only on a journaled IRIS namespace. (TSTART is + ; also forbidden lexically inside an IRIS ``try{}``/``xecute``, but STDFIX's + ; own tstart is a direct command, so this never bites here.) ; ; Errors set $ECODE to one of: ; ,U-STDFIX-EMPTY-TAG, @@ -56,13 +66,19 @@ new $etrap,saved,target if tag="" set $ecode=",U-STDFIX-EMPTY-TAG," quit set saved="",target=$tlevel - set $etrap="set saved=$ecode set $ecode="""" if $tlevel>target trollback target set $ecode=saved quit" + ; YDB `trollback N` rolls back TO level N; IRIS `trollback N` rolls back N + ; LEVELS. Both must undo exactly back to `target` (this scope's one tstart), + ; so the rollback form is engine-split — on IRIS use `trollback $tlevel-target`. + if $zversion["IRIS" set $etrap="set saved=$ecode set $ecode="""" if $tlevel>target trollback $tlevel-target set $ecode=saved quit" + else set $etrap="set saved=$ecode set $ecode="""" if $tlevel>target trollback target set $ecode=saved quit" tstart set ^STDLIB($job,"FIX","STACK",$tlevel)=tag ; m-lint: disable-next-line=M-MOD-036 xecute code ; XECUTE-of-arg is the documented purpose of with(). ; m-lint: disable-next-line=M-MOD-009 - trollback target ; matches the tstart above; preserves outer scopes. + if $zversion["IRIS" trollback $tlevel-target + ; m-lint: disable-next-line=M-MOD-009 + else trollback target ; matches the tstart above; preserves outer scopes. quit ; active() ; Predicate — is any nested transaction currently open? @@ -103,7 +119,9 @@ new $etrap,saved,target,setup,teardown if '$data(^STDLIB($job,"FIX","REG",tag)) set $ecode=",U-STDFIX-UNREGISTERED-TAG," quit set saved="",target=$tlevel - set $etrap="set saved=$ecode set $ecode="""" if $tlevel>target trollback target set $ecode=saved quit" + ; engine-split rollback (see with(): YDB rolls back TO level, IRIS by count). + if $zversion["IRIS" set $etrap="set saved=$ecode set $ecode="""" if $tlevel>target trollback $tlevel-target set $ecode=saved quit" + else set $etrap="set saved=$ecode set $ecode="""" if $tlevel>target trollback target set $ecode=saved quit" set setup=$get(^STDLIB($job,"FIX","REG",tag,"SETUP")) set teardown=$get(^STDLIB($job,"FIX","REG",tag,"TEARDOWN")) tstart @@ -115,7 +133,9 @@ ; m-lint: disable-next-line=M-MOD-036 if teardown'="" xecute teardown ; m-lint: disable-next-line=M-MOD-009 - trollback target ; matches the tstart above; preserves outer scopes. + if $zversion["IRIS" trollback $tlevel-target + ; m-lint: disable-next-line=M-MOD-009 + else trollback target ; matches the tstart above; preserves outer scopes. quit ; cleanup ; Best-effort rollback of any leaked transaction scope.