diff --git a/contributing/parallel_safe_session_attach.md b/contributing/parallel_safe_session_attach.md index ae2d614..b7c1fd5 100644 --- a/contributing/parallel_safe_session_attach.md +++ b/contributing/parallel_safe_session_attach.md @@ -174,14 +174,14 @@ If you write a new bootstrap-style file that legitimately needs custom attach, a 1. **Don't inline the literal `%%SESSION_PATH%%` token as a sentinel comparison.** The PowerShell wrapper's `.Replace()` is global, so any occurrence of the literal token will be rewritten. If you need to detect "unsubstituted token," build the comparison string at runtime via `Chr(37) & Chr(37) & "SESSION_PATH" & Chr(37) & Chr(37)`. See `sap_gui_object_details.vbs` for the precedent (and the bug it originally hid). 2. **Include order matters when both attach-lib and session-lock are present.** Attach lib MUST load first because session-lock's pre-unlock popup sweep reads from `oSession`. The canonical pattern above gets this right. 3. **The helper handles ALL error paths.** Don't wrap `AttachSapSession(SESSION_PATH)` in your own `If oSession Is Nothing Then ...` — the helper has already `WScript.Quit 2`'d on failure. Adding your own block is dead code. -4. **`SAPDEV_SESSION_PATH` only reaches cscript if cscript is a CHILD OF THE SAME PowerShell process.** A process env var is inherited by children — it is *not* shared between sibling processes and it dies when the process that set it exits. Most SKILL.md files set it in the **generator** block (`...-Fill the tokens`) and then launch `cscript` from a **separate, later** block, so the variable is already gone: the helper skips Strategy 2 and silently falls through to the sole-connection default. That is not hypothetical — it is what let a run pinned to S4D/100 drive a GUI window on S4H/400 and download the wrong system's source (2026-08-06). **Fix the generator by baking the resolved path into `%%SESSION_PATH%%`** (Strategy 1 — it is a `Const` in the emitted VBS, so it survives any process boundary), and only rely on the env var when the same block that exports it also runs `cscript`. +4. **`SAPDEV_SESSION_PATH` only reaches cscript if cscript is a CHILD OF THE SAME PowerShell process.** A process env var is inherited by children — it is *not* shared between sibling processes and it dies when the process that set it exits. Most SKILL.md files set it in the **generator** block (`...-Fill the tokens`) and then launch `cscript` from a **separate, later** block, so the variable is already gone: the helper skips Strategy 2 and silently falls through to the sole-connection default. That is not hypothetical — it is what let a run pinned to S4D/100 drive a GUI window on S4H/400 and download the wrong system's source (2026-08-06). **Fix the generator by baking the resolved path into `%%SESSION_PATH%%`** (Strategy 1 — it is a `Const` in the emitted VBS, so it survives any process boundary), and only rely on the env var when the same block that exports it also spawns the consumer. CI-enforced as a hard error since 2026-08-06 — see gate 13 below. Every SKILL.md in the repo now bakes; the env var remains a valid Strategy 2 for a caller that spawns `cscript` from its own process. 5. **Declare the expected SAP system whenever the skill also talks RFC.** `Connect-SapRfc` and `AttachSapSession` resolve their target through two *different* chains (pin → GUI-active → default → sole-profile, vs. hint → env → sole-connection → refuse), so they can land on different systems while the skill believes it read one. Export the expectation in the block that launches `cscript`: ```powershell . '\scripts\sap_connection_lib.ps1' Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null # sets SAPDEV_EXPECT_SYSTEM/_CLIENT & 'C:/Windows/SysWOW64/cscript.exe' //NoLogo '{RUN_TEMP}\..._run.vbs' ``` - `AssertSapGuiTarget` in the attach lib then refuses (exit 2) any session that is not that system, instead of retargeting silently. Every attach — expectation or not — now also emits `GUI_TARGET: system=… client=… user=… path=… via=…`, so a skill's output always records which SAP system it actually drove. **Surface that line in the skill's report; never state a system you did not read off it.** + `AssertSapGuiTarget` in the attach lib then refuses (exit 2) any session that is not that system, instead of retargeting silently. Every attach — expectation or not — now also emits `GUI_TARGET: system=… client=… user=… path=… via=…`, so a skill's output always records which SAP system it actually drove. **Surface that line in the skill's report; never state a system you did not read off it.** This matters most on the **write** paths — a create/update/delete aimed at the wrong system is not a bad read, it is a bad write — so it is CI-enforced as a hard error for every write-capable skill since 2026-08-06 (gate 14 below). Read-only GUI skills are not gated, but declaring the expectation there is still recommended: the incident that motivated all of this *was* a read. --- @@ -202,7 +202,7 @@ to hard errors once their counts ratcheted to zero (cscript/locale-literal 2026-07-10; missing screen baselines 2026-07-24 at 136/136), so violations now FAIL the run instead of appearing in the OK line.) -On failure, the script lists each non-conforming file with a specific reason. This contract's gate covers twelve conditions (2026-07-03: grown from seven; the authoritative spec for each is its own comment block inside `scripts/check-consistency.mjs` — note the checker has since grown further gates *outside* this contract's scope, e.g. the Rule 0 safety-gate coverage check and the manifest/marketplace alignment checks): +On failure, the script lists each non-conforming file with a specific reason. This contract's gate covers fourteen conditions (2026-07-03: grown from seven; 2026-08-06: +2 for the cross-system target guard; the authoritative spec for each is its own comment block inside `scripts/check-consistency.mjs` — note the checker has since grown further gates *outside* this contract's scope, e.g. the Rule 0 safety-gate coverage check and the manifest/marketplace alignment checks): 1. Legacy `For Each oCandidate In oApp.Children` (and its variant patterns) → must not appear in non-exempt operational VBS. 2. VBS with `Const SESSION_PATH` but no `%%ATTACH_LIB_VBS%%` include → the helper will be undefined at runtime. @@ -216,6 +216,10 @@ On failure, the script lists each non-conforming file with a specific reason. Th 10. `references/*.vbs` line that branches on translated GUI text — `InStr(...)` against a curated English literal ("resulted in errors", "locked", "Initial Screen", ...) or an `LCase() = "..."` compare — outside comments/`WScript.Echo` → **hard error** (promoted from WARN on 2026-07-10; documented multi-locale matchers are exempted via `LOCALE_LITERAL_EXEMPT` — see `shared/rules/language_independence_rules.md`). 11. Shipped `.ps1`/`.vbs` (skills' `references/` + sap-dev-core `shared/scripts/`) containing a non-ASCII byte without a UTF-8 BOM → **hard error** (promoted from WARN on 2026-07-02 once the tree reached zero offenders; use `--`/`->` in comments and `ChrW()` for runtime non-ASCII). 12. A file in `sap-dev-core/shared/scripts` not mentioned in CLAUDE.md's "Current Shared Files" table → **hard error** (added 2026-07-03; the reverse direction — a shared script shrunk to a single same-plugin consumer with no shared-side wiring — draws a shared-placement WARN, exceptions via `SHARED_PLACEMENT_ALLOWLIST`). +13. SKILL.md fenced block that assigns `$env:SAPDEV_SESSION_PATH` but launches **no child process in that same block** → **dead pin** → **hard error** (added 2026-08-06). Each fenced block runs as its own process, so the export dies before the VBS starts, the attach lib finds no env var, and Strategy 3 (sole-connection) silently discards the AI-session pin. Bake the resolved path into `%%SESSION_PATH%%` instead — see gotcha 4 and the canonical wrapper above. A block that *does* spawn a child (`cscript`, or a `powershell -File …` that resolves the session itself, as `/sap-update-addon`'s detect step does) is fine: children inherit the environment. +14. SKILL.md fenced block that invokes `cscript` from a **write-capable** skill (`SAFETY_GATE_SKILLS`) without `Set-SapGuiTargetExpectation` in that same block → **unverified GUI target** → **hard error** (added 2026-08-06). Same process boundary as 13: `SAPDEV_EXPECT_SYSTEM`/`_CLIENT` must be exported by the process that launches `cscript` or `AssertSapGuiTarget` has nothing to enforce, and a GUI parked on another system gets written to instead of refused. See gotcha 5. Blocks whose only VBS targets are in `TIER3_EXEMPT_VBS` are skipped — those templates never call `AttachSapSession`, so the expectation would be inert (the pre-login `sap_check_gui_login_status.vbs` probe, the `sap_gui_security_warmup.vbs` Hardcopy, `/sap-gui-probe`'s self-resolving drivers). + +> **Ratchet history for 13/14.** Both shipped 2026-08-06 as WARN over 102 offenders (58 dead pins + 44 unverified write blocks), hard-erroring only the six flagship deploy skills (se38 / se37 / se11 / se24 / se91 / se21). The remaining 34 skills were migrated the same day, both counts reached zero, and both halves were promoted to unconditional hard errors — same path the bare-cscript and screen-baseline gates took. --- diff --git a/plugins/sap-dev-core/skills/sap-activate-object/SKILL.md b/plugins/sap-dev-core/skills/sap-activate-object/SKILL.md index fc06c3d..0619953 100644 --- a/plugins/sap-dev-core/skills/sap-activate-object/SKILL.md +++ b/plugins/sap-dev-core/skills/sap-activate-object/SKILL.md @@ -240,14 +240,16 @@ $content = $content -replace '%%OBJECT_NAME%%','THE_NAME' $content = $content -replace '%%OBJECT_TYPE%%','THE_TYPE' # SE11 only $content = $content -replace '%%ACTIVATION_LOG_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_activation_log.vbs' # SE11 only $content = $content -replace '%%TEMP_DIR%%','{RUN_TEMP}' # SE11 only -# Phase 3.5 session-attach plumbing. -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# Phase 4.2 session-attach plumbing. BAKE the resolved path into %%SESSION_PATH%% +# (attach Strategy 1): this generator is a SEPARATE process from the one that runs +# cscript, so an $env:SAPDEV_SESSION_PATH exported here dies with it and the attach +# lib silently falls through to its sole-connection default (2026-08-06). . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_activate_<TXN>_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` > **Activation-log capture (SE11 only, by design)**: when SE11 activation @@ -264,10 +266,18 @@ Write-Host 'Done' > those VBS templates is the right surfacing mechanism. Do NOT propagate > this helper to non-SE11 activate scripts. -Run via 32-bit cscript: +Generate: ```bash powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_activate_<TXN>_run.ps1" -C:/Windows/SysWOW64/cscript.exe //NoLogo {RUN_TEMP}\sap_activate_<TXN>_run.vbs +``` + +Run via 32-bit cscript, declaring the GUI target in the SAME block (the attach +lib reads `SAPDEV_EXPECT_SYSTEM`/`_CLIENT` from the process environment, so a GUI +parked on a different system than the RFC leg is refused, not activated): +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:/Windows/SysWOW64/cscript.exe' //NoLogo '{RUN_TEMP}\sap_activate_<TXN>_run.vbs' ``` Each VBS emits: diff --git a/plugins/sap-dev-core/skills/sap-atc/SKILL.md b/plugins/sap-dev-core/skills/sap-atc/SKILL.md index e8fa928..2861fa1 100644 --- a/plugins/sap-dev-core/skills/sap-atc/SKILL.md +++ b/plugins/sap-dev-core/skills/sap-atc/SKILL.md @@ -252,12 +252,14 @@ $content = $content.Replace('%%OBJECT_NAME%%', 'THE_OBJECT_NAME') # empt # Batch mode (A5): absolute path to the <TYPE> <NAME> object-list file, else empty. $content = $content.Replace('%%OBJECT_LIST_FILE%%','THE_OBJECT_LIST_FILE') $content = $content.Replace('%%SESSION_LOCK_VBS%%',"$shared\scripts\sap_session_lock.vbs") -# Phase 3.5 session-attach plumbing. -$sessionPath = '' +# Phase 4.2 session-attach plumbing. BAKE the resolved path into %%SESSION_PATH%% +# (attach Strategy 1): this generator is a SEPARATE process from the one that runs +# cscript, so an $env:SAPDEV_SESSION_PATH exported here dies with it and the attach +# lib silently falls through to its sole-connection default (2026-08-06). +. "$shared\scripts\sap_connection_lib.ps1" +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' $content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content.Replace('%%ATTACH_LIB_VBS%%', "$shared\scripts\sap_attach_lib.vbs") -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_atc_stage1_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) ``` @@ -316,11 +318,12 @@ $content = $content.Replace('%%CHECK_VARIANT%%', 'THE_CHECK_VARIANT') $content = $content.Replace('%%OBJECT_PROVIDER%%', 'THE_OBJECT_PROVIDER') $content = $content.Replace('%%SESSION_LOCK_VBS%%', "$shared\scripts\sap_session_lock.vbs") # Phase 3.5 session-attach plumbing. -$sessionPath = '' +# BAKE the session path (attach Strategy 1) -- an env var exported by this +# generator process never reaches the separate process that runs cscript. +. "$shared\scripts\sap_connection_lib.ps1" +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' $content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content.Replace('%%ATTACH_LIB_VBS%%', "$shared\scripts\sap_attach_lib.vbs") -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_atc_stage2_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) ``` @@ -374,11 +377,12 @@ $content = [System.IO.File]::ReadAllText("$skillDir\references\sap_atc_check_ru $content = $content.Replace('%%RUN_SERIES_NAME%%', 'THE_RUN_SERIES') $content = $content.Replace('%%SESSION_LOCK_VBS%%', "$shared\scripts\sap_session_lock.vbs") # Phase 3.5 session-attach plumbing. -$sessionPath = '' +# BAKE the session path (attach Strategy 1) -- an env var exported by this +# generator process never reaches the separate process that runs cscript. +. "$shared\scripts\sap_connection_lib.ps1" +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' $content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content.Replace('%%ATTACH_LIB_VBS%%', "$shared\scripts\sap_attach_lib.vbs") -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_atc_stage3_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) ``` @@ -456,11 +460,12 @@ $content = $content.Replace('%%RUN_SERIES_NAME%%', 'THE_RUN_SERIES') $content = $content.Replace('%%OUTPUT_PATH%%', 'THE_OUTPUT_PATH') $content = $content.Replace('%%SESSION_LOCK_VBS%%', "$shared\scripts\sap_session_lock.vbs") # Phase 3.5 session-attach plumbing. -$sessionPath = '' +# BAKE the session path (attach Strategy 1) -- an env var exported by this +# generator process never reaches the separate process that runs cscript. +. "$shared\scripts\sap_connection_lib.ps1" +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' $content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content.Replace('%%ATTACH_LIB_VBS%%', "$shared\scripts\sap_attach_lib.vbs") -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_atc_stage4_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) ``` @@ -581,11 +586,12 @@ $content = $content.Replace('%%RUN_SERIES_NAME%%', 'THE_RUN_SERIES') $content = $content.Replace('%%OUTPUT_PATH%%', 'THE_DRILL_PATH') $content = $content.Replace('%%SESSION_LOCK_VBS%%',"$shared\scripts\sap_session_lock.vbs") # Phase 3.5 session-attach plumbing. -$sessionPath = '' +# BAKE the session path (attach Strategy 1) -- an env var exported by this +# generator process never reaches the separate process that runs cscript. +. "$shared\scripts\sap_connection_lib.ps1" +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' $content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content.Replace('%%ATTACH_LIB_VBS%%', "$shared\scripts\sap_attach_lib.vbs") -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_atc_stage4b_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) ``` diff --git a/plugins/sap-dev-core/skills/sap-change-package/SKILL.md b/plugins/sap-dev-core/skills/sap-change-package/SKILL.md index 02d2fd0..e535a5d 100644 --- a/plugins/sap-dev-core/skills/sap-change-package/SKILL.md +++ b/plugins/sap-dev-core/skills/sap-change-package/SKILL.md @@ -275,20 +275,31 @@ $content = $content -replace '%%OBJECT_TYPE%%','THE_TYPE' # SE11 only $content = $content -replace '%%NEW_PACKAGE%%','THE_PKG' $content = $content -replace '%%TRANSPORT%%','THE_TR' $content = $content -replace '%%TR_DESCRIPTION%%','THE_TR_DESC' -# Phase 3.5 session-attach plumbing. -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# Phase 4.2 session-attach plumbing. BAKE the resolved path into %%SESSION_PATH%% +# (attach Strategy 1): this generator is a SEPARATE process from the one that runs +# cscript, so an $env:SAPDEV_SESSION_PATH exported here dies with it and the attach +# lib silently falls through to its sole-connection default (2026-08-06). . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_change_package_<TXN>_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` -Run via 32-bit cscript: +Generate: ```bash powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_change_package_<TXN>_run.ps1" -C:/Windows/SysWOW64/cscript.exe //NoLogo {RUN_TEMP}\sap_change_package_<TXN>_run.vbs +``` + +Run via 32-bit cscript, declaring the GUI target in the SAME block (the attach +lib reads `SAPDEV_EXPECT_SYSTEM`/`_CLIENT` from the process environment, so a GUI +parked on a different system than the RFC leg is refused rather than having its +namesake object reassigned): +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:/Windows/SysWOW64/cscript.exe' //NoLogo '{RUN_TEMP}\sap_change_package_<TXN>_run.vbs' ``` Each VBS emits a stable contract: diff --git a/plugins/sap-dev-core/skills/sap-check-fix/SKILL.md b/plugins/sap-dev-core/skills/sap-check-fix/SKILL.md index 1358643..e112579 100644 --- a/plugins/sap-dev-core/skills/sap-check-fix/SKILL.md +++ b/plugins/sap-dev-core/skills/sap-check-fix/SKILL.md @@ -215,18 +215,30 @@ Write `{RUN_TEMP}\sap_check_fix_probe_se38.ps1`: $content = [System.IO.File]::ReadAllText('<SKILL_DIR>\..\sap-se38\references\sap_se38_check.vbs', [System.Text.Encoding]::UTF8) $content = $content -replace '%%PROGRAM_NAME%%','THE_OBJECT_NAME' # Session-attach plumbing (mandatory — the check.vbs includes the attach lib). -$content = $content -replace '%%SESSION_PATH%%', '' -$content = $content -replace '%%ATTACH_LIB_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# BAKE the resolved path into %%SESSION_PATH%% (attach Strategy 1): this generator +# is a SEPARATE process from the one that runs cscript, so an +# $env:SAPDEV_SESSION_PATH exported here dies with it and the attach lib silently +# falls through to its sole-connection default (2026-08-06). . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_check_fix_probe_se38.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` -Then run the PS1 (writes the VBS) and execute the VBS: +Then run the PS1 (writes the VBS): ```bash powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_check_fix_probe_se38.ps1" -C:/Windows/SysWOW64/cscript.exe //NoLogo "{RUN_TEMP}\sap_check_fix_probe_se38.vbs" +``` + +Execute the VBS with the GUI target declared in the SAME block — the attach lib +reads `SAPDEV_EXPECT_SYSTEM`/`_CLIENT` from the process environment, so a GUI +parked on a different system than the RFC leg is refused rather than probed: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:/Windows/SysWOW64/cscript.exe' //NoLogo "{RUN_TEMP}\sap_check_fix_probe_se38.vbs" ``` For probes 2–6 use the sibling template and its own token(s) (SE37 `%%FM_NAME%%`, SE24 `%%CLASS_NAME%%`, SE11 `%%OBJECT_TYPE%%` + `%%OBJECT_NAME%%`) plus the same two session-attach substitutions above, writing to `sap_check_fix_probe_<se37|se24|se11>.ps1` / `.vbs`. diff --git a/plugins/sap-dev-core/skills/sap-cmod/SKILL.md b/plugins/sap-dev-core/skills/sap-cmod/SKILL.md index e0b47b8..1f888a0 100644 --- a/plugins/sap-dev-core/skills/sap-cmod/SKILL.md +++ b/plugins/sap-dev-core/skills/sap-cmod/SKILL.md @@ -165,18 +165,31 @@ $content = [System.IO.File]::ReadAllText('<SKILL_DIR>\references\<TEMPLATE>.vbs' $content = $content -replace '%%PROJECT_NAME%%','THE_PROJECT' # ... e.g. %%ENHANCEMENTS%% / %%SHORT_TEXT%% / %%PACKAGE%% / %%TRANSPORT%% ... # --- session-attach plumbing (always) --- -$content = $content -replace '%%SESSION_PATH%%', '' +# BAKE the resolved path into %%SESSION_PATH%% (attach Strategy 1): this generator +# is a SEPARATE process from the one that runs cscript, so an +# $env:SAPDEV_SESSION_PATH exported here dies with it and the attach lib silently +# falls through to its sole-connection default (2026-08-06). +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' $content = $content -replace '%%SESSION_LOCK_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_session_lock.vbs' -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\<TEMPLATE>_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` ```bash powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\<TEMPLATE>_run.ps1" -C:/Windows/SysWOW64/cscript.exe //NoLogo {RUN_TEMP}\<TEMPLATE>_run.vbs +``` + +Declare the GUI target in the SAME block as cscript — the attach lib reads +`SAPDEV_EXPECT_SYSTEM`/`_CLIENT` from the process environment, so a GUI parked on +a different system than the RFC leg is refused instead of having its namesake +project/enhancement written: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:/Windows/SysWOW64/cscript.exe' //NoLogo '{RUN_TEMP}\<TEMPLATE>_run.vbs' ``` > **Encoding:** always `-Encoding Unicode` (UTF-16 LE) — what `cscript` diff --git a/plugins/sap-dev-core/skills/sap-explain-object/SKILL.md b/plugins/sap-dev-core/skills/sap-explain-object/SKILL.md index 5c401d1..91fc8c1 100644 --- a/plugins/sap-dev-core/skills/sap-explain-object/SKILL.md +++ b/plugins/sap-dev-core/skills/sap-explain-object/SKILL.md @@ -147,18 +147,29 @@ $r = Read-SapAbapSource -Name '{OBJECT}' -Type '{TYPE}' -OutDir '{OUT}' -WithInc SE24 GUI download VBS (skip if `{NOGUI}`): ```powershell $skillSe24 = '<SKILL_DIR>\..\sap-se24' +# BAKE the resolved session path into %%SESSION_PATH%% (attach Strategy 1): this +# generator is a SEPARATE process from the one that runs cscript, so an +# $env:SAPDEV_SESSION_PATH exported here dies with it and the attach lib silently +# falls through to its sole-connection default -- which is how a run pinned to one +# SAP system downloaded ANOTHER one's source under the same name (2026-08-06). +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' $vbs = ([System.IO.File]::ReadAllText("$skillSe24\references\sap_se24_check_and_download.vbs", [System.Text.Encoding]::UTF8)). Replace('%%CLASS_NAME%%','{OBJECT}'). Replace('%%OUTPUT_FILE%%','{OUT}\source.txt'). - Replace('%%SESSION_PATH%%',''). + Replace('%%SESSION_PATH%%',$sessionPath). Replace('%%SYNTAX_CHECK_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_syntax_check_lib.vbs'). Replace('%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs') -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\explain_dl.vbs', $vbs, [System.Text.UnicodeEncoding]::new($false, $true)) ``` -```bash -"C:/Windows/SysWOW64/cscript.exe" //NoLogo "{RUN_TEMP}\explain_dl.vbs" +Run the download with the GUI target declared in the SAME block — the RFC readers +above and this GUI leg resolve their target through different chains, and the +`GUI_TARGET:` line the attach lib echoes is what tells you which system the source +actually came from: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& "C:/Windows/SysWOW64/cscript.exe" //NoLogo "{RUN_TEMP}\explain_dl.vbs" ``` > Note: GUI download returns the pretty-printed *display* view (local `TYPES` > may appear at outer scope). Adequate for comprehension; flag it in the dossier. diff --git a/plugins/sap-dev-core/skills/sap-file-transfer/SKILL.md b/plugins/sap-dev-core/skills/sap-file-transfer/SKILL.md index d45bbb2..c5f21cd 100644 --- a/plugins/sap-dev-core/skills/sap-file-transfer/SKILL.md +++ b/plugins/sap-dev-core/skills/sap-file-transfer/SKILL.md @@ -196,13 +196,20 @@ $content = $content.Replace('%%LOCAL_FILE%%', '<local-file>') $content = $content.Replace('%%REMOTE_FILE%%', '<appserver-file>') $content = $content.Replace('%%TRANSFER_MODE%%', '<ASC|BIN>') $content = $content.Replace('%%OVERWRITE%%', '<X-or-empty>') -$content = $content.Replace('%%SESSION_PATH%%', '<--session value or empty>') +# Prefer an explicit --session, else this AI session's pin. BAKE it into +# %%SESSION_PATH%% (attach Strategy 1) so it survives even if the cscript call +# later moves to its own block (an exported env var would not). +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +$sessionPath = '<--session value or empty>' +if (-not $sessionPath) { $sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' } +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content.Replace('%%ATTACH_LIB_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs') $content = $content.Replace('%%SESSION_LOCK_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_session_lock.vbs') -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText("$runTemp\sap_file_transfer_run.vbs", $content, [System.Text.UnicodeEncoding]::new($false, $true)) -C:\Windows\SysWOW64\cscript.exe //NoLogo "$runTemp\sap_file_transfer_run.vbs" +# Declare which SAP system the GUI leg may drive; the attach lib reads this from +# the process environment, so it MUST be set by the process that runs cscript. +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo "$runTemp\sap_file_transfer_run.vbs" ``` **Outcome contract** (parse stdout + exit code — never infer success from diff --git a/plugins/sap-dev-core/skills/sap-function-group/SKILL.md b/plugins/sap-dev-core/skills/sap-function-group/SKILL.md index ca4f6ae..bc47258 100644 --- a/plugins/sap-dev-core/skills/sap-function-group/SKILL.md +++ b/plugins/sap-dev-core/skills/sap-function-group/SKILL.md @@ -258,21 +258,31 @@ $content = $content -replace '%%FUGR_ID%%','THE_ID' $content = $content -replace '%%FUGR_DESC%%','THE_DESC' $content = $content -replace '%%PACKAGE%%','THE_PKG' $content = $content -replace '%%TRANSPORT%%','THE_TR' -# Phase 3.5 session-attach plumbing. -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# Phase 4.2 session-attach plumbing. BAKE the resolved path into %%SESSION_PATH%% +# (attach Strategy 1): this generator is a SEPARATE process from the one that runs +# cscript, so an $env:SAPDEV_SESSION_PATH exported here dies with it and the attach +# lib silently falls through to its sole-connection default (2026-08-06). . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_function_group_gui_create_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Run via cscript: ```bash powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_function_group_gui_create_run.ps1" -C:/Windows/SysWOW64/cscript.exe //NoLogo {RUN_TEMP}\sap_function_group_gui_create_run.vbs +``` + +Declare the GUI target in the SAME block as cscript — the attach lib reads +`SAPDEV_EXPECT_SYSTEM`/`_CLIENT` from the process environment, so a GUI parked on +a different system than the RFC leg is refused instead of receiving the FG: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:/Windows/SysWOW64/cscript.exe' //NoLogo '{RUN_TEMP}\sap_function_group_gui_create_run.vbs' ``` **Output:** the VBS prints `INFO: sbar [...]` and `INFO: activate sbar @@ -390,21 +400,30 @@ $content = $content -replace '%%TRANSPORT%%','THE_TR' $content = $content -replace '%%PACKAGE%%','THE_PACKAGE' # FG DEVCLASS (pre-check 3); fills the ECC6 KO007 Object-Directory popup. Empty = Local Object. $content = $content -replace '%%ORIG_LANG%%','' # 1-char orig lang for an empty KO007 package; VBS defaults to E $content = $content -replace '%%SESSION_LOCK_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_session_lock.vbs' -# Phase 3.5 session-attach plumbing. -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# BAKE the session path (attach Strategy 1) -- an env var exported by this +# generator process never reaches the separate process that runs cscript. Delete +# is irreversible, so a silent retarget would destroy the WRONG system's FG. . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_function_group_gui_delete_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Run: ```bash powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_function_group_gui_delete_run.ps1" -C:/Windows/SysWOW64/cscript.exe //NoLogo {RUN_TEMP}\sap_function_group_gui_delete_run.vbs +``` + +Declare the GUI target in the SAME block as cscript. Deletion is irreversible, so +refusing a GUI parked on a different system than the RFC leg is the difference +between deleting the intended FG and destroying its namesake elsewhere: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:/Windows/SysWOW64/cscript.exe' //NoLogo '{RUN_TEMP}\sap_function_group_gui_delete_run.vbs' ``` ### Behaviour Notes diff --git a/plugins/sap-dev-core/skills/sap-gui-inspect/SKILL.md b/plugins/sap-dev-core/skills/sap-gui-inspect/SKILL.md index 408880b..7b4631a 100644 --- a/plugins/sap-dev-core/skills/sap-gui-inspect/SKILL.md +++ b/plugins/sap-dev-core/skills/sap-gui-inspect/SKILL.md @@ -258,15 +258,18 @@ $manifest = "$diagDir\manifest.tsv" $content = [System.IO.File]::ReadAllText("$skillDir\references\sap_gui_diagnose_capture.vbs", [System.Text.Encoding]::UTF8) $content = $content.Replace('%%OUTPUT_DIR%%', $diagDir) $content = $content.Replace('%%MANIFEST%%', $manifest) -# Session-attach plumbing (Phase 3.5 multi-connection aware). Resolution: -# explicit --session > SAPDEV_SESSION_PATH > sole-connection auto-default > refuse. +# Session-attach plumbing (Phase 4.2). Resolution: explicit --session > this AI +# session's pin, BAKED into %%SESSION_PATH%% (attach Strategy 1). Do not export +# $env:SAPDEV_SESSION_PATH here instead: this generator is a SEPARATE process from +# the one that runs cscript, so the env var would already be gone and the attach +# lib would silently fall through to its sole-connection default (2026-08-06). +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' $sessionPath = '' # set to the parsed --session value if supplied +if (-not $sessionPath) { $sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' } $content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content.Replace('%%ATTACH_LIB_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs') -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText("{RUN_TEMP}\sap_gui_inspect_capture_run.vbs", $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Run via `cscript`, **with the SAP GUI Security watcher in parallel**. `HardCopy` diff --git a/plugins/sap-dev-core/skills/sap-job/SKILL.md b/plugins/sap-dev-core/skills/sap-job/SKILL.md index d7c4f7b..64cbc66 100644 --- a/plugins/sap-dev-core/skills/sap-job/SKILL.md +++ b/plugins/sap-dev-core/skills/sap-job/SKILL.md @@ -211,14 +211,17 @@ $content = $content.Replace('%%JOBNAME%%', 'THE_JOBNAME') # '' = program $content = $content.Replace('%%START%%', 'THE_START') # immediate|YYYYMMDDHHMMSS|event:E $content = $content.Replace('%%PERIOD%%', 'THE_PERIOD') # '' | daily|weekly|monthly $content = $content.Replace('%%JOBCLASS%%', 'THE_CLASS') # A|B|C ('' -> C) -# Tier-3 session-attach plumbing. +# Tier-3 session-attach plumbing. BAKE the resolved path into %%SESSION_PATH%% +# (attach Strategy 1): this generator is a SEPARATE process from the one that runs +# cscript, so an $env:SAPDEV_SESSION_PATH exported here dies with it and the attach +# lib silently falls through to its sole-connection default (2026-08-06). +. "$shared\scripts\sap_connection_lib.ps1" $sessionPath = '' # set to the parsed --session value if supplied +if (-not $sessionPath) { $sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' } $content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content.Replace('%%ATTACH_LIB_VBS%%', "$shared\scripts\sap_attach_lib.vbs") -. "$shared\scripts\sap_connection_lib.ps1" -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_sm36_schedule.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` *Operations generator* — write `{RUN_TEMP}\sap_sm37_ops_run.ps1` (same shape): @@ -235,20 +238,31 @@ $content = $content.Replace('%%FROM_DATE%%', 'THE_FROM') # '' = scre $content = $content.Replace('%%TO_DATE%%', 'THE_TO') $content = $content.Replace('%%STATUS_FILTER%%', 'THE_STATUSES') # e.g. 'RF'; '' = all $content = $content.Replace('%%SAVE_PATH%%', 'THE_SAVE_PATH') # log %PC target, '' to skip -$sessionPath = '' +# BAKE the session path (attach Strategy 1) -- an env var exported by this +# generator process never reaches the separate process that runs cscript. +. "$shared\scripts\sap_connection_lib.ps1" +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' $content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content.Replace('%%ATTACH_LIB_VBS%%', "$shared\scripts\sap_attach_lib.vbs") -. "$shared\scripts\sap_connection_lib.ps1" -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_sm37_ops.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` -Run the generator, then the VBS via the **32-bit** cscript host (SAP GUI COM needs 32-bit): +Run the generator, then the VBS via the **32-bit** cscript host (SAP GUI COM needs +32-bit). Same two blocks for either flow — substitute `sap_sm37_ops` / +`sap_sm36_schedule` for `<VBS>`: ```bash -powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_sm37_ops_run.ps1" -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_sm37_ops.vbs +powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\<VBS>_run.ps1" +``` + +Declare the GUI target in the SAME block as cscript — the attach lib reads +`SAPDEV_EXPECT_SYSTEM`/`_CLIENT` from the process environment, so a job operation +can never land on a system other than the one the RFC leg resolved: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\<VBS>.vbs' ``` For the GUI **log** save-output, wrap the cscript with the SAP GUI Security guard exactly diff --git a/plugins/sap-dev-core/skills/sap-run-abap-unit/SKILL.md b/plugins/sap-dev-core/skills/sap-run-abap-unit/SKILL.md index 55989ff..bb921bb 100644 --- a/plugins/sap-dev-core/skills/sap-run-abap-unit/SKILL.md +++ b/plugins/sap-dev-core/skills/sap-run-abap-unit/SKILL.md @@ -172,19 +172,25 @@ $content = [System.IO.File]::ReadAllText("$skillDir\references\$tpl", [System.T $content = $content.Replace('%%OBJECT_NAME%%', 'THE_OBJECT') # '1' when --with-coverage (or --min-coverage given); '' for results-only. $content = $content.Replace('%%WITH_COVERAGE%%', 'THE_WITH_COVERAGE') -# Tier-3 session-attach plumbing. -$sessionPath = '' +# Tier-3 session-attach plumbing. BAKE the resolved path into %%SESSION_PATH%% +# (attach Strategy 1): this generator is a SEPARATE process from the one that runs +# cscript, so an $env:SAPDEV_SESSION_PATH exported here dies with it and the attach +# lib silently falls through to its sole-connection default (2026-08-06). +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' $content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content.Replace('%%ATTACH_LIB_VBS%%', "$shared\scripts\sap_attach_lib.vbs") -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_run_abap_unit.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) ``` -Run via 32-bit cscript: +Run via 32-bit cscript, declaring the GUI target in the SAME block — the attach +lib reads `SAPDEV_EXPECT_SYSTEM`/`_CLIENT` from the process environment, so unit +results can never come from a system other than the one the RFC leg resolved: -```bash -C:/Windows/SysWOW64/cscript.exe //NoLogo {RUN_TEMP}\sap_run_abap_unit.vbs +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:/Windows/SysWOW64/cscript.exe' //NoLogo '{RUN_TEMP}\sap_run_abap_unit.vbs' ``` --- diff --git a/plugins/sap-dev-core/skills/sap-run-report/SKILL.md b/plugins/sap-dev-core/skills/sap-run-report/SKILL.md index 796665b..c566691 100644 --- a/plugins/sap-dev-core/skills/sap-run-report/SKILL.md +++ b/plugins/sap-dev-core/skills/sap-run-report/SKILL.md @@ -172,14 +172,17 @@ $content = $content.Replace('%%VARIANT%%', 'THE_VARIANT') # '' if none $content = $content.Replace('%%VALUES%%', 'THE_VALUES') # '' if none $content = $content.Replace('%%MODE%%', 'THE_MODE') # FG | BG $content = $content.Replace('%%SAVE_PATH%%', 'THE_SAVE_PATH') # '' to skip capture -# Tier-3 session-attach plumbing. +# Tier-3 session-attach plumbing. BAKE the resolved path into %%SESSION_PATH%% +# (attach Strategy 1): this generator is a SEPARATE process from the one that runs +# cscript, so an $env:SAPDEV_SESSION_PATH exported here dies with it and the attach +# lib silently falls through to its sole-connection default (2026-08-06). +. "$shared\scripts\sap_connection_lib.ps1" $sessionPath = '' # set to the parsed --session value if supplied +if (-not $sessionPath) { $sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' } $content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content.Replace('%%ATTACH_LIB_VBS%%', "$shared\scripts\sap_attach_lib.vbs") -. "$shared\scripts\sap_connection_lib.ps1" -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_sa38_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Run the generator: @@ -190,9 +193,13 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_sa38_run_run.ps1" ### Execute **No capture (background, or foreground without `--save-output`)** — run the VBS via the -**32-bit** cscript host (SAP GUI COM needs 32-bit): -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_sa38_run.vbs +**32-bit** cscript host (SAP GUI COM needs 32-bit), declaring the GUI target in the SAME +block (the attach lib reads `SAPDEV_EXPECT_SYSTEM`/`_CLIENT` from the process environment, +so the report can never be executed on a system other than the one the RFC leg resolved): +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_sa38_run.vbs' ``` **Foreground with `--save-output`** — the `%PC` list download is SAP-GUI-side file IO, so @@ -201,6 +208,9 @@ Substitute `THE_SAVE_PATH` (= `--save-output`) and `THE_SID` / `THE_CLIENT` (pin ```powershell $shared = '<SAP_DEV_CORE_SHARED_DIR>\scripts' $out = 'THE_SAVE_PATH' +# Declare which SAP system the GUI leg may drive; MUST be in the same block as cscript. +. "$shared\sap_connection_lib.ps1" +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null & "$shared\sap_gui_security_precheck.ps1" -Path $out -Access w -System 'THE_SID' -Client 'THE_CLIENT' -Transaction 'SA38' | Out-Host $watcher = $null if ($LASTEXITCODE -ne 0) { diff --git a/plugins/sap-dev-core/skills/sap-se01/SKILL.md b/plugins/sap-dev-core/skills/sap-se01/SKILL.md index 4a9cc20..615d60f 100644 --- a/plugins/sap-dev-core/skills/sap-se01/SKILL.md +++ b/plugins/sap-dev-core/skills/sap-se01/SKILL.md @@ -219,14 +219,17 @@ $content = [System.IO.File]::ReadAllText('<SKILL_DIR>\references\sap_se01_create $content = $content -replace '%%REQUEST_TYPE%%','THE_TYPE' $content = $content -replace '%%DESCRIPTION%%','THE_DESC' $content = $content -replace '%%SESSION_LOCK_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_session_lock.vbs' -# Phase 3.5 session-attach plumbing. -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# Phase 4.2 session-attach plumbing. BAKE the resolved path into %%SESSION_PATH%% +# (attach Strategy 1): this generator is a SEPARATE process from the one that runs +# cscript, so an $env:SAPDEV_SESSION_PATH exported here dies with it and the attach +# lib silently falls through to its sole-connection default -- which would create +# the TR on a different SAP system than the deploy that asked for it (2026-08-06). . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se01_create_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace `THE_TYPE` (W or C), `THE_DESC` (short text), and `<SKILL_DIR>` / `{WORK_TEMP}` with absolute paths. @@ -234,7 +237,15 @@ Replace `THE_TYPE` (W or C), `THE_DESC` (short text), and `<SKILL_DIR>` / Run: ```bash powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se01_create_run.ps1" -C:/Windows/SysWOW64/cscript.exe //NoLogo {RUN_TEMP}\sap_se01_create_run.vbs +``` + +Declare the GUI target in the SAME block as cscript — the attach lib reads +`SAPDEV_EXPECT_SYSTEM`/`_CLIENT` from the process environment, so the TR is always +created on the system the caller's RFC leg resolved: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:/Windows/SysWOW64/cscript.exe' //NoLogo '{RUN_TEMP}\sap_se01_create_run.vbs' ``` **Expected last line of stdout:** `DONE`. If the VBS prints `ERROR:`, abort @@ -447,20 +458,29 @@ Write `{RUN_TEMP}\sap_se01_release_run.ps1`: ```powershell $content = [System.IO.File]::ReadAllText('<SKILL_DIR>\references\sap_se01_release.vbs', [System.Text.Encoding]::UTF8) $content = $content -replace '%%TRANSPORT%%','THE_TR' -# Phase 3.5 session-attach plumbing. -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# BAKE the session path (attach Strategy 1) -- an env var exported by this +# generator process never reaches the separate process that runs cscript. Release +# is irreversible, so a silent retarget would release the WRONG system's TR. . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se01_release_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Run: ```bash powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se01_release_run.ps1" -C:/Windows/SysWOW64/cscript.exe //NoLogo {RUN_TEMP}\sap_se01_release_run.vbs +``` + +Declare the GUI target in the SAME block as cscript. Release is irreversible, so +refusing a GUI parked on a different system than the RFC leg is the difference +between releasing the intended TR and releasing another system's namesake: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:/Windows/SysWOW64/cscript.exe' //NoLogo '{RUN_TEMP}\sap_se01_release_run.vbs' ``` ## R6 — Interpret VBS output @@ -637,13 +657,15 @@ Write `{RUN_TEMP}\sap_se01_delete_run.ps1`: $content = [System.IO.File]::ReadAllText('<SKILL_DIR>\references\sap_se01_delete.vbs', [System.Text.Encoding]::UTF8) $content = $content -replace '%%TRANSPORT%%','THE_TR' $content = $content -replace '%%SESSION_LOCK_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_session_lock.vbs' -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# BAKE the session path (attach Strategy 1) -- an env var exported by this +# generator process never reaches the separate process that runs cscript. Delete +# is irreversible, so a silent retarget would destroy the WRONG system's TR. . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se01_delete_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` The VBS derives the shared `sap_delete_popups.vbs` path from the substituted `%%ATTACH_LIB_VBS%%` directory (same `shared/scripts` folder) -- no extra token. @@ -651,7 +673,15 @@ The VBS derives the shared `sap_delete_popups.vbs` path from the substituted Run: ```bash powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se01_delete_run.ps1" -C:/Windows/SysWOW64/cscript.exe //NoLogo {RUN_TEMP}\sap_se01_delete_run.vbs +``` + +Declare the GUI target in the SAME block as cscript. Deletion is irreversible, so +refusing a GUI parked on a different system than the RFC leg is the difference +between deleting the intended TR and destroying another system's namesake: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:/Windows/SysWOW64/cscript.exe' //NoLogo '{RUN_TEMP}\sap_se01_delete_run.vbs' ``` ## D5 -- Interpret VBS output @@ -789,13 +819,14 @@ $content = [System.IO.File]::ReadAllText('<SKILL_DIR>\references\sap_se01_remove $content = $content -replace '%%TRANSPORT%%','THE_TR' $content = $content -replace '%%OBJECTS%%','THE_OBJECTS' $content = $content -replace '%%SESSION_LOCK_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_session_lock.vbs' -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# BAKE the session path (attach Strategy 1) -- an env var exported by this +# generator process never reaches the separate process that runs cscript. . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se01_remove_objects_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace `THE_TR`, `THE_OBJECTS` (the comma-separated list, or empty for remove-ALL), and `<SKILL_DIR>` / `{WORK_TEMP}` / `{RUN_TEMP}` with absolute @@ -805,7 +836,14 @@ empty only for a deliberate operator-confirmed remove-ALL. Run: ```bash powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se01_remove_objects_run.ps1" -C:/Windows/SysWOW64/cscript.exe //NoLogo {RUN_TEMP}\sap_se01_remove_objects_run.vbs +``` + +Declare the GUI target in the SAME block as cscript, so object entries are only +ever unassigned from the TR on the system the RFC leg resolved: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:/Windows/SysWOW64/cscript.exe' //NoLogo '{RUN_TEMP}\sap_se01_remove_objects_run.vbs' ``` ## X5 — Interpret VBS output diff --git a/plugins/sap-dev-core/skills/sap-se11/SKILL.md b/plugins/sap-dev-core/skills/sap-se11/SKILL.md index 4a56a57..25594d0 100644 --- a/plugins/sap-dev-core/skills/sap-se11/SKILL.md +++ b/plugins/sap-dev-core/skills/sap-se11/SKILL.md @@ -448,14 +448,20 @@ Write `{RUN_TEMP}\sap_se11_check_run.ps1`: $content = [System.IO.File]::ReadAllText('<SKILL_DIR>\references\sap_se11_check.vbs', [System.Text.Encoding]::UTF8) $content = $content -replace '%%OBJECT_TYPE%%','THE_OBJECT_TYPE' $content = $content -replace '%%OBJECT_NAME%%','THE_OBJECT_NAME' -# Phase 3.5 session-attach plumbing. -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# Phase 4.2 session-attach plumbing. BAKE the resolved session path into the VBS +# (%%SESSION_PATH%% = attach Strategy 1) rather than exporting it as +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that later launches cscript, so an env var set here dies with it and the attach +# lib silently falls through to its sole-connection default (Strategy 3) -- which +# is how a run pinned to one SAP system drove another one (2026-08-06). A baked-in +# const survives the process boundary; an empty value still falls through exactly +# as before. . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se11_check_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace `THE_OBJECT_TYPE` with one of: `TABLE`, `VIEW`, `DATATYPE`, `TYPEGROUP`, `DOMAIN`, `SEARCHHELP`, `LOCKOBJECT`. @@ -482,8 +488,17 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se11_check_run.ps1" ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_se11_check_run.vbs +Run as one PowerShell block — the target expectation MUST be declared in the +SAME process that launches cscript (the attach lib reads it from the process +environment, and cscript is a child of THIS PowerShell, not of the generator +block above). It resolves the same profile `Connect-SapRfc` uses, so a GUI +sitting on a different system than the RFC leg is refused loud instead of +silently answering EXIST/NOT_EXIST about the wrong system: + +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_se11_check_run.vbs' ``` **Parse the last line of output:** @@ -740,9 +755,17 @@ $content = $content -replace '%%TEMP_DIR%%','{RUN_TEMP}' # ignore the token if it isn't present in their template). $content = $content -replace '%%ENHANCEMENT_CATEGORY%%','THE_ENH_CATEGORY' $content = $content -replace '%%ENH_CATEGORY_VBS%%','<SKILL_DIR>\references\sap_se11_set_enh_category.vbs' -# Phase 3.5 session-attach plumbing. -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath +# Phase 4.2 session-attach plumbing. BAKE the resolved session path into the VBS +# (%%SESSION_PATH%% = attach Strategy 1) rather than exporting it as +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that later launches cscript, so an env var set here dies with it and the attach +# lib silently falls through to its sole-connection default (Strategy 3) -- which +# is how a run pinned to one SAP system WROTE to another (2026-08-06). A baked-in +# const survives the process boundary; an empty value still falls through exactly +# as before. +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' # Post-activate RFC verify plumbing (parity with create, Step 5b). The DOMAIN, # STRUCTURE and TABLETYPE update templates shell out to the verify PS1 AFTER @@ -751,10 +774,8 @@ $content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scr # success. Other update templates ignore these tokens. $content = $content -replace '%%POST_ACTIVATE_VERIFY_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_se11_post_activate_verify.vbs' $content = $content -replace '%%POST_ACTIVATE_VERIFY_PS1%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_se11_post_activate_verify.ps1' -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se11_update_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace `<TYPE>` with the lowercase object type (table, view, dataelement, structure, tabletype, typegroup, domain, searchhelp, lockobject). Replace all `THE_*` placeholders and `<SKILL_DIR>`. `THE_PACKAGE` and `THE_TRANSPORT` are optional — use empty string for local object. @@ -802,6 +823,13 @@ watcher is skipped once a rule has been persisted. Run as one PowerShell block ```powershell $shared = '<SAP_DEV_CORE_SHARED_DIR>\scripts' $log = '{RUN_TEMP}\THE_OBJECT_NAME.activation_log.txt' # path the activation-log save would write +# 0. Declare which SAP system the GUI leg may WRITE to. MUST happen in THIS block: +# the attach lib reads it from the process environment, and cscript is a child +# of THIS PowerShell, not of the generator block above. It resolves the same +# profile Connect-SapRfc uses, so a GUI parked on a different system than the +# RFC leg is refused instead of having ITS namesake DDIC object overwritten. +. "$shared\sap_connection_lib.ps1" +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null # 1. Pre-check the allow-list (read-only; lets us skip the watcher once a rule exists). & "$shared\sap_gui_security_precheck.ps1" -Path $log -Access w -System 'THE_SID' -Client 'THE_CLIENT' -Transaction 'SE11' | Out-Host $allowed = ($LASTEXITCODE -eq 0) @@ -906,9 +934,17 @@ $content = $content -replace '%%VIEW_TYPE%%','THE_VIEW_TYPE' Then write: ```powershell -# Phase 3.5 session-attach plumbing. -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath +# Phase 4.2 session-attach plumbing. BAKE the resolved session path into the VBS +# (%%SESSION_PATH%% = attach Strategy 1) rather than exporting it as +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that later launches cscript, so an env var set here dies with it and the attach +# lib silently falls through to its sole-connection default (Strategy 3) -- which +# is how a run pinned to one SAP system WROTE to another (2026-08-06). A baked-in +# const survives the process boundary; an empty value still falls through exactly +# as before. +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' # Phase 4.3 post-activate RFC verify plumbing. The VBS shells out to the PS1 # AFTER activation; the PS1 reads the AI-session pinned connection from @@ -920,10 +956,8 @@ $content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scr # as SUCCESS_UNVERIFIED (not SUCCESS) and suggest /sap-dev-status. $content = $content -replace '%%POST_ACTIVATE_VERIFY_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_se11_post_activate_verify.vbs' $content = $content -replace '%%POST_ACTIVATE_VERIFY_PS1%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_se11_post_activate_verify.ps1' -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se11_create_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace all `THE_*` placeholders, `<TYPE>`, and `<SKILL_DIR>`. `THE_PACKAGE` and `THE_TRANSPORT` are optional — use empty string for local object. @@ -954,6 +988,13 @@ watcher is skipped once a rule has been persisted. Run as one PowerShell block ```powershell $shared = '<SAP_DEV_CORE_SHARED_DIR>\scripts' $log = '{RUN_TEMP}\THE_OBJECT_NAME.activation_log.txt' # path the activation-log save would write +# 0. Declare which SAP system the GUI leg may WRITE to. MUST happen in THIS block: +# the attach lib reads it from the process environment, and cscript is a child +# of THIS PowerShell, not of the generator block above. It resolves the same +# profile Connect-SapRfc uses, so a GUI parked on a different system than the +# RFC leg is refused instead of receiving this DDIC object. +. "$shared\sap_connection_lib.ps1" +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null # 1. Pre-check the allow-list (read-only; lets us skip the watcher once a rule exists). & "$shared\sap_gui_security_precheck.ps1" -Path $log -Access w -System 'THE_SID' -Client 'THE_CLIENT' -Transaction 'SE11' | Out-Host $allowed = ($LASTEXITCODE -eq 0) @@ -1120,14 +1161,20 @@ $content = $content -replace '%%OBJECT_NAME%%','THE_OBJECT_NAME' $content = $content -replace '%%PACKAGE%%','THE_PACKAGE' $content = $content -replace '%%TRANSPORT%%','THE_TRANSPORT' $content = $content -replace '%%SESSION_LOCK_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_session_lock.vbs' -# Phase 3.5 session-attach plumbing. -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# Phase 4.2 session-attach plumbing. BAKE the resolved session path into the VBS +# (%%SESSION_PATH%% = attach Strategy 1) rather than exporting it as +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that later launches cscript, so an env var set here dies with it and the attach +# lib silently falls through to its sole-connection default (Strategy 3) -- which +# is how a run pinned to one SAP system WROTE to another (2026-08-06). A baked-in +# const survives the process boundary; an empty value still falls through exactly +# as before. . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se11_chgpkg_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace `THE_OBJECT_TYPE` (one of TABL/VIEW/DTEL/TTYP/DOMA/SHLP/ENQU), `THE_OBJECT_NAME` (UPPERCASE), `THE_PACKAGE`, `THE_TRANSPORT` (empty string to create a new request), and @@ -1140,8 +1187,16 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se11_chgpkg_run.ps1" ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_se11_chgpkg_run.vbs +Run as one PowerShell block — the target expectation MUST be declared in the +SAME process that launches cscript (the attach lib reads it from the process +environment, and cscript is a child of THIS PowerShell, not of the generator +block above), so a GUI parked on a different system than the RFC leg is refused +instead of having ITS namesake object reassigned (2026-08-06): + +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_se11_chgpkg_run.vbs' ``` **On success** (output contains `SUCCESS:`): report the package change to the user. @@ -1237,14 +1292,19 @@ $content = $content -replace '%%TRANSPORT%%','THE_TRANSPORT' $content = $content -replace '%%PACKAGE%%','THE_OBJDIR_PACKAGE' $content = $content -replace '%%ORIG_LANG%%','THE_OBJDIR_LANG' $content = $content -replace '%%SESSION_LOCK_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_session_lock.vbs' -# Phase 3.5 session-attach plumbing. -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# Phase 4.2 session-attach plumbing. BAKE the resolved session path into the VBS +# (%%SESSION_PATH%% = attach Strategy 1) rather than exporting it as +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that later launches cscript, so an env var set here dies with it and the attach +# lib silently falls through to its sole-connection default (Strategy 3) -- which +# is how a run pinned to one SAP system drove another one (2026-08-06). Delete is +# irreversible, so a silent retarget here destroys the WRONG system's object. . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se11_delete_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace `THE_OBJECT_TYPE`, `THE_OBJECT_NAME`, `THE_TRANSPORT`, and `<SKILL_DIR>`. @@ -1255,8 +1315,17 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se11_delete_run.ps1" ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_se11_delete_run.vbs +Run as one PowerShell block — the target expectation MUST be declared in the +SAME process that launches cscript (the attach lib reads it from the process +environment, and cscript is a child of THIS PowerShell, not of the generator +block above). Deletion is irreversible, so refusing a GUI parked on a different +system than the RFC leg is the difference between deleting the intended DDIC +object and destroying its namesake on another system (2026-08-06): + +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_se11_delete_run.vbs' ``` ### Behaviour Notes diff --git a/plugins/sap-dev-core/skills/sap-se14/SKILL.md b/plugins/sap-dev-core/skills/sap-se14/SKILL.md index 56e56eb..c41a7c0 100644 --- a/plugins/sap-dev-core/skills/sap-se14/SKILL.md +++ b/plugins/sap-dev-core/skills/sap-se14/SKILL.md @@ -92,24 +92,34 @@ CONVERSION_TERMINATED state, which cannot be safely manufactured — see Scope). guessed click). **`adjust`** — substitute the 5 tokens and run the recorded save-data-only driver via **32-bit -cscript**. Mirror the parallel-safe attach contract (set `SAPDEV_SESSION_PATH`, keep the base -`{WORK_TEMP}` for `Get-SapCurrentSessionPath`, write the runtime VBS to `{RUN_TEMP}`, UTF-16 LE BOM): +cscript**. Mirror the parallel-safe attach contract (BAKE the resolved path into +`%%SESSION_PATH%%`, keep the base `{WORK_TEMP}` for `Get-SapCurrentSessionPath`, write the runtime +VBS to `{RUN_TEMP}`, UTF-16 LE BOM): ```powershell $shared = '<SAP_DEV_CORE_SHARED_DIR>\scripts' . "$shared\sap_connection_lib.ps1" -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +# BAKE the path (attach Strategy 1) rather than exporting $env:SAPDEV_SESSION_PATH: +# this generator is a SEPARATE process from the one that runs cscript, so the env +# var would already be gone and the attach lib would silently fall through to its +# sole-connection default (2026-08-06). +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' # or the --session value $vbs = [IO.File]::ReadAllText('<SKILL_DIR>\references\sap_se14_adjust.vbs', [Text.Encoding]::UTF8) $vbs = $vbs.Replace('%%TABLE%%', '<T>') # upper-cased table name $vbs = $vbs.Replace('%%OUTPUT_FILE%%', '{RUN_TEMP}\se14_adjust.json') -$vbs = $vbs.Replace('%%SESSION_PATH%%', '') # or the --session value +$vbs = $vbs.Replace('%%SESSION_PATH%%', $sessionPath) $vbs = $vbs.Replace('%%ATTACH_LIB_VBS%%', "$shared\sap_attach_lib.vbs") $vbs = $vbs.Replace('%%SESSION_LOCK_VBS%%', "$shared\sap_session_lock.vbs") [IO.File]::WriteAllText('{RUN_TEMP}\se14_adjust_run.vbs', $vbs, [System.Text.UnicodeEncoding]::new($false, $true)) ``` -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo "{RUN_TEMP}\se14_adjust_run.vbs" +Declare the GUI target in the SAME block as cscript — the attach lib reads +`SAPDEV_EXPECT_SYSTEM`/`_CLIENT` from the process environment, and an SE14 adjust +is a data-affecting write, so landing on the wrong system is not recoverable: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo "{RUN_TEMP}\se14_adjust_run.vbs" ``` Parse the `SE14:` / `STATUS:` lines (and read `{RUN_TEMP}\se14_adjust.json` = diff --git a/plugins/sap-dev-core/skills/sap-se16n/SKILL.md b/plugins/sap-dev-core/skills/sap-se16n/SKILL.md index 1da31d7..6144fc6 100644 --- a/plugins/sap-dev-core/skills/sap-se16n/SKILL.md +++ b/plugins/sap-dev-core/skills/sap-se16n/SKILL.md @@ -204,16 +204,18 @@ $content = $content -replace '%%OUTPUT_FILE%%','{RUN_TEMP}\se16n_THE_TABLE.txt' # 2. SAPDEV_SESSION_PATH env var # 3. Sole-connection + sole-session auto-default # 4. Refuse with helpful error (multiple connections, no resolver) +# Resolve the AI-session's pin here and BAKE it into %%SESSION_PATH%% (Strategy 1). +# Do NOT export it as $env:SAPDEV_SESSION_PATH instead (Strategy 2): this generator +# is a SEPARATE process from the one that runs cscript, so the env var would +# already be gone and the helper would silently fall through to Strategy 3 -- +# which on a machine with two SAP GUIs open reads the wrong system (2026-08-06). +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' $sessionPath = '' # set to the parsed --session value if supplied -$content = $content -replace '%%SESSION_PATH%%', $sessionPath +if (-not $sessionPath) { $sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' } +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' -# Resolve the AI-session's pinned session path and pass via env var so -# AttachSapSession's Strategy 2 picks it up. Falls back to sole-connection -# default for the single-conn case. -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se16n_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace `THE_TABLE` with the actual table name (UPPERCASE) and `<SKILL_DIR>` / `{WORK_TEMP}` with their absolute paths. @@ -236,6 +238,13 @@ system / client: ```powershell $shared = '<SAP_DEV_CORE_SHARED_DIR>\scripts' $out = '{RUN_TEMP}\se16n_THE_TABLE.txt' +# 0. Declare which SAP system the GUI leg may read. MUST happen in THIS block: +# the attach lib reads it from the process environment, and cscript is a child +# of THIS PowerShell. Table data with no provenance is exactly the 2026-08-06 +# failure mode -- a dump labelled "THE_TABLE on <SID>" that came from another +# system is worse than no dump at all. +. "$shared\sap_connection_lib.ps1" +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null # 1. Pre-check the allow-list (read-only; informational + lets us skip the watcher). & "$shared\sap_gui_security_precheck.ps1" -Path $out -Access w -System 'THE_SID' -Client 'THE_CLIENT' -Transaction 'SE16N' | Out-Host $allowed = ($LASTEXITCODE -eq 0) @@ -374,22 +383,27 @@ surfaces (it is always present; the flag only documents intent). (`AGG` rows are `FIELD<TAB>FUNC`; `FILTER` rows are `FIELD<TAB>OP<TAB>value…` with SE16N operators EQ/NE/GT/LT/GE/LE/BT/NB/CP/NP/IN.) 3. **Fill + run the VBS** (same token/encoding idiom as Step 4 of the normal flow — - resolve `$sessionPath`, set `$env:SAPDEV_SESSION_PATH`, write UTF-16LE, run + resolve `$sessionPath` and BAKE it into `%%SESSION_PATH%%`, write UTF-16LE, run 32-bit cscript): ```powershell + . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' + $sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' $c = [System.IO.File]::ReadAllText('<SKILL_DIR>\references\sap_se16h_agg.vbs', [System.Text.Encoding]::UTF8) $c = $c -replace '%%TABLE_NAME%%','<TABLE>' $c = $c -replace '%%PARAMS_FILE%%','{RUN_TEMP}\se16h_agg_params.txt' $c = $c -replace '%%OUTPUT_FILE%%','{RUN_TEMP}\se16h_agg_<TABLE>.txt' $c = $c -replace '%%MAX_GROUPS%%','100000' $c = $c -replace '%%MIN_COUNT%%','' - $c = $c -replace '%%SESSION_PATH%%', $sessionPath + $c = $c.Replace('%%SESSION_PATH%%', $sessionPath) $c = $c -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' - $env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se16h_agg_run.vbs', $c, [System.Text.UnicodeEncoding]::new($false,$true)) ``` - ```bash - C:/Windows/SysWOW64/cscript.exe //NoLogo '{RUN_TEMP}\sap_se16h_agg_run.vbs' + Declare the GUI target in the SAME block as cscript, so the aggregate is read + from the system the RFC leg resolved and the `GUI_TARGET:` line proves it: + ```powershell + . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' + Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null + & 'C:/Windows/SysWOW64/cscript.exe' //NoLogo '{RUN_TEMP}\sap_se16h_agg_run.vbs' ``` 4. **Parse the last stdout lines** and report: - `SE16H_AGG: table=.. groups=<n> cols=<c> truncated=<0|1> group_by=[..] agg=[..]` diff --git a/plugins/sap-dev-core/skills/sap-se19/SKILL.md b/plugins/sap-dev-core/skills/sap-se19/SKILL.md index 6e1dd0b..57566ae 100644 --- a/plugins/sap-dev-core/skills/sap-se19/SKILL.md +++ b/plugins/sap-dev-core/skills/sap-se19/SKILL.md @@ -230,18 +230,31 @@ $c = $c -replace '%%TRKORR%%','<TRKORR-or-empty>' # default '' => accept pre-filled package / Local Object; '' lang => VBS uses 'E'). $c = $c -replace '%%PACKAGE%%','<OBJDIR_PKG-or-empty>' $c = $c -replace '%%ORIG_LANG%%','<OBJDIR_LANG-or-empty>' -# --- shared attach plumbing (Phase 3.5 / 4.2) --- -$c = $c -replace '%%SESSION_PATH%%','' -$c = $c -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# --- shared attach plumbing (Phase 4.2) --- +# BAKE the resolved path into %%SESSION_PATH%% (attach Strategy 1): this generator +# is a SEPARATE process from the one that runs cscript, so an +# $env:SAPDEV_SESSION_PATH exported here dies with it and the attach lib silently +# falls through to its sole-connection default (2026-08-06). . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$c = $c.Replace('%%SESSION_PATH%%', $sessionPath) +$c = $c -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\se19_run.vbs', $c, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` ```bash powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\se19_run.ps1" -C:/Windows/SysWOW64/cscript.exe //NoLogo {RUN_TEMP}\se19_run.vbs +``` + +Declare the GUI target in the SAME block as cscript — the attach lib reads +`SAPDEV_EXPECT_SYSTEM`/`_CLIENT` from the process environment, so a BAdI +implementation can never be written to a system other than the one the RFC leg +resolved: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:/Windows/SysWOW64/cscript.exe' //NoLogo '{RUN_TEMP}\se19_run.vbs' ``` Always write the VBS with **`-Encoding Unicode`** (UTF-16 LE) — cscript needs it. diff --git a/plugins/sap-dev-core/skills/sap-se21/SKILL.md b/plugins/sap-dev-core/skills/sap-se21/SKILL.md index 55d4326..d48a36d 100644 --- a/plugins/sap-dev-core/skills/sap-se21/SKILL.md +++ b/plugins/sap-dev-core/skills/sap-se21/SKILL.md @@ -230,14 +230,20 @@ $content = $content.Replace('%%PACKAGE%%', 'THE_PACKAGE') $content = $content.Replace('%%DESCRIPTION%%', 'THE_DESCRIPTION') $content = $content.Replace('%%TRANSPORT%%', 'THE_TRANSPORT') $content = $content.Replace('%%SESSION_LOCK_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_session_lock.vbs') -# Phase 3.5 session-attach plumbing. -$sessionPath = '' +# Phase 4.2 session-attach plumbing. BAKE the resolved session path into the VBS +# (%%SESSION_PATH%% = attach Strategy 1) rather than exporting it as +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that later launches cscript, so an env var set here dies with it and the attach +# lib silently falls through to its sole-connection default (Strategy 3) -- which +# is how a run pinned to one SAP system WROTE to another (2026-08-06). A baked-in +# const survives the process boundary; an empty value still falls through exactly +# as before. +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' $content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content.Replace('%%ATTACH_LIB_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs') -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se21_filled.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` The `%%SESSION_LOCK_VBS%%` token is required because `sap_se21_create.vbs` @@ -257,9 +263,18 @@ Run: powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se21_run.ps1" ``` -Execute via the 32-bit cscript host (bare `cscript` resolves to the 64-bit host; SAP GUI COM needs 32-bit — see CLAUDE.md / feedback_sap_gui_vbs_must_be_32bit): -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo "{RUN_TEMP}\sap_se21_filled.vbs" +Execute via the 32-bit cscript host (bare `cscript` resolves to the 64-bit host; +SAP GUI COM needs 32-bit — see CLAUDE.md / feedback_sap_gui_vbs_must_be_32bit), +as ONE PowerShell block: the target expectation MUST be declared in the SAME +process that launches cscript (the attach lib reads it from the process +environment, and cscript is a child of THIS PowerShell, not of the generator +block above), so a GUI parked on a different system than the RFC leg is refused +instead of receiving this package (2026-08-06): + +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo "{RUN_TEMP}\sap_se21_filled.vbs" ``` --- @@ -419,21 +434,38 @@ $content = $content.Replace('%%TRANSPORT%%', 'THE_TRANSPORT') $content = $content.Replace('%%PACKAGE2%%', '') $content = $content.Replace('%%ORIG_LANG%%', '') $content = $content.Replace('%%SESSION_LOCK_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_session_lock.vbs') -# Phase 3.5 session-attach plumbing. -$sessionPath = '' +# Phase 4.2 session-attach plumbing. BAKE the resolved session path into the VBS +# (%%SESSION_PATH%% = attach Strategy 1) rather than exporting it as +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that later launches cscript, so an env var set here dies with it and the attach +# lib silently falls through to its sole-connection default (Strategy 3) -- which +# is how a run pinned to one SAP system drove another one (2026-08-06). Delete is +# irreversible, so a silent retarget here destroys the WRONG system's package. +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' $content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content.Replace('%%ATTACH_LIB_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs') -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se21_delete_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` -Run: +Run the generator: ```bash powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se21_delete_run.ps1" -C:\Windows\SysWOW64\cscript.exe //NoLogo "{RUN_TEMP}\sap_se21_delete_run.vbs" +``` + +Then execute as ONE PowerShell block — the target expectation MUST be declared in +the SAME process that launches cscript (the attach lib reads it from the process +environment, and cscript is a child of THIS PowerShell, not of the generator +above). Deletion is irreversible, so refusing a GUI parked on a different system +than the RFC leg is the difference between deleting the intended package and +destroying its namesake on another system (2026-08-06): + +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo "{RUN_TEMP}\sap_se21_delete_run.vbs" ``` ### Behaviour Notes diff --git a/plugins/sap-dev-core/skills/sap-se24/SKILL.md b/plugins/sap-dev-core/skills/sap-se24/SKILL.md index c024a45..eaf79ea 100644 --- a/plugins/sap-dev-core/skills/sap-se24/SKILL.md +++ b/plugins/sap-dev-core/skills/sap-se24/SKILL.md @@ -239,14 +239,20 @@ Write `{RUN_TEMP}\sap_se24_check_run.ps1`: ```powershell $content = [System.IO.File]::ReadAllText('<SKILL_DIR>\references\sap_se24_check.vbs', [System.Text.Encoding]::UTF8) $content = $content -replace '%%CLASS_NAME%%','THE_CLASS_NAME' -# Phase 3.5 session-attach plumbing. -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# Phase 4.2 session-attach plumbing. BAKE the resolved session path into the VBS +# (%%SESSION_PATH%% = attach Strategy 1) rather than exporting it as +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that later launches cscript, so an env var set here dies with it and the attach +# lib silently falls through to its sole-connection default (Strategy 3) -- which +# is how a run pinned to one SAP system drove another one (2026-08-06). A baked-in +# const survives the process boundary; an empty value still falls through exactly +# as before. . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se24_check_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace `THE_CLASS_NAME` with the actual class name (UPPERCASE) and `<SKILL_DIR>` with the absolute path to this skill directory. @@ -257,8 +263,17 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se24_check_run.ps1" ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_se24_check_run.vbs +Run as one PowerShell block — the target expectation MUST be declared in the +SAME process that launches cscript (the attach lib reads it from the process +environment, and cscript is a child of THIS PowerShell, not of the generator +block above). It resolves the same profile `Connect-SapRfc` uses, so a GUI +sitting on a different system than the RFC leg is refused loud instead of +silently answering EXIST/NOT_EXIST about the wrong system: + +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_se24_check_run.vbs' ``` **Parse the last line of output:** @@ -423,13 +438,20 @@ $content = $content -replace '%%TRANSPORT%%','THE_TRANSPORT' $content = $content -replace '%%SESSION_LOCK_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_session_lock.vbs' # Locale-aware syntax-check classifier (Ctrl+F2 grid MSGTYPE match for ZH/JA/DE logons). $content = $content -replace '%%SYNTAX_CHECK_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_syntax_check_lib.vbs' -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# Phase 4.2 session-attach plumbing. BAKE the resolved session path into the VBS +# (%%SESSION_PATH%% = attach Strategy 1) rather than exporting it as +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that later launches cscript, so an env var set here dies with it and the attach +# lib silently falls through to its sole-connection default (Strategy 3) -- which +# is how a run pinned to one SAP system WROTE to another (2026-08-06). A baked-in +# const survives the process boundary; an empty value still falls through exactly +# as before. . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se24_update_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace `THE_CLASS_NAME` (UPPERCASE), `THE_SOURCE_PATH` (absolute path with backslashes), `THE_PACKAGE` and `THE_TRANSPORT` (blank if local $TMP), and `<SKILL_DIR>`. @@ -455,6 +477,13 @@ GUI will read — the same value used for `%%ABAP_SOURCE_FILE%%`) and `THE_SID` ```powershell $shared = '<SAP_DEV_CORE_SHARED_DIR>\scripts' $src = 'THE_SOURCE_PATH' # the local file SAP GUI's Upload menu will read +# 0. Declare which SAP system the GUI leg may WRITE to. MUST happen in THIS block: +# the attach lib reads it from the process environment, and cscript is a child +# of THIS PowerShell, not of the generator block above. It resolves the same +# profile Connect-SapRfc uses, so a GUI parked on a different system than the +# RFC leg is refused instead of receiving this class's source (2026-08-06). +. "$shared\sap_connection_lib.ps1" +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null # 1. Pre-check the allow-list (read-only; informational + lets us skip the watcher). & "$shared\sap_gui_security_precheck.ps1" -Path $src -Access r -System 'THE_SID' -Client 'THE_CLIENT' -Transaction 'SE24' | Out-Host $allowed = ($LASTEXITCODE -eq 0) @@ -527,13 +556,20 @@ $content = $content -replace '%%TRANSPORT%%','THE_TRANSPORT' $content = $content -replace '%%CLASS_KIND%%','THE_CLASS_KIND' $content = $content -replace '%%WITH_MESSAGE%%','THE_WITH_MESSAGE' $content = $content -replace '%%SESSION_LOCK_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_session_lock.vbs' -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# Phase 4.2 session-attach plumbing. BAKE the resolved session path into the VBS +# (%%SESSION_PATH%% = attach Strategy 1) rather than exporting it as +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that later launches cscript, so an env var set here dies with it and the attach +# lib silently falls through to its sole-connection default (Strategy 3) -- which +# is how a run pinned to one SAP system WROTE to another (2026-08-06). A baked-in +# const survives the process boundary; an empty value still falls through exactly +# as before. . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se24_create_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace all `THE_*` placeholders (PACKAGE/TRANSPORT blank if local $TMP) and `<SKILL_DIR>`. Set `THE_CLASS_KIND` = `NORMAL` or `EXCEPTION` per the rule above, and @@ -546,8 +582,16 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se24_create_run.ps1" ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_se24_create_run.vbs +Run as one PowerShell block — the target expectation MUST be declared in the +SAME process that launches cscript (the attach lib reads it from the process +environment, and cscript is a child of THIS PowerShell, not of the generator +block above), so a GUI parked on a different system than the RFC leg is refused +instead of receiving this class (2026-08-06): + +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_se24_create_run.vbs' ``` **On success** (output contains `SUCCESS:`): @@ -644,13 +688,20 @@ $content = $content -replace '%%TEST_SOURCE_FILE%%','THE_TEST_SOURCE_PATH' $content = $content -replace '%%TRANSPORT%%','THE_TRANSPORT' $content = $content -replace '%%SESSION_LOCK_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_session_lock.vbs' $content = $content -replace '%%SYNTAX_CHECK_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_syntax_check_lib.vbs' -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# Phase 4.2 session-attach plumbing. BAKE the resolved session path into the VBS +# (%%SESSION_PATH%% = attach Strategy 1) rather than exporting it as +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that later launches cscript, so an env var set here dies with it and the attach +# lib silently falls through to its sole-connection default (Strategy 3) -- which +# is how a run pinned to one SAP system WROTE to another (2026-08-06). A baked-in +# const survives the process boundary; an empty value still falls through exactly +# as before. . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se24_test_classes_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace `THE_CLASS_NAME` (UPPERCASE), `THE_TEST_SOURCE_PATH` (absolute path with backslashes), `THE_TRANSPORT` (blank if `$TMP`), and `<SKILL_DIR>`. The test @@ -666,6 +717,12 @@ with the OS-level watcher — same pattern as Step A. Substitute `THE_SID` / ```powershell $shared = '<SAP_DEV_CORE_SHARED_DIR>\scripts' $src = 'THE_TEST_SOURCE_PATH' # the path SAP GUI will read +# Declare which SAP system the GUI leg may WRITE to. MUST happen in THIS block: +# the attach lib reads it from the process environment, and cscript is a child of +# THIS PowerShell, not of the generator block above -- so a GUI parked on a +# different system than the RFC leg is refused, not written to (2026-08-06). +. "$shared\sap_connection_lib.ps1" +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null & "$shared\sap_gui_security_precheck.ps1" -Path $src -Access r -System 'THE_SID' -Client 'THE_CLIENT' -Transaction 'SE24' | Out-Host $allowed = ($LASTEXITCODE -eq 0) $watcher = $null @@ -773,14 +830,20 @@ $content = $content.Replace('%%STATUS%%', 'THE_STATUS') $content = $content.Replace('%%CATEGORY%%', 'THE_CATEGORY') $content = $content.Replace('%%TRANSPORT%%', 'THE_TRANSPORT') $content = $content.Replace('%%SESSION_LOCK_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_session_lock.vbs') -# Phase 3.5 session-attach plumbing. -$sessionPath = '' +# Phase 4.2 session-attach plumbing. BAKE the resolved session path into the VBS +# (%%SESSION_PATH%% = attach Strategy 1) rather than exporting it as +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that later launches cscript, so an env var set here dies with it and the attach +# lib silently falls through to its sole-connection default (Strategy 3) -- which +# is how a run pinned to one SAP system WROTE to another (2026-08-06). A baked-in +# const survives the process boundary; an empty value still falls through exactly +# as before. +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' $content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content.Replace('%%ATTACH_LIB_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs') -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se24_change_props_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Use `.Replace()` (literal) for `STATUS`/`CATEGORY`/`TRANSPORT` — description text may contain regex metacharacters (e.g. colons, dots), which is why every @@ -795,8 +858,16 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se24_change_props_run.p ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_se24_change_props_run.vbs +Run as one PowerShell block — the target expectation MUST be declared in the +SAME process that launches cscript (the attach lib reads it from the process +environment, and cscript is a child of THIS PowerShell, not of the generator +block above), so a GUI parked on a different system than the RFC leg is refused +instead of having ITS namesake class's properties rewritten (2026-08-06): + +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_se24_change_props_run.vbs' ``` ### Behaviour Notes @@ -920,13 +991,19 @@ $content = $content.Replace('%%TRANSPORT%%', 'THE_TRANSPORT') $content = $content.Replace('%%PACKAGE%%', 'THE_OBJDIR_PACKAGE') $content = $content.Replace('%%ORIG_LANG%%', 'THE_OBJDIR_LANG') $content = $content.Replace('%%SESSION_LOCK_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_session_lock.vbs') -$sessionPath = '' +# Phase 4.2 session-attach plumbing. BAKE the resolved session path into the VBS +# (%%SESSION_PATH%% = attach Strategy 1) rather than exporting it as +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that later launches cscript, so an env var set here dies with it and the attach +# lib silently falls through to its sole-connection default (Strategy 3) -- which +# is how a run pinned to one SAP system drove another one (2026-08-06). Delete is +# irreversible, so a silent retarget here destroys the WRONG system's class. +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' $content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content.Replace('%%ATTACH_LIB_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs') -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se24_delete_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace `<SKILL_DIR>` and the `THE_*` placeholders. @@ -937,8 +1014,17 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se24_delete_run.ps1" ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_se24_delete_run.vbs +Run as one PowerShell block — the target expectation MUST be declared in the +SAME process that launches cscript (the attach lib reads it from the process +environment, and cscript is a child of THIS PowerShell, not of the generator +block above). Deletion is irreversible, so refusing a GUI parked on a different +system than the RFC leg is the difference between deleting the intended class +and destroying its namesake on another system (2026-08-06): + +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_se24_delete_run.vbs' ``` ### Behaviour Notes @@ -1102,15 +1188,21 @@ $workTemp = 'THE_WORK_TEMP' $content = [System.IO.File]::ReadAllText("$skillDir\references\sap_se24_check_and_download.vbs", [System.Text.Encoding]::UTF8) $content = $content -replace '%%CLASS_NAME%%', $className $content = $content -replace '%%OUTPUT_FILE%%', $outFile -# Phase 3.5 session-attach plumbing. -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath +# Phase 4.2 session-attach plumbing. BAKE the resolved session path into the VBS +# (%%SESSION_PATH%% = attach Strategy 1) rather than exporting it as +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that later launches cscript, so an env var set here dies with it and the attach +# lib silently falls through to its sole-connection default (Strategy 3) -- which +# is how a run pinned to one SAP system read ANOTHER one's source (2026-08-06). +# A baked-in const survives the process boundary; an empty value still falls +# through exactly as before. +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp $workTemp +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content -replace '%%ATTACH_LIB_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' $content = $content -replace '%%SYNTAX_CHECK_LIB_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_syntax_check_lib.vbs' -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp $workTemp [System.IO.File]::WriteAllText("{RUN_TEMP}\sap_se24_check_and_download_run.vbs", $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` | Placeholder | Value | @@ -1139,6 +1231,14 @@ system / client: ```powershell $shared = '<SAP_DEV_CORE_SHARED_DIR>\scripts' $out = '{RUN_TEMP}\THE_CLASS_NAME_from_sap.txt' # the path SAP GUI will write +# 0. Declare which SAP system the GUI leg may drive. MUST happen in THIS block: +# the attach lib reads it from the process environment, and cscript is a child +# of THIS PowerShell, not of the generator block above. It resolves the same +# profile Connect-SapRfc uses, so if the GUI is sitting on a different system +# than the RFC readers target, the attach refuses loud instead of downloading +# another system's source under this class's name (2026-08-06). +. "$shared\sap_connection_lib.ps1" +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null # 1. Pre-check the allow-list (read-only; informational + lets us skip the watcher). & "$shared\sap_gui_security_precheck.ps1" -Path $out -Access w -System 'THE_SID' -Client 'THE_CLIENT' -Transaction 'SE24' | Out-Host $allowed = ($LASTEXITCODE -eq 0) diff --git a/plugins/sap-dev-core/skills/sap-se37/SKILL.md b/plugins/sap-dev-core/skills/sap-se37/SKILL.md index d65455e..9a0c2ad 100644 --- a/plugins/sap-dev-core/skills/sap-se37/SKILL.md +++ b/plugins/sap-dev-core/skills/sap-se37/SKILL.md @@ -246,14 +246,20 @@ Write `{RUN_TEMP}\sap_se37_check_run.ps1`: ```powershell $content = [System.IO.File]::ReadAllText('<SKILL_DIR>\references\sap_se37_check.vbs', [System.Text.Encoding]::UTF8) $content = $content -replace '%%FM_NAME%%','THE_FM_NAME' -# Phase 3.5 session-attach plumbing. -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# Phase 4.2 session-attach plumbing. BAKE the resolved session path into the VBS +# (%%SESSION_PATH%% = attach Strategy 1) rather than exporting it as +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that later launches cscript, so an env var set here dies with it and the attach +# lib silently falls through to its sole-connection default (Strategy 3) -- which +# is how a run pinned to one SAP system drove another one (2026-08-06). A baked-in +# const survives the process boundary; an empty value still falls through exactly +# as before. . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se37_check_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace `THE_FM_NAME` with the actual function module name (UPPERCASE) and `<SKILL_DIR>` with the absolute path to this skill directory. @@ -264,8 +270,17 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se37_check_run.ps1" ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_se37_check_run.vbs +Run as one PowerShell block — the target expectation MUST be declared in the +SAME process that launches cscript (the attach lib reads it from the process +environment, and cscript is a child of THIS PowerShell, not of the generator +block above). It resolves the same profile `Connect-SapRfc` uses, so a GUI +sitting on a different system than the RFC leg is refused loud instead of +silently answering EXIST/NOT_EXIST about the wrong system: + +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_se37_check_run.vbs' ``` **Parse the last line of output:** @@ -602,7 +617,16 @@ $content = $content.Replace('%%INTERFACE_CODE%%', $ifaceCode) $content = $content.Replace('%%SESSION_LOCK_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_session_lock.vbs') # Locale-aware syntax-check classifier (Ctrl+F2 grid MSGTYPE match for ZH/JA/DE logons). $content = $content.Replace('%%SYNTAX_CHECK_LIB_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_syntax_check_lib.vbs') -$sessionPath = '' +# Phase 4.2 session-attach plumbing. BAKE the resolved session path into the VBS +# (%%SESSION_PATH%% = attach Strategy 1) rather than exporting it as +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that later launches cscript, so an env var set here dies with it and the attach +# lib silently falls through to its sole-connection default (Strategy 3) -- which +# is how a run pinned to one SAP system WROTE to another (2026-08-06). A baked-in +# const survives the process boundary; an empty value still falls through exactly +# as before. +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp $workTemp $content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content.Replace('%%ATTACH_LIB_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs') # OS-level foreground guard for the clipboard paste (Source tab editor is loaded @@ -614,11 +638,9 @@ $content = $content.Replace('%%FOREGROUND_GUARD_PS1%%', '<SAP_DEV_CORE_SHARED_DI # can't run it emits WARNING: POST_ACTIVATE_VERIFY_UNAVAILABLE (see Step 6). $content = $content.Replace('%%POST_ACTIVATE_VERIFY_PS1%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_se37_post_activate_verify.ps1') $content = $content.Replace('%%POST_ACTIVATE_VERIFY_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_se11_post_activate_verify.vbs') -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp $workTemp [System.IO.File]::WriteAllText("{RUN_TEMP}\sap_se37_update_run.vbs", $content, [System.Text.UnicodeEncoding]::new($false, $true)) Write-Host "VBS written: {RUN_TEMP}\sap_se37_update_run.vbs" -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` > **Important**: Use `[System.IO.File]::WriteAllText(..., [System.Text.UnicodeEncoding]::new($false,$true))` @@ -649,9 +671,15 @@ reads the file itself, so this path does **not** trip the modal SAP GUI Security dialog and needs no OS-level watcher. It DOES need the OS-level **foreground guard** so Ctrl+V lands in SAP and not in whatever app the user is editing in — that guard is invoked from inside the VBS (via `%%FOREGROUND_GUARD_PS1%%`), so -just run the 32-bit cscript: +just run the 32-bit cscript — with the GUI target expectation declared in the +SAME block (the attach lib reads it from the process environment, and cscript is +a child of THIS PowerShell, not of the generator block above), so a GUI parked on +a different system than the RFC leg is refused instead of receiving this FM's +source (2026-08-06): ```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null & 'C:/Windows/SysWOW64/cscript.exe' //NoLogo '{RUN_TEMP}\sap_se37_update_run.vbs' ``` @@ -896,8 +924,17 @@ $content = $content.Replace('%%INTERFACE_CODE%%', $ifaceCode) $content = $content -replace '%%SESSION_LOCK_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_session_lock.vbs' # Locale-aware syntax-check classifier (Ctrl+F2 grid MSGTYPE match for ZH/JA/DE logons). $content = $content -replace '%%SYNTAX_CHECK_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_syntax_check_lib.vbs' -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath +# Phase 4.2 session-attach plumbing. BAKE the resolved session path into the VBS +# (%%SESSION_PATH%% = attach Strategy 1) rather than exporting it as +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that later launches cscript, so an env var set here dies with it and the attach +# lib silently falls through to its sole-connection default (Strategy 3) -- which +# is how a run pinned to one SAP system WROTE to another (2026-08-06). A baked-in +# const survives the process boundary; an empty value still falls through exactly +# as before. +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp $workTemp +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content -replace '%%ATTACH_LIB_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' # OS-level foreground guard for the clipboard paste (the Source tab editor is # loaded via clipboard + SendKeys now, not the S/4-only Utilities>Upload menu). @@ -909,11 +946,9 @@ $content = $content -replace '%%FOREGROUND_GUARD_PS1%%','<SAP_DEV_CORE_SHARED_DI # emits WARNING: POST_ACTIVATE_VERIFY_UNAVAILABLE (see Step 6). $content = $content -replace '%%POST_ACTIVATE_VERIFY_PS1%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_se37_post_activate_verify.ps1' $content = $content -replace '%%POST_ACTIVATE_VERIFY_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_se11_post_activate_verify.vbs' -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp $workTemp [System.IO.File]::WriteAllText("{RUN_TEMP}\sap_se37_create_run.vbs", $content, [System.Text.UnicodeEncoding]::new($false, $true)) Write-Host "VBS written: {RUN_TEMP}\sap_se37_create_run.vbs" -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Fill these placeholders before writing: @@ -937,8 +972,16 @@ Confirm the parse output matches the expected interface before proceeding. ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_se37_create_run.vbs +Run as one PowerShell block — the target expectation MUST be declared in the +SAME process that launches cscript (the attach lib reads it from the process +environment, and cscript is a child of THIS PowerShell, not of the generator +block above), so a GUI parked on a different system than the RFC leg is refused +instead of receiving this FM (2026-08-06): + +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_se37_create_run.vbs' ``` Proceed to Step 6 to evaluate the result. @@ -999,14 +1042,20 @@ $content = $content.Replace('%%PROCESSING_TYPE%%', 'THE_PROCESSING_TYPE') $content = $content.Replace('%%UPDATE_KIND%%', 'THE_UPDATE_KIND') $content = $content.Replace('%%TRANSPORT%%', 'THE_TRANSPORT') $content = $content.Replace('%%SESSION_LOCK_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_session_lock.vbs') -# Phase 3.5 session-attach plumbing. -$sessionPath = '' +# Phase 4.2 session-attach plumbing. BAKE the resolved session path into the VBS +# (%%SESSION_PATH%% = attach Strategy 1) rather than exporting it as +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that later launches cscript, so an env var set here dies with it and the attach +# lib silently falls through to its sole-connection default (Strategy 3) -- which +# is how a run pinned to one SAP system WROTE to another (2026-08-06). A baked-in +# const survives the process boundary; an empty value still falls through exactly +# as before. +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' $content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content.Replace('%%ATTACH_LIB_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs') -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se37_change_attrs_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Use `.Replace()` (literal) — short text may contain regex metacharacters. Replace `<SKILL_DIR>` and the `THE_*` placeholders. @@ -1018,8 +1067,16 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se37_change_attrs_run.p ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_se37_change_attrs_run.vbs +Run as one PowerShell block — the target expectation MUST be declared in the +SAME process that launches cscript (the attach lib reads it from the process +environment, and cscript is a child of THIS PowerShell, not of the generator +block above), so a GUI parked on a different system than the RFC leg is refused +instead of having ITS namesake FM's attributes rewritten (2026-08-06): + +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_se37_change_attrs_run.vbs' ``` ### Behaviour Notes @@ -1113,14 +1170,20 @@ $content = $content.Replace('%%FM_NAME%%', 'THE_FM_NAME') $content = $content.Replace('%%NEW_FUNC_GROUP%%', 'THE_NEW_FUNC_GROUP') $content = $content.Replace('%%TRANSPORT%%', 'THE_TRANSPORT') $content = $content.Replace('%%SESSION_LOCK_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_session_lock.vbs') -# Phase 3.5 session-attach plumbing. -$sessionPath = '' +# Phase 4.2 session-attach plumbing. BAKE the resolved session path into the VBS +# (%%SESSION_PATH%% = attach Strategy 1) rather than exporting it as +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that later launches cscript, so an env var set here dies with it and the attach +# lib silently falls through to its sole-connection default (Strategy 3) -- which +# is how a run pinned to one SAP system WROTE to another (2026-08-06). A baked-in +# const survives the process boundary; an empty value still falls through exactly +# as before. +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' $content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content.Replace('%%ATTACH_LIB_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs') -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se37_reassign_fugr_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace `<SKILL_DIR>` and the `THE_*` placeholders. @@ -1131,8 +1194,16 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se37_reassign_fugr_run. ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_se37_reassign_fugr_run.vbs +Run as one PowerShell block — the target expectation MUST be declared in the +SAME process that launches cscript (the attach lib reads it from the process +environment, and cscript is a child of THIS PowerShell, not of the generator +block above), so a GUI parked on a different system than the RFC leg is refused +instead of having ITS namesake FM moved to another function group (2026-08-06): + +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_se37_reassign_fugr_run.vbs' ``` ### Behaviour Notes @@ -1216,13 +1287,19 @@ $content = $content.Replace('%%TRANSPORT%%', 'THE_TRANSPORT') $content = $content.Replace('%%PACKAGE%%', 'THE_OBJDIR_PACKAGE') $content = $content.Replace('%%ORIG_LANG%%', 'THE_OBJDIR_LANG') $content = $content.Replace('%%SESSION_LOCK_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_session_lock.vbs') -$sessionPath = '' +# Phase 4.2 session-attach plumbing. BAKE the resolved session path into the VBS +# (%%SESSION_PATH%% = attach Strategy 1) rather than exporting it as +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that later launches cscript, so an env var set here dies with it and the attach +# lib silently falls through to its sole-connection default (Strategy 3) -- which +# is how a run pinned to one SAP system drove another one (2026-08-06). Delete is +# irreversible, so a silent retarget here destroys the WRONG system's FM. +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' $content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content.Replace('%%ATTACH_LIB_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs') -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se37_delete_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace `<SKILL_DIR>` and the `THE_*` placeholders. @@ -1233,8 +1310,17 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se37_delete_run.ps1" ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_se37_delete_run.vbs +Run as one PowerShell block — the target expectation MUST be declared in the +SAME process that launches cscript (the attach lib reads it from the process +environment, and cscript is a child of THIS PowerShell, not of the generator +block above). Deletion is irreversible, so refusing a GUI parked on a different +system than the RFC leg is the difference between deleting the intended FM and +destroying its namesake on another system (2026-08-06): + +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_se37_delete_run.vbs' ``` ### Behaviour Notes diff --git a/plugins/sap-dev-core/skills/sap-se38/SKILL.md b/plugins/sap-dev-core/skills/sap-se38/SKILL.md index ab037e9..6e68bbd 100644 --- a/plugins/sap-dev-core/skills/sap-se38/SKILL.md +++ b/plugins/sap-dev-core/skills/sap-se38/SKILL.md @@ -227,16 +227,20 @@ Write `{RUN_TEMP}\sap_se38_check_run.ps1`: ```powershell $content = [System.IO.File]::ReadAllText('<SKILL_DIR>\references\sap_se38_check.vbs', [System.Text.Encoding]::UTF8) $content = $content -replace '%%PROGRAM_NAME%%','THE_PROGRAM_NAME' -# Phase 4.2 session-attach plumbing. SESSION_PATH empty -> attach lib falls -# through to SAPDEV_SESSION_PATH (set below) -> sole-connection -> refuse. -# Pass --session for explicit targeting in parallel/multi-connection contexts. -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# Phase 4.2 session-attach plumbing. BAKE the resolved session path into the VBS +# (%%SESSION_PATH%% = attach Strategy 1) rather than exporting it as +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that later launches cscript, so an env var set here dies with it and the attach +# lib silently falls through to its sole-connection default (Strategy 3) -- which +# is how a run pinned to one SAP system drove another one (2026-08-06). A baked-in +# const survives the process boundary; an empty value still falls through exactly +# as before. Pass --session to override in parallel/multi-connection contexts. . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se38_check_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace `THE_PROGRAM_NAME` with the actual program name (UPPERCASE) and `<SKILL_DIR>` with the absolute path to this skill directory. @@ -247,8 +251,17 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se38_check_run.ps1" ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_se38_check_run.vbs +Run as one PowerShell block — the target expectation MUST be declared in the +SAME process that launches cscript (the attach lib reads it from the process +environment, and cscript is a child of THIS PowerShell, not of the generator +block above). It resolves the same profile `Connect-SapRfc` uses, so a GUI +sitting on a different system than the RFC leg is refused loud instead of +silently answering EXIST/NOT_EXIST about the wrong system: + +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_se38_check_run.vbs' ``` **Parse the last line of output:** @@ -413,8 +426,17 @@ $content = $content -replace '%%PACKAGE%%','THE_PACKAGE' $content = $content -replace '%%TRANSPORT%%','THE_TRANSPORT' $content = $content -replace '%%SESSION_LOCK_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_session_lock.vbs' $content = $content -replace '%%FOREGROUND_GUARD_PS1%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_gui_foreground_guard.ps1' -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath +# Phase 4.2 session-attach plumbing. BAKE the resolved session path into the VBS +# (%%SESSION_PATH%% = attach Strategy 1) rather than exporting it as +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that later launches cscript, so an env var set here dies with it and the attach +# lib silently falls through to its sole-connection default (Strategy 3) -- which +# is how a run pinned to one SAP system WROTE to another (2026-08-06). A baked-in +# const survives the process boundary; an empty value still falls through exactly +# as before. +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' # Locale-aware syntax-check classifier (Ctrl+F2 grid MSGTYPE match for ZH/JA/DE logons). $content = $content -replace '%%SYNTAX_CHECK_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_syntax_check_lib.vbs' @@ -427,10 +449,8 @@ $content = $content -replace '%%POST_ACTIVATE_VERIFY_PS1%%','<SAP_DEV_CORE_SHARE # silently-failed paste leaves the OLD source active and PROGDIR/F8 still pass. $content = $content -replace '%%CONTENT_VERIFY_VBS%%','<SKILL_DIR>\references\sap_se38_content_verify.vbs' $content = $content -replace '%%CONTENT_VERIFY_PS1%%','<SKILL_DIR>\references\sap_se38_content_verify.ps1' -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se38_update_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace `THE_PROGRAM_NAME` (UPPERCASE), `THE_PROGRAM_TYPE` (`I` when updating an **Include** program — e.g. a function-exit `ZX…` include — so the verify skips the F8 run-test; otherwise empty or `1`), `THE_SOURCE_PATH` (absolute path with backslashes), `THE_PACKAGE` (SAP package or empty string), `THE_TRANSPORT` (transport number or empty string), `<SKILL_DIR>`, and `<SAP_DEV_CORE_SHARED_DIR>` (absolute path to `plugins/sap-dev-core/shared/`). @@ -447,11 +467,18 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se38_update_run.ps1" ### Execute -```bash +```powershell +# Declare which SAP system the GUI leg may WRITE to. MUST happen in THIS block: +# the attach lib reads it from the process environment, and cscript inherits this +# PowerShell's environment (via sap_run_with_lock.ps1 -> cmd /c). It resolves the +# same profile Connect-SapRfc uses, so a GUI parked on a different system than the +# RFC leg is refused instead of receiving this program's source (2026-08-06). +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null # SE38 stages ABAP source on the Windows clipboard + SendKeys ^v behind an OS # foreground guard -- both machine-global singletons that a per-run folder cannot # isolate. Serialize the paste across concurrent runs with a global named mutex. -powershell -NoProfile -ExecutionPolicy Bypass -File "<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_run_with_lock.ps1" -MutexName SapDevGuiPaste_v1 -TimeoutMs 180000 -Command "C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_se38_update_run.vbs" +& powershell -NoProfile -ExecutionPolicy Bypass -File "<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_run_with_lock.ps1" -MutexName SapDevGuiPaste_v1 -TimeoutMs 180000 -Command "C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_se38_update_run.vbs" ``` Proceed to Step 6 to evaluate the result. @@ -511,8 +538,17 @@ $content = $content -replace '%%PACKAGE%%','THE_PACKAGE' $content = $content -replace '%%TRANSPORT%%','THE_TRANSPORT' $content = $content -replace '%%SESSION_LOCK_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_session_lock.vbs' $content = $content -replace '%%FOREGROUND_GUARD_PS1%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_gui_foreground_guard.ps1' -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath +# Phase 4.2 session-attach plumbing. BAKE the resolved session path into the VBS +# (%%SESSION_PATH%% = attach Strategy 1) rather than exporting it as +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that later launches cscript, so an env var set here dies with it and the attach +# lib silently falls through to its sole-connection default (Strategy 3) -- which +# is how a run pinned to one SAP system WROTE to another (2026-08-06). A baked-in +# const survives the process boundary; an empty value still falls through exactly +# as before. +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' # Locale-aware syntax-check classifier (Ctrl+F2 grid MSGTYPE match for ZH/JA/DE logons). $content = $content -replace '%%SYNTAX_CHECK_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_syntax_check_lib.vbs' @@ -525,10 +561,8 @@ $content = $content -replace '%%POST_ACTIVATE_VERIFY_PS1%%','<SAP_DEV_CORE_SHARE # silently-failed paste leaves the OLD source active and PROGDIR/F8 still pass. $content = $content -replace '%%CONTENT_VERIFY_VBS%%','<SKILL_DIR>\references\sap_se38_content_verify.vbs' $content = $content -replace '%%CONTENT_VERIFY_PS1%%','<SKILL_DIR>\references\sap_se38_content_verify.ps1' -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se38_create_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace all `THE_*` placeholders and `<SKILL_DIR>`. `THE_PACKAGE` and `THE_TRANSPORT` follow the same rules as Step 5a. @@ -539,11 +573,18 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se38_create_run.ps1" ### Execute -```bash +```powershell +# Declare which SAP system the GUI leg may WRITE to. MUST happen in THIS block: +# the attach lib reads it from the process environment, and cscript inherits this +# PowerShell's environment (via sap_run_with_lock.ps1 -> cmd /c). It resolves the +# same profile Connect-SapRfc uses, so a GUI parked on a different system than the +# RFC leg is refused instead of receiving this program's source (2026-08-06). +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null # SE38 stages ABAP source on the Windows clipboard + SendKeys ^v behind an OS # foreground guard -- both machine-global singletons that a per-run folder cannot # isolate. Serialize the paste across concurrent runs with a global named mutex. -powershell -NoProfile -ExecutionPolicy Bypass -File "<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_run_with_lock.ps1" -MutexName SapDevGuiPaste_v1 -TimeoutMs 180000 -Command "C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_se38_create_run.vbs" +& powershell -NoProfile -ExecutionPolicy Bypass -File "<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_run_with_lock.ps1" -MutexName SapDevGuiPaste_v1 -TimeoutMs 180000 -Command "C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_se38_create_run.vbs" ``` Proceed to Step 6 to evaluate the result. @@ -672,14 +713,20 @@ if (Test-Path '{RUN_TEMP}\sap_se38_textelm_symbols.txt') { $content = $content.Replace('%%TEXT_SYMBOLS%%', $txtSyms) $content = $content.Replace('%%PACKAGE%%','THE_PACKAGE') $content = $content.Replace('%%TRANSPORT%%','THE_TRANSPORT') -# Phase 3.5 session-attach plumbing. -$sessionPath = '' +# Phase 4.2 session-attach plumbing. BAKE the resolved session path into the VBS +# (%%SESSION_PATH%% = attach Strategy 1) rather than exporting it as +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that later launches cscript, so an env var set here dies with it and the attach +# lib silently falls through to its sole-connection default (Strategy 3) -- which +# is how a run pinned to one SAP system WROTE to another (2026-08-06). A baked-in +# const survives the process boundary; an empty value still falls through exactly +# as before. +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' $content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content.Replace('%%ATTACH_LIB_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs') -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se38_textelm_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace `THE_PROGRAM_NAME` (UPPERCASE), `THE_PACKAGE`, `THE_TRANSPORT`, and `<SKILL_DIR>`. @@ -697,8 +744,16 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se38_textelm_run.ps1" ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_se38_textelm_run.vbs +Run as one PowerShell block — the target expectation MUST be declared in the +SAME process that launches cscript (the attach lib reads it from the process +environment, and cscript is a child of THIS PowerShell, not of the generator +block above), so a GUI parked on a different system than the RFC leg is refused +instead of receiving this program's selection texts (2026-08-06): + +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_se38_textelm_run.vbs' ``` **On success** (output contains `SUCCESS:`): proceed to Step 5c.1. The VBS template now handles @@ -899,14 +954,20 @@ $content = $content.Replace('%%STATUS%%', 'THE_STATUS') $content = $content.Replace('%%TYPE%%', 'THE_TYPE') $content = $content.Replace('%%TRANSPORT%%', 'THE_TRANSPORT') $content = $content.Replace('%%SESSION_LOCK_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_session_lock.vbs') -# Phase 3.5 session-attach plumbing. -$sessionPath = '' +# Phase 4.2 session-attach plumbing. BAKE the resolved session path into the VBS +# (%%SESSION_PATH%% = attach Strategy 1) rather than exporting it as +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that later launches cscript, so an env var set here dies with it and the attach +# lib silently falls through to its sole-connection default (Strategy 3) -- which +# is how a run pinned to one SAP system WROTE to another (2026-08-06). A baked-in +# const survives the process boundary; an empty value still falls through exactly +# as before. +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' $content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content.Replace('%%ATTACH_LIB_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs') -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se38_change_attrs_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Use `.Replace()` (literal) — title/status texts may contain regex metacharacters. Replace `<SKILL_DIR>` and the `THE_*` placeholders. @@ -918,8 +979,16 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se38_change_attrs_run.p ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_se38_change_attrs_run.vbs +Run as one PowerShell block — the target expectation MUST be declared in the +SAME process that launches cscript (the attach lib reads it from the process +environment, and cscript is a child of THIS PowerShell, not of the generator +block above), so a GUI parked on a different system than the RFC leg is refused +instead of having ITS program's attributes rewritten (2026-08-06): + +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_se38_change_attrs_run.vbs' ``` ### Behaviour Notes @@ -1020,13 +1089,19 @@ $content = $content.Replace('%%TRANSPORT%%', 'THE_TRANSPORT') $content = $content.Replace('%%PACKAGE%%', 'THE_OBJDIR_PACKAGE') $content = $content.Replace('%%ORIG_LANG%%', 'THE_OBJDIR_LANG') $content = $content.Replace('%%SESSION_LOCK_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_session_lock.vbs') -$sessionPath = '' +# Phase 4.2 session-attach plumbing. BAKE the resolved session path into the VBS +# (%%SESSION_PATH%% = attach Strategy 1) rather than exporting it as +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that later launches cscript, so an env var set here dies with it and the attach +# lib silently falls through to its sole-connection default (Strategy 3) -- which +# is how a run pinned to one SAP system drove another one (2026-08-06). Delete is +# irreversible, so a silent retarget here destroys the WRONG system's program. +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' $content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content.Replace('%%ATTACH_LIB_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs') -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se38_delete_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace `<SKILL_DIR>` and the `THE_*` placeholders. @@ -1037,8 +1112,17 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se38_delete_run.ps1" ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_se38_delete_run.vbs +Run as one PowerShell block — the target expectation MUST be declared in the +SAME process that launches cscript (the attach lib reads it from the process +environment, and cscript is a child of THIS PowerShell, not of the generator +block above). Deletion is irreversible, so refusing a GUI parked on a different +system than the RFC leg is the difference between deleting the intended program +and destroying its namesake on another system (2026-08-06): + +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_se38_delete_run.vbs' ``` ### Behaviour Notes diff --git a/plugins/sap-dev-core/skills/sap-se41/SKILL.md b/plugins/sap-dev-core/skills/sap-se41/SKILL.md index ba15ac9..de4740e 100644 --- a/plugins/sap-dev-core/skills/sap-se41/SKILL.md +++ b/plugins/sap-dev-core/skills/sap-se41/SKILL.md @@ -257,13 +257,16 @@ $content = $content -replace '%%SHORT_TEXT%%','THE_SHORT_TEXT' $content = $content -replace '%%DEF_FILE%%','{WORK_TEMP}\THE_STATUS.def' $content = $content -replace '%%TARGET_PROGRAM%%','THE_TARGET_PROGRAM' $content = $content -replace '%%TARGET_STATUS%%','THE_TARGET_STATUS' -# Phase 3.5 session-attach plumbing. -$content = $content -replace '%%SESSION_PATH%%', '' -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# Phase 4.2 session-attach plumbing. BAKE the resolved path into %%SESSION_PATH%% +# (attach Strategy 1): this generator is a SEPARATE process from the one that runs +# cscript, so an $env:SAPDEV_SESSION_PATH exported here dies with it and the attach +# lib silently falls through to its sole-connection default (2026-08-06). . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se41_ops_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Run: @@ -273,8 +276,13 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se41_ops_run.ps1" ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_se41_ops_run.vbs +Declare the GUI target in the SAME block as cscript — the attach lib reads +`SAPDEV_EXPECT_SYSTEM`/`_CLIENT` from the process environment, so a GUI-status +change can never land on a system other than the one the RFC leg resolved: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_se41_ops_run.vbs' ``` --- diff --git a/plugins/sap-dev-core/skills/sap-se51/SKILL.md b/plugins/sap-dev-core/skills/sap-se51/SKILL.md index af451fb..8f12b1b 100644 --- a/plugins/sap-dev-core/skills/sap-se51/SKILL.md +++ b/plugins/sap-dev-core/skills/sap-se51/SKILL.md @@ -168,14 +168,16 @@ Write `{RUN_TEMP}\sap_se51_check_run.ps1`: $content = [System.IO.File]::ReadAllText('<SKILL_DIR>\references\sap_se51_check.vbs', [System.Text.Encoding]::UTF8) $content = $content -replace '%%PROGRAM_NAME%%','THE_PROGRAM_NAME' $content = $content -replace '%%SCREEN_NUMBER%%','THE_SCREEN_NUMBER' -# Phase 3.5 session-attach plumbing. -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# Phase 4.2 session-attach plumbing. BAKE the resolved path into %%SESSION_PATH%% +# (attach Strategy 1): this generator is a SEPARATE process from the one that runs +# cscript, so an $env:SAPDEV_SESSION_PATH exported here dies with it and the attach +# lib silently falls through to its sole-connection default (2026-08-06). . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se51_check_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace `THE_PROGRAM_NAME` (UPPERCASE), `THE_SCREEN_NUMBER`, and `<SKILL_DIR>`. @@ -186,8 +188,13 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se51_check_run.ps1" ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_se51_check_run.vbs +Declare the GUI target in the SAME block as cscript — the attach lib reads +`SAPDEV_EXPECT_SYSTEM`/`_CLIENT` from the process environment, so a GUI parked on +a different system than the RFC leg is refused instead of driven: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_se51_check_run.vbs' ``` **Parse the last line of output:** @@ -218,14 +225,14 @@ $content = $content -replace '%%PROGRAM_NAME%%','THE_PROGRAM_NAME' $content = $content -replace '%%SCREEN_NUMBER%%','THE_SCREEN_NUMBER' $content = $content -replace '%%LOG_FILE%%','{RUN_TEMP}\sap_se51_update.log' $content = $content -replace '%%FOREGROUND_GUARD_PS1%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_gui_foreground_guard.ps1' -# Phase 3.5 session-attach plumbing. -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# BAKE the session path (attach Strategy 1) -- an env var exported by this +# generator process never reaches the separate process that runs cscript. . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se51_update_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace `THE_PROGRAM_NAME` (UPPERCASE), `THE_SCREEN_NUMBER`, and `<SKILL_DIR>`. @@ -277,14 +284,14 @@ $content = $content -replace '%%SCREEN_NUMBER%%','THE_SCREEN_NUMBER' $content = $content -replace '%%SCREEN_SHORT_TEXT%%','THE_SHORT_TEXT' $content = $content -replace '%%LOG_FILE%%','{RUN_TEMP}\sap_se51_create.log' $content = $content -replace '%%FOREGROUND_GUARD_PS1%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_gui_foreground_guard.ps1' -# Phase 3.5 session-attach plumbing. -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# BAKE the session path (attach Strategy 1) -- an env var exported by this +# generator process never reaches the separate process that runs cscript. . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se51_create_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace all `THE_*` placeholders and `<SKILL_DIR>`. @@ -555,15 +562,17 @@ $c = $c -replace '%%TRANSPORT%%','THE_TR' $c = $c -replace '%%PACKAGE%%','THE_PACKAGE' $c = $c -replace '%%ELEMENT_FILE%%','{RUN_TEMP}\se51_elements.txt' $c = $c -replace '%%LOG_FILE%%','{RUN_TEMP}\sap_se51_add_element.log' -# SESSION_PATH: leave '' for the single-session case (the attach lib resolves -# via SAPDEV_SESSION_PATH below). If multiple sessions are open on the -# connection the lib refuses — pass an explicit '/app/con[0]/ses[0]' here. -$c = $c -replace '%%SESSION_PATH%%','' -$c = $c -replace '%%ATTACH_LIB_VBS%%',"$shared\sap_attach_lib.vbs" +# SESSION_PATH: BAKE this AI session's pin in (attach Strategy 1). Do NOT export +# it as $env:SAPDEV_SESSION_PATH instead: this generator is a SEPARATE process +# from the one that runs cscript, so the env var would already be gone and the +# lib would silently fall through to its sole-connection default (2026-08-06). +# Override with an explicit '/app/con[0]/ses[0]' when several sessions are open. . "$shared\sap_connection_lib.ps1" -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$c = $c.Replace('%%SESSION_PATH%%', $sessionPath) +$c = $c -replace '%%ATTACH_LIB_VBS%%',"$shared\sap_attach_lib.vbs" [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se51_add_element_run.vbs', $c, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Run with **`cscript`** (this VBS writes to a log file, not the clipboard, so no @@ -571,7 +580,14 @@ Run with **`cscript`** (this VBS writes to a log file, not the clipboard, so no ```bash powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se51_add_element_run.ps1" -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_se51_add_element_run.vbs +``` + +Declare the GUI target in the SAME block as cscript so screen elements are only +ever added on the system the RFC leg resolved: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_se51_add_element_run.vbs' ``` Read `{RUN_TEMP}\sap_se51_add_element.log`. Last line: @@ -732,15 +748,15 @@ $c = $c -replace '%%TRANSPORT%%','THE_TR' $c = $c -replace '%%PACKAGE%%','THE_PACKAGE' $c = $c -replace '%%LINESPEC_FILE%%','{RUN_TEMP}\se51_lines.txt' $c = $c -replace '%%LOG_FILE%%','{RUN_TEMP}\sap_se51_layout_rebuild.log' -# SESSION_PATH: leave '' for the single-session case (the attach lib resolves -# via SAPDEV_SESSION_PATH below). If multiple sessions are open on the -# connection the lib refuses — pass an explicit '/app/con[0]/ses[0]' here. -$c = $c -replace '%%SESSION_PATH%%','' -$c = $c -replace '%%ATTACH_LIB_VBS%%',"$shared\sap_attach_lib.vbs" +# SESSION_PATH: BAKE this AI session's pin in (attach Strategy 1) -- an env var +# exported by this generator process never reaches the process that runs cscript. +# Override with an explicit '/app/con[0]/ses[0]' when several sessions are open. . "$shared\sap_connection_lib.ps1" -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$c = $c.Replace('%%SESSION_PATH%%', $sessionPath) +$c = $c -replace '%%ATTACH_LIB_VBS%%',"$shared\sap_attach_lib.vbs" [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se51_layout_rebuild_run.vbs', $c, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Run with **`cscript`** (writes to a log file; no clipboard/foreground guard @@ -748,7 +764,15 @@ needed): ```bash powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se51_layout_rebuild_run.ps1" -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_se51_layout_rebuild_run.vbs +``` + +Declare the GUI target in the SAME block as cscript. A layout rebuild replaces the +screen, so refusing a GUI parked on a different system than the RFC leg is the +difference between rebuilding the intended screen and clobbering its namesake: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_se51_layout_rebuild_run.vbs' ``` Read `{RUN_TEMP}\sap_se51_layout_rebuild.log`. Last line: diff --git a/plugins/sap-dev-core/skills/sap-se54/SKILL.md b/plugins/sap-dev-core/skills/sap-se54/SKILL.md index da6e6cc..130a186 100644 --- a/plugins/sap-dev-core/skills/sap-se54/SKILL.md +++ b/plugins/sap-dev-core/skills/sap-se54/SKILL.md @@ -151,14 +151,16 @@ Write `{RUN_TEMP}\sap_se54_check_run.ps1`: ```powershell $content = [System.IO.File]::ReadAllText('<SKILL_DIR>\references\sap_se54_check.vbs', [System.Text.Encoding]::UTF8) $content = $content -replace '%%TABLE_NAME%%','THE_TABLE_NAME' -# Phase 3.5 session-attach plumbing. -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# Phase 4.2 session-attach plumbing. BAKE the resolved path into %%SESSION_PATH%% +# (attach Strategy 1): this generator is a SEPARATE process from the one that runs +# cscript, so an $env:SAPDEV_SESSION_PATH exported here dies with it and the attach +# lib silently falls through to its sole-connection default (2026-08-06). . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se54_check_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace `THE_TABLE_NAME` with the actual table name (UPPERCASE) and `<SKILL_DIR>` with the absolute path to this skill directory. @@ -169,8 +171,13 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se54_check_run.ps1" ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_se54_check_run.vbs +Declare the GUI target in the SAME block as cscript — the attach lib reads +`SAPDEV_EXPECT_SYSTEM`/`_CLIENT` from the process environment, so a GUI parked on +a different system than the RFC leg is refused instead of driven: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_se54_check_run.vbs' ``` **Parse the last line of output:** @@ -200,13 +207,14 @@ $content = $content -replace '%%TRANSPORT%%','THE_TRANSPORT' # RESOLVED # Phase 3.5 session-attach plumbing. The generate VBS also derives the shared # post-action popup walker (sap_delete_popups.vbs) from %%ATTACH_LIB_VBS%% # (same dir), so no extra token is needed for it. -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# BAKE the session path (attach Strategy 1) -- an env var exported by this +# generator process never reaches the separate process that runs cscript. . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se54_generate_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace all `THE_*` placeholders and `<SKILL_DIR>`. `THE_TRANSPORT` is `RESOLVED_TR` from Step 1b (empty string for a `$TMP` / Local Object generation); @@ -219,8 +227,12 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se54_generate_run.ps1" ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_se54_generate_run.vbs +Declare the GUI target in the SAME block as cscript (see Step 4) so the +maintenance dialog is generated on the system the RFC leg resolved: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_se54_generate_run.vbs' ``` Proceed to Step 5 to evaluate the result. diff --git a/plugins/sap-dev-core/skills/sap-se91/SKILL.md b/plugins/sap-dev-core/skills/sap-se91/SKILL.md index 62e3b5b..a8ce51e 100644 --- a/plugins/sap-dev-core/skills/sap-se91/SKILL.md +++ b/plugins/sap-dev-core/skills/sap-se91/SKILL.md @@ -182,14 +182,20 @@ Write `{RUN_TEMP}\sap_se91_check_run.ps1`: ```powershell $content = [System.IO.File]::ReadAllText('<SKILL_DIR>\references\sap_se91_check.vbs', [System.Text.Encoding]::UTF8) $content = $content -replace '%%MSG_CLASS%%','THE_MSG_CLASS' -# Phase 3.5 session-attach plumbing. -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# Phase 4.2 session-attach plumbing. BAKE the resolved session path into the VBS +# (%%SESSION_PATH%% = attach Strategy 1) rather than exporting it as +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that later launches cscript, so an env var set here dies with it and the attach +# lib silently falls through to its sole-connection default (Strategy 3) -- which +# is how a run pinned to one SAP system drove another one (2026-08-06). A baked-in +# const survives the process boundary; an empty value still falls through exactly +# as before. . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se91_check_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace `THE_MSG_CLASS` with the actual message class name (UPPERCASE) and `<SKILL_DIR>` with the absolute path to this skill directory. @@ -200,8 +206,17 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se91_check_run.ps1" ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_se91_check_run.vbs +Run as one PowerShell block — the target expectation MUST be declared in the +SAME process that launches cscript (the attach lib reads it from the process +environment, and cscript is a child of THIS PowerShell, not of the generator +block above). It resolves the same profile `Connect-SapRfc` uses, so a GUI +sitting on a different system than the RFC leg is refused loud instead of +silently answering EXIST/NOT_EXIST about the wrong system: + +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_se91_check_run.vbs' ``` **Parse the last line of output:** @@ -308,14 +323,20 @@ $content = $content -replace '%%MSG_CLASS%%','THE_MSG_CLASS' $content = $content -replace '%%MESSAGES_FILE%%','THE_MESSAGES_FILE' $content = $content -replace '%%PACKAGE%%','THE_PACKAGE' $content = $content -replace '%%TRANSPORT%%','THE_TRANSPORT' -# Phase 3.5 session-attach plumbing. -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# Phase 4.2 session-attach plumbing. BAKE the resolved session path into the VBS +# (%%SESSION_PATH%% = attach Strategy 1) rather than exporting it as +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that later launches cscript, so an env var set here dies with it and the attach +# lib silently falls through to its sole-connection default (Strategy 3) -- which +# is how a run pinned to one SAP system WROTE to another (2026-08-06). A baked-in +# const survives the process boundary; an empty value still falls through exactly +# as before. . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se91_update_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace `THE_MSG_CLASS` (UPPERCASE), `THE_MESSAGES_FILE` (absolute path with backslashes), `THE_PACKAGE`, `THE_TRANSPORT`, and `<SKILL_DIR>`. If package/transport not provided, replace with empty strings. @@ -326,8 +347,16 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se91_update_run.ps1" ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_se91_update_run.vbs +Run as one PowerShell block — the target expectation MUST be declared in the +SAME process that launches cscript (the attach lib reads it from the process +environment, and cscript is a child of THIS PowerShell, not of the generator +block above), so a GUI parked on a different system than the RFC leg is refused +instead of having ITS namesake message class rewritten (2026-08-06): + +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_se91_update_run.vbs' ``` Proceed to Step 6 to evaluate the result. @@ -351,14 +380,20 @@ $content = $content -replace '%%SHORT_TEXT%%','THE_SHORT_TEXT' $content = $content -replace '%%MESSAGES_FILE%%','THE_MESSAGES_FILE' $content = $content -replace '%%PACKAGE%%','THE_PACKAGE' $content = $content -replace '%%TRANSPORT%%','THE_TRANSPORT' -# Phase 3.5 session-attach plumbing. -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# Phase 4.2 session-attach plumbing. BAKE the resolved session path into the VBS +# (%%SESSION_PATH%% = attach Strategy 1) rather than exporting it as +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that later launches cscript, so an env var set here dies with it and the attach +# lib silently falls through to its sole-connection default (Strategy 3) -- which +# is how a run pinned to one SAP system WROTE to another (2026-08-06). A baked-in +# const survives the process boundary; an empty value still falls through exactly +# as before. . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se91_create_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` > **Messages-file encoding (important):** The `%%MESSAGES_FILE%%` referenced @@ -389,8 +424,16 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se91_create_run.ps1" ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_se91_create_run.vbs +Run as one PowerShell block — the target expectation MUST be declared in the +SAME process that launches cscript (the attach lib reads it from the process +environment, and cscript is a child of THIS PowerShell, not of the generator +block above), so a GUI parked on a different system than the RFC leg is refused +instead of receiving this message class (2026-08-06): + +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_se91_create_run.vbs' ``` Proceed to Step 6 to evaluate the result. @@ -477,14 +520,20 @@ $content = $content.Replace('%%MSG_CLASS%%', 'THE_MSG_CLASS') $content = $content.Replace('%%SHORT_TEXT%%', 'THE_SHORT_TEXT') $content = $content.Replace('%%RESPONSIBLE%%', 'THE_RESPONSIBLE') $content = $content.Replace('%%TRANSPORT%%', 'THE_TRANSPORT') -# Phase 3.5 session-attach plumbing. -$sessionPath = '' +# Phase 4.2 session-attach plumbing. BAKE the resolved session path into the VBS +# (%%SESSION_PATH%% = attach Strategy 1) rather than exporting it as +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that later launches cscript, so an env var set here dies with it and the attach +# lib silently falls through to its sole-connection default (Strategy 3) -- which +# is how a run pinned to one SAP system WROTE to another (2026-08-06). A baked-in +# const survives the process boundary; an empty value still falls through exactly +# as before. +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' $content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content.Replace('%%ATTACH_LIB_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs') -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_se91_change_props_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Use `.Replace()` (literal) — short-text values may contain regex metacharacters. Replace `<SKILL_DIR>` and the `THE_*` placeholders. @@ -496,8 +545,16 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_se91_change_props_run.p ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_se91_change_props_run.vbs +Run as one PowerShell block — the target expectation MUST be declared in the +SAME process that launches cscript (the attach lib reads it from the process +environment, and cscript is a child of THIS PowerShell, not of the generator +block above), so a GUI parked on a different system than the RFC leg is refused +instead of having ITS namesake message class's header rewritten (2026-08-06): + +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_se91_change_props_run.vbs' ``` ### Behaviour Notes diff --git a/plugins/sap-dev-core/skills/sap-snro/SKILL.md b/plugins/sap-dev-core/skills/sap-snro/SKILL.md index 878de45..188b20d 100644 --- a/plugins/sap-dev-core/skills/sap-snro/SKILL.md +++ b/plugins/sap-dev-core/skills/sap-snro/SKILL.md @@ -175,14 +175,16 @@ Write `{RUN_TEMP}\sap_snro_check_run.ps1`: ```powershell $content = [System.IO.File]::ReadAllText('<SKILL_DIR>\references\sap_snro_check.vbs', [System.Text.Encoding]::UTF8) $content = $content.Replace('%%NRO_NAME%%','THE_NRO_NAME') -# Phase 3.5 session-attach plumbing. -$sessionPath = '' +# Phase 4.2 session-attach plumbing. BAKE the resolved path into %%SESSION_PATH%% +# (attach Strategy 1): this generator is a SEPARATE process from the one that runs +# cscript, so an $env:SAPDEV_SESSION_PATH exported here dies with it and the attach +# lib silently falls through to its sole-connection default (2026-08-06). +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' $content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content.Replace('%%ATTACH_LIB_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs') -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_snro_check_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace `THE_NRO_NAME` with the actual NRO name (UPPERCASE) and `<SKILL_DIR>` with the absolute path to this skill directory. @@ -193,8 +195,13 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_snro_check_run.ps1" ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_snro_check_run.vbs +Declare the GUI target in the SAME block as cscript — the attach lib reads +`SAPDEV_EXPECT_SYSTEM`/`_CLIENT` from the process environment, so a GUI parked on +a different system than the RFC leg is refused instead of driven: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_snro_check_run.vbs' ``` **Parse the last line of output:** @@ -232,14 +239,14 @@ $content = $content.Replace('%%DOMLEN%%', 'THE_DOMLEN') $content = $content.Replace('%%PERCENTAGE%%', 'THE_PERCENTAGE') $content = $content.Replace('%%PACKAGE%%', 'THE_PACKAGE') $content = $content.Replace('%%TRANSPORT%%', 'THE_TRANSPORT') -# Phase 3.5 session-attach plumbing. -$sessionPath = '' +# BAKE the session path (attach Strategy 1) -- an env var exported by this +# generator process never reaches the separate process that runs cscript. +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' $content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content.Replace('%%ATTACH_LIB_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs') -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_snro_create_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Use `.Replace()` (literal). Replace `<SKILL_DIR>` and all `THE_*` placeholders. If `LONG_TEXT` is blank, pass the short text. If `PERCENTAGE` is blank, pass `10.0`. If package/transport not provided, pass empty strings. @@ -250,8 +257,12 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_snro_create_run.ps1" ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_snro_create_run.vbs +Declare the GUI target in the SAME block as cscript (see the check step) so the +number range object is created on the system the RFC leg resolved: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_snro_create_run.vbs' ``` Proceed to Step 6 to evaluate the result. If the user also supplied intervals, continue with Step 5c after a successful create. @@ -278,14 +289,14 @@ $content = $content.Replace('%%DOMLEN%%', 'THE_DOMLEN') $content = $content.Replace('%%PERCENTAGE%%', 'THE_PERCENTAGE') $content = $content.Replace('%%PACKAGE%%', 'THE_PACKAGE') $content = $content.Replace('%%TRANSPORT%%', 'THE_TRANSPORT') -# Phase 3.5 session-attach plumbing. -$sessionPath = '' +# BAKE the session path (attach Strategy 1) -- an env var exported by this +# generator process never reaches the separate process that runs cscript. +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' $content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content.Replace('%%ATTACH_LIB_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs') -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_snro_update_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Pass empty strings for fields the user does not want to change. The VBS only touches fields whose token is non-empty. @@ -296,8 +307,12 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_snro_update_run.ps1" ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_snro_update_run.vbs +Declare the GUI target in the SAME block as cscript (see the check step) so the +number range object is updated on the system the RFC leg resolved: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_snro_update_run.vbs' ``` Proceed to Step 6. @@ -327,14 +342,14 @@ $tpl = "$skillDir\references\sap_snro_intervals.vbs" $content = [System.IO.File]::ReadAllText($tpl, [System.Text.Encoding]::UTF8) $content = $content.Replace('%%NRO_NAME%%', 'THE_NRO_NAME') $content = $content.Replace('%%INTERVALS_FILE%%','THE_INTERVALS_FILE') -# Phase 3.5 session-attach plumbing. -$sessionPath = '' +# BAKE the session path (attach Strategy 1) -- an env var exported by this +# generator process never reaches the separate process that runs cscript. +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' $content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content.Replace('%%ATTACH_LIB_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs') -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_snro_intervals_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Run: @@ -344,8 +359,13 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_snro_intervals_run.ps1" ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_snro_intervals_run.vbs +Declare the GUI target in the SAME block as cscript (see the check step). Number +range intervals carry live document numbering, so landing on the wrong system +would corrupt that system's numbering state: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_snro_intervals_run.vbs' ``` Proceed to Step 6. diff --git a/plugins/sap-dev-core/skills/sap-sp02/SKILL.md b/plugins/sap-dev-core/skills/sap-sp02/SKILL.md index a2c1fce..e8e6cb8 100644 --- a/plugins/sap-dev-core/skills/sap-sp02/SKILL.md +++ b/plugins/sap-dev-core/skills/sap-sp02/SKILL.md @@ -129,16 +129,18 @@ $content = $content.Replace('%%SPOOL_NUM_COL%%', 'THE_SPOOL_NUM_COL') # empty $content = $content.Replace('%%FORMAT_INDEX%%', 'THE_FORMAT_INDEX') # empty for default 0 $content = $content.Replace('%%OUTPUT_DIR%%', 'THE_OUTPUT_DIR') # MUST end with '\' $content = $content.Replace('%%OUTPUT_FILE%%', 'THE_OUTPUT_FILE') -# Session-attach plumbing (Phase 3.5 multi-connection aware). Resolution: -# explicit --session > SAPDEV_SESSION_PATH > sole- -# connection auto-default > refuse. See sap_attach_lib.vbs for details. +# Session-attach plumbing (Phase 4.2). Resolution: explicit --session > this AI +# session's pin, BAKED into %%SESSION_PATH%% (attach Strategy 1). Do NOT export +# $env:SAPDEV_SESSION_PATH instead: this generator is a SEPARATE process from the +# one that runs cscript, so the env var would already be gone and the helper would +# silently fall through to its sole-connection default (2026-08-06). +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' $sessionPath = '' # set to the parsed --session value if supplied +if (-not $sessionPath) { $sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' } $content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content.Replace('%%ATTACH_LIB_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs') -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_sp02_download_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Run the generator: diff --git a/plugins/sap-dev-core/skills/sap-st22/SKILL.md b/plugins/sap-dev-core/skills/sap-st22/SKILL.md index 9ff53d2..bb3abcc 100644 --- a/plugins/sap-dev-core/skills/sap-st22/SKILL.md +++ b/plugins/sap-dev-core/skills/sap-st22/SKILL.md @@ -98,24 +98,32 @@ $lines | Set-Content '{RUN_DIR}\st22_params.txt' -Encoding Default ## Step 2 — Run the Reader (32-bit cscript) -Substitute the attach tokens + IO paths. Set `SAPDEV_SESSION_PATH` so the -attach helper targets this AI session's pinned connection (per the parallel-safe -attach contract). +Substitute the attach tokens + IO paths, **baking** this AI session's pinned +session path into `%%SESSION_PATH%%` so the attach helper targets the pinned +connection (per the parallel-safe attach contract). ```powershell $shared = '<SAP_DEV_CORE_SHARED_DIR>\scripts' . "$shared\sap_connection_lib.ps1" -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +# BAKE the path (attach Strategy 1) rather than exporting $env:SAPDEV_SESSION_PATH: +# this generator is a SEPARATE process from the one that runs cscript, so the env +# var would already be gone and the helper would silently fall through to its +# sole-connection default -- reporting another system's dumps (2026-08-06). +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' # or the --session value $vbs = [IO.File]::ReadAllText('<SKILL_DIR>\references\sap_st22_read.vbs', [Text.Encoding]::UTF8) $vbs = $vbs.Replace('%%ATTACH_LIB_VBS%%', "$shared\sap_attach_lib.vbs") -$vbs = $vbs.Replace('%%SESSION_PATH%%', '') # or the --session value +$vbs = $vbs.Replace('%%SESSION_PATH%%', $sessionPath) $vbs = $vbs.Replace('%%PARAMS_FILE%%', '{RUN_DIR}\st22_params.txt') $vbs = $vbs.Replace('%%OUTPUT_FILE%%', '{RUN_DIR}\evidence_st22.json') [IO.File]::WriteAllText('{RUN_DIR}\st22_run.vbs', $vbs, [System.Text.UnicodeEncoding]::new($false, $true)) ``` -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo "{RUN_DIR}\st22_run.vbs" +Declare the GUI target in the SAME block as cscript, so the dumps in the evidence +file provably came from the system the rest of `/sap-diagnose` read over RFC: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo "{RUN_DIR}\st22_run.vbs" ``` (32-bit `cscript` is mandatory — SAP GUI Scripting COM is 32-bit. Never `cmd /c`.) diff --git a/plugins/sap-dev-core/skills/sap-stms/SKILL.md b/plugins/sap-dev-core/skills/sap-stms/SKILL.md index 4d87271..eea6799 100644 --- a/plugins/sap-dev-core/skills/sap-stms/SKILL.md +++ b/plugins/sap-dev-core/skills/sap-stms/SKILL.md @@ -232,11 +232,18 @@ without importing (exit 1). ```powershell $shared = '<SAP_DEV_CORE_SHARED_DIR>\scripts' . "$shared\sap_connection_lib.ps1" -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +# BAKE the path into %%SESSION_PATH%% (attach Strategy 1) rather than exporting +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that runs cscript, so the env var would already be gone and the attach lib would +# silently fall through to its sole-connection default (2026-08-06). NOTE: this +# pins the DOMAIN CONTROLLER session STMS is driven from -- the import's *target* +# system is a separate concern, guarded by this skill's own TARGET-based PROD gate +# (W2/W3), which is why sap-stms is deliberately off SAFETY_GATE_SKILLS. +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' # or the --session value $vbs = [IO.File]::ReadAllText('<SKILL_DIR>\references\sap_stms_import.vbs', [Text.Encoding]::UTF8) $vbs = $vbs.Replace('%%ATTACH_LIB_VBS%%', "$shared\sap_attach_lib.vbs") $vbs = $vbs.Replace('%%SESSION_LOCK_VBS%%',"$shared\sap_session_lock.vbs") -$vbs = $vbs.Replace('%%SESSION_PATH%%', '') # or the --session value +$vbs = $vbs.Replace('%%SESSION_PATH%%', $sessionPath) $vbs = $vbs.Replace('%%TR%%', 'THE_TR') $vbs = $vbs.Replace('%%TARGET_SID%%', 'THE_SID') $vbs = $vbs.Replace('%%TARGET_CLIENT%%', 'THE_CLIENT') diff --git a/plugins/sap-dev-core/skills/sap-trace/SKILL.md b/plugins/sap-dev-core/skills/sap-trace/SKILL.md index 620f75f..a4a9ac2 100644 --- a/plugins/sap-dev-core/skills/sap-trace/SKILL.md +++ b/plugins/sap-dev-core/skills/sap-trace/SKILL.md @@ -156,14 +156,18 @@ $content = $content -replace '%%TO_TIME%%', 'HH:MM:SS' $content = $content -replace '%%MEASUREMENT%%', 'THE_MEASUREMENT' # SAT only; '' = latest # Session-attach plumbing (Phase 4.2). AttachSapSession resolves the target # session: SESSION_PATH constant -> SAPDEV_SESSION_PATH env var -> -# sole-connection default -> refuse loud. +# sole-connection default -> refuse loud. Resolve the pin here and BAKE it into +# the SESSION_PATH constant (Strategy 1); the env var (Strategy 2) does NOT work +# from this block, which is a SEPARATE process from the one that runs cscript, so +# the helper would silently fall through to the sole-connection default and trace +# whatever SAP GUI happens to be open (2026-08-06). +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' $sessionPath = '' # set to the parsed --session value if supplied -$content = $content -replace '%%SESSION_PATH%%', $sessionPath +if (-not $sessionPath) { $sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' } +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content -replace '%%ATTACH_LIB_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_trace_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace `RUNTS` / `THE_USER` / `HH:MM:SS` / `THE_MEASUREMENT` and the diff --git a/plugins/sap-dev-core/skills/sap-update-addon/SKILL.md b/plugins/sap-dev-core/skills/sap-update-addon/SKILL.md index 8cb9634..72c95c0 100644 --- a/plugins/sap-dev-core/skills/sap-update-addon/SKILL.md +++ b/plugins/sap-dev-core/skills/sap-update-addon/SKILL.md @@ -220,19 +220,26 @@ $content = $content.Replace('%%OPERATION%%', 'THE_OPERATION') # but if SAP then prompts for a TR the VBS aborts with ABORT_EMPTY_TR -- it # never blind-Enters the transport popup. $content = $content.Replace('%%TRANSPORT%%', 'THE_TRANSPORT_OR_EMPTY') -# Phase 3.5 session-attach plumbing. -$sessionPath = '' +# Phase 4.2 session-attach plumbing. BAKE the resolved path into %%SESSION_PATH%% +# (attach Strategy 1): this generator is a SEPARATE process from the one that runs +# cscript, so an $env:SAPDEV_SESSION_PATH exported here dies with it and the attach +# lib silently falls through to its sole-connection default (2026-08-06). +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' $content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content.Replace('%%ATTACH_LIB_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs') -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_update_addon_sm30_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` -Execute (SAP GUI Scripting COM requires 32-bit cscript): -```bash -C:/Windows/SysWOW64/cscript.exe //NoLogo "{RUN_TEMP}\sap_update_addon_sm30_run.vbs" +Execute (SAP GUI Scripting COM requires 32-bit cscript), declaring the GUI target +in the SAME block — the attach lib reads `SAPDEV_EXPECT_SYSTEM`/`_CLIENT` from the +process environment, so add-on table rows can never be written to a system other +than the one the RFC detection resolved: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:/Windows/SysWOW64/cscript.exe' //NoLogo "{RUN_TEMP}\sap_update_addon_sm30_run.vbs" ``` **SM30 Notes:** @@ -265,19 +272,23 @@ $content = [System.IO.File]::ReadAllText('<SKILL_DIR>\references\sap_update_addo $content = $content.Replace('%%TABLE_NAME%%', 'THE_TABLE_NAME') $content = $content.Replace('%%DATA_FILE%%', 'THE_DATA_FILE') $content = $content.Replace('%%OPERATION%%', 'THE_OPERATION') -# Phase 3.5 session-attach plumbing. -$sessionPath = '' +# BAKE the session path (attach Strategy 1) -- an env var exported by this +# generator process never reaches the separate process that runs cscript. +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' $content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content.Replace('%%ATTACH_LIB_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs') -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_update_addon_se16_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` -Execute (SAP GUI Scripting COM requires 32-bit cscript): -```bash -C:/Windows/SysWOW64/cscript.exe //NoLogo "{RUN_TEMP}\sap_update_addon_se16_run.vbs" +Execute (SAP GUI Scripting COM requires 32-bit cscript), declaring the GUI target +in the SAME block (see the SM30 path above) so rows land only on the system the +RFC detection resolved: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:/Windows/SysWOW64/cscript.exe' //NoLogo "{RUN_TEMP}\sap_update_addon_se16_run.vbs" ``` **SE16 Notes (INSERT / UPDATE):** @@ -319,14 +330,14 @@ $content = $content.Replace('%%TEMP_DIR%%', '{RUN_TEMP}') # refuses OPERATION=DELETE upfront with # "ERROR: PROG method supports upsert (MODIFY) only" (exit 1). $content = $content.Replace('%%OPERATION%%', 'THE_OPERATION') -# Phase 3.5 session-attach plumbing. -$sessionPath = '' +# BAKE the session path (attach Strategy 1) -- an env var exported by this +# generator process never reaches the separate process that runs cscript. +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' $content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content.Replace('%%ATTACH_LIB_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs') -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_update_addon_prog_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` ### Execute (with SAP GUI Security guard) @@ -344,6 +355,10 @@ can appear well after the upload. ```powershell $shared = '<SAP_DEV_CORE_SHARED_DIR>\scripts' +# Declare which SAP system the GUI leg may write to; MUST be in the same block as +# cscript (the attach lib reads it from the process environment). +. "$shared\sap_connection_lib.ps1" +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null $watcher = Start-Process powershell -PassThru -WindowStyle Hidden ` -RedirectStandardOutput "{RUN_TEMP}\sap_update_addon_sidecar.out" ` -ArgumentList @('-NoProfile','-ExecutionPolicy','Bypass','-File', diff --git a/plugins/sap-dev-core/skills/sap-where-used-list/SKILL.md b/plugins/sap-dev-core/skills/sap-where-used-list/SKILL.md index 5df7831..a2b7b0d 100644 --- a/plugins/sap-dev-core/skills/sap-where-used-list/SKILL.md +++ b/plugins/sap-dev-core/skills/sap-where-used-list/SKILL.md @@ -137,16 +137,19 @@ $content = $content.Replace('%%OBJECT_TYPE%%', 'THE_OBJECT_TYPE') # empty $content = $content.Replace('%%OBJECT_NAME%%', 'THE_OBJECT_NAME') $content = $content.Replace('%%TO_SPOOL%%', 'THE_TO_SPOOL') # 'X' or empty $content = $content.Replace('%%SESSION_LOCK_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_session_lock.vbs') -# Session-attach plumbing (Phase 3.5 multi-connection aware). Resolution: -# explicit --session > SAPDEV_SESSION_PATH > sole- -# connection auto-default > refuse. See sap_attach_lib.vbs for details. +# Session-attach plumbing (Phase 4.2). Resolution: explicit --session > this AI +# session's pin, BAKED into %%SESSION_PATH%% (attach Strategy 1). Do NOT export +# $env:SAPDEV_SESSION_PATH instead: this generator is a SEPARATE process from the +# one that runs cscript, so the env var would already be gone and the helper would +# silently fall through to its sole-connection default -- which would list the +# callers found on a DIFFERENT SAP system than the one asked about (2026-08-06). +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' $sessionPath = '' # set to the parsed --session value if supplied +if (-not $sessionPath) { $sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' } $content = $content.Replace('%%SESSION_PATH%%', $sessionPath) $content = $content.Replace('%%ATTACH_LIB_VBS%%', '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs') -. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_where_used_list_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Run via 32-bit cscript: diff --git a/plugins/sap-gen-code/skills/sap-review-abap/SKILL.md b/plugins/sap-gen-code/skills/sap-review-abap/SKILL.md index 920cb18..5b6511a 100644 --- a/plugins/sap-gen-code/skills/sap-review-abap/SKILL.md +++ b/plugins/sap-gen-code/skills/sap-review-abap/SKILL.md @@ -159,11 +159,16 @@ $r = Read-SapAbapSource -Name '{OBJECT}' -Type '{TYPE}' -OutDir '{OUT}' -WithInc **Class / interface (GUI download; skip if `--no-gui`):** reuse the SE24 download VBS exactly as `/sap-explain-object` does — substitute -`%%CLASS_NAME%% %%OUTPUT_FILE%% %%SESSION_PATH%% %%ATTACH_LIB_VBS%%`, set -`$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}'`, -write UTF-16, and run via 32-bit cscript: -```bash -"C:/Windows/SysWOW64/cscript.exe" //NoLogo "{RUN_TEMP}\review_dl.vbs" +`%%CLASS_NAME%% %%OUTPUT_FILE%% %%SESSION_PATH%% %%ATTACH_LIB_VBS%%`, **baking** +`Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}'` into `%%SESSION_PATH%%` (an +`$env:SAPDEV_SESSION_PATH` exported by the generator dies with that process and +the attach lib then reads whatever GUI happens to be open — 2026-08-06), write +UTF-16, and run via 32-bit cscript with the GUI target declared in the SAME +block, so the reviewed body provably comes from the system the RFC readers used: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& "C:/Windows/SysWOW64/cscript.exe" //NoLogo "{RUN_TEMP}\review_dl.vbs" ``` If `--no-gui` and `{TYPE}=class`: skip the body, note "class body not acquired (--no-gui) — reviewed signature only", and run only the dimensions that work on diff --git a/plugins/sap-project/skills/sap-bp/SKILL.md b/plugins/sap-project/skills/sap-bp/SKILL.md index fa1e915..b52e973 100644 --- a/plugins/sap-project/skills/sap-bp/SKILL.md +++ b/plugins/sap-project/skills/sap-bp/SKILL.md @@ -189,14 +189,16 @@ Write `{RUN_TEMP}\sap_bp_check_run.ps1`: ```powershell $content = [System.IO.File]::ReadAllText('<SKILL_DIR>\references\sap_bp_check.vbs', [System.Text.Encoding]::UTF8) $content = $content -replace '%%BP_NUMBER%%','THE_BP_NUMBER' -# Phase 3.5 session-attach plumbing. -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# Phase 4.2 session-attach plumbing. BAKE the resolved path into %%SESSION_PATH%% +# (attach Strategy 1): this generator is a SEPARATE process from the one that runs +# cscript, so an $env:SAPDEV_SESSION_PATH exported here dies with it and the attach +# lib silently falls through to its sole-connection default (2026-08-06). . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_bp_check_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace `THE_BP_NUMBER` with the actual BP number and `<SKILL_DIR>` with the absolute path to this skill directory. @@ -207,8 +209,13 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_bp_check_run.ps1" ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_bp_check_run.vbs +Declare the GUI target in the SAME block as cscript — the attach lib reads +`SAPDEV_EXPECT_SYSTEM`/`_CLIENT` from the process environment, so a GUI parked on +a different system than the RFC leg is refused instead of driven: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_bp_check_run.vbs' ``` **Parse the last line of output:** @@ -235,14 +242,14 @@ $content = [System.IO.File]::ReadAllText('<SKILL_DIR>\references\sap_bp_update.v $content = $content -replace '%%BP_NUMBER%%','THE_BP_NUMBER' $content = $content -replace '%%DEFINITION_FILE%%','THE_DEFINITION_FILE' $content = $content -replace '%%SESSION_LOCK_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_session_lock.vbs' -# Phase 3.5 session-attach plumbing. -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# BAKE the session path (attach Strategy 1) -- an env var exported by this +# generator process never reaches the separate process that runs cscript. . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_bp_update_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace `THE_BP_NUMBER`, `THE_DEFINITION_FILE` (absolute path with backslashes), and `<SKILL_DIR>`. @@ -253,8 +260,12 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_bp_update_run.ps1" ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_bp_update_run.vbs +Declare the GUI target in the SAME block as cscript (see Step 4) so a BP update +can never land on a system other than the one the RFC leg resolved: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_bp_update_run.vbs' ``` Proceed to Step 6 to evaluate the result. @@ -292,14 +303,14 @@ $content = $content -replace '%%BP_ROLE%%','THE_BP_ROLE' $content = $content -replace '%%BP_GROUPING%%','THE_BP_GROUPING' $content = $content -replace '%%DEFINITION_FILE%%','THE_DEFINITION_FILE' $content = $content -replace '%%SESSION_LOCK_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_session_lock.vbs' -# Phase 3.5 session-attach plumbing. -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# BAKE the session path (attach Strategy 1) -- an env var exported by this +# generator process never reaches the separate process that runs cscript. . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_bp_create_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace all `THE_*` placeholders and `<SKILL_DIR>`. - `THE_BP_NUMBER`: Leave blank for auto-assign, or set an external number @@ -313,8 +324,12 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_bp_create_run.ps1" ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_bp_create_run.vbs +Declare the GUI target in the SAME block as cscript (see Step 4) so a BP create +can never land on a system other than the one the RFC leg resolved: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_bp_create_run.vbs' ``` Proceed to Step 6 to evaluate the result. diff --git a/plugins/sap-project/skills/sap-gateway-service/SKILL.md b/plugins/sap-project/skills/sap-gateway-service/SKILL.md index fb0a2c6..e40b354 100644 --- a/plugins/sap-project/skills/sap-gateway-service/SKILL.md +++ b/plugins/sap-project/skills/sap-gateway-service/SKILL.md @@ -73,23 +73,34 @@ via RFC_READ_TABLE or BBP_RFC_READ_TABLE (its RSTR/STRG columns trip a SAPLSDTX/ ASSIGN-CASTING dump) — the backend returns `GW_ERRLOG_GUI_ONLY`, so `errors` is driven by the `/IWFND/ERROR_LOG` **GUI reader** `sap_gateway_errlog_deep.vbs` (recorded + live-verified end-to-end on S4D 2026-07-11 — needs a live GUI session; pin one via `/sap-login`). Substitute the attach + IO -tokens, set `SAPDEV_SESSION_PATH` (parallel-safe attach contract), and run it via **32-bit cscript**: +tokens, **bake** the resolved session path (parallel-safe attach contract), and run it via +**32-bit cscript**: ```powershell $shared = '<SAP_DEV_CORE_SHARED_DIR>\scripts' . "$shared\sap_connection_lib.ps1" -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +# BAKE the path into %%SESSION_PATH%% (attach Strategy 1) rather than exporting +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that runs cscript, so the env var would already be gone and the attach lib would +# silently fall through to its sole-connection default (2026-08-06). +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' # or the --session value # PARAMS_FILE = KEY=VALUE lines: FROMDATE=YYYYMMDD TODATE=YYYYMMDD USER=<b> SERVICE=<n> TOPN=<n> [DEEP=1] $vbs = [IO.File]::ReadAllText('<SKILL_DIR>\references\sap_gateway_errlog_deep.vbs', [Text.Encoding]::UTF8) $vbs = $vbs.Replace('%%ATTACH_LIB_VBS%%', "$shared\sap_attach_lib.vbs") -$vbs = $vbs.Replace('%%SESSION_PATH%%', '') # or the --session value +$vbs = $vbs.Replace('%%SESSION_PATH%%', $sessionPath) $vbs = $vbs.Replace('%%PARAMS_FILE%%', '{RUN_TEMP}\gwerr_params.txt') $vbs = $vbs.Replace('%%OUTPUT_FILE%%', '{RUN_TEMP}\gwerr.json') [IO.File]::WriteAllText('{RUN_TEMP}\gwerr_run.vbs', $vbs, [System.Text.UnicodeEncoding]::new($false, $true)) ``` -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo "{RUN_TEMP}\gwerr_run.vbs" +Run with the GUI target declared in the SAME block — the RFC legs of this skill +and this GUI reader resolve their target through different chains, so without the +expectation the error log could come from a different system than the rest of the +report: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo "{RUN_TEMP}\gwerr_run.vbs" ``` Parse `GWERR: entries=<n> deep=<d> file=<path>` + `STATUS: OK`; a `STATUS: GRID_NOT_FOUND` line means diff --git a/plugins/sap-project/skills/sap-mm01/SKILL.md b/plugins/sap-project/skills/sap-mm01/SKILL.md index a21ad3b..9b1bdf3 100644 --- a/plugins/sap-project/skills/sap-mm01/SKILL.md +++ b/plugins/sap-project/skills/sap-mm01/SKILL.md @@ -224,14 +224,16 @@ Write `{RUN_TEMP}\sap_mm01_check_run.ps1`: ```powershell $content = [System.IO.File]::ReadAllText('<SKILL_DIR>\references\sap_mm01_check.vbs', [System.Text.Encoding]::UTF8) $content = $content -replace '%%MATERIAL%%','THE_MATERIAL' -# Phase 3.5 session-attach plumbing. -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# Phase 4.2 session-attach plumbing. BAKE the resolved path into %%SESSION_PATH%% +# (attach Strategy 1): this generator is a SEPARATE process from the one that runs +# cscript, so an $env:SAPDEV_SESSION_PATH exported here dies with it and the attach +# lib silently falls through to its sole-connection default (2026-08-06). . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_mm01_check_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace `THE_MATERIAL` with the actual material number and `<SKILL_DIR>` with the absolute path to this skill directory. @@ -242,8 +244,13 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_mm01_check_run.ps1" ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_mm01_check_run.vbs +Declare the GUI target in the SAME block as cscript — the attach lib reads +`SAPDEV_EXPECT_SYSTEM`/`_CLIENT` from the process environment, so a GUI parked on +a different system than the RFC leg is refused instead of driven: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_mm01_check_run.vbs' ``` **Parse the last line of output:** @@ -268,14 +275,14 @@ $content = [System.IO.File]::ReadAllText('<SKILL_DIR>\references\sap_mm01_update $content = $content -replace '%%MATERIAL%%','THE_MATERIAL' $content = $content -replace '%%DEFINITION_FILE%%','THE_DEFINITION_FILE' $content = $content -replace '%%SESSION_LOCK_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_session_lock.vbs' -# Phase 3.5 session-attach plumbing. -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# BAKE the session path (attach Strategy 1) -- an env var exported by this +# generator process never reaches the separate process that runs cscript. . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_mm01_update_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace `THE_MATERIAL`, `THE_DEFINITION_FILE` (absolute path with backslashes), and `<SKILL_DIR>`. @@ -286,8 +293,12 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_mm01_update_run.ps1" ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_mm01_update_run.vbs +Declare the GUI target in the SAME block as cscript (see Step 4) so a material +update can never land on a system other than the one the RFC leg resolved: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_mm01_update_run.vbs' ``` Proceed to Step 6 to evaluate the result. @@ -312,14 +323,14 @@ $content = $content -replace '%%INDUSTRY%%','THE_INDUSTRY' $content = $content -replace '%%MATERIAL_TYPE%%','THE_MATERIAL_TYPE' $content = $content -replace '%%DEFINITION_FILE%%','THE_DEFINITION_FILE' $content = $content -replace '%%SESSION_LOCK_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_session_lock.vbs' -# Phase 3.5 session-attach plumbing. -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# BAKE the session path (attach Strategy 1) -- an env var exported by this +# generator process never reaches the separate process that runs cscript. . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_mm01_create_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace all `THE_*` placeholders and `<SKILL_DIR>`. @@ -330,8 +341,12 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_mm01_create_run.ps1" ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_mm01_create_run.vbs +Declare the GUI target in the SAME block as cscript (see Step 4) so a material +create can never land on a system other than the one the RFC leg resolved: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_mm01_create_run.vbs' ``` Proceed to Step 6 to evaluate the result. diff --git a/plugins/sap-project/skills/sap-pfcg/SKILL.md b/plugins/sap-project/skills/sap-pfcg/SKILL.md index 0c5e0fb..59ce409 100644 --- a/plugins/sap-project/skills/sap-pfcg/SKILL.md +++ b/plugins/sap-project/skills/sap-pfcg/SKILL.md @@ -93,26 +93,35 @@ capture the `/sap-suim fetch-role` grant-set TSV as the human-facing before-stat abort on RETURN E/A incl. CUA-child, verify AGR_USERS re-read). - **create / generate (recorded GUI writes)** -> for create, resolve a **Customizing TR** via `/sap-transport-request --type customizing` (single roles are usually client-local, so the driver - tolerates no TR popup; generate needs no TR). Substitute the attach + lock + arg tokens, set - `SAPDEV_SESSION_PATH`, and run the driver via **32-bit cscript**: + tolerates no TR popup; generate needs no TR). Substitute the attach + lock + arg tokens, **bake** + the resolved session path, and run the driver via **32-bit cscript**: ```powershell $shared = '<SAP_DEV_CORE_SHARED_DIR>\scripts' . "$shared\sap_connection_lib.ps1" - $env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' + # BAKE the path into %%SESSION_PATH%% (attach Strategy 1) rather than exporting + # $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one + # that runs cscript, so the env var would already be gone and the attach lib would + # silently fall through to its sole-connection default (2026-08-06). + $sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' # or the --session value $drv = 'sap_pfcg_create.vbs' # or 'sap_pfcg_generate.vbs' $vbs = [IO.File]::ReadAllText("<SKILL_DIR>\references\$drv", [Text.Encoding]::UTF8) $vbs = $vbs.Replace('%%ATTACH_LIB_VBS%%', "$shared\sap_attach_lib.vbs") $vbs = $vbs.Replace('%%SESSION_LOCK_VBS%%', "$shared\sap_session_lock.vbs") - $vbs = $vbs.Replace('%%SESSION_PATH%%', '') # or the --session value + $vbs = $vbs.Replace('%%SESSION_PATH%%', $sessionPath) $vbs = $vbs.Replace('%%ROLE_NAME%%', '<ROLE>') $vbs = $vbs.Replace('%%ROLE_DESC%%', '<short description>') # create only $vbs = $vbs.Replace('%%TRANSPORT%%', '<customizing-TR>') # create only; empty -> ABORT if PFCG prompts [IO.File]::WriteAllText('{RUN_TEMP}\pfcg_run.vbs', $vbs, [System.Text.UnicodeEncoding]::new($false, $true)) ``` - ```bash - C:\Windows\SysWOW64\cscript.exe //NoLogo "{RUN_TEMP}\pfcg_run.vbs" + Declare the GUI target in the SAME block as cscript — the attach lib reads + `SAPDEV_EXPECT_SYSTEM`/`_CLIENT` from the process environment, so a role create / + generate can never land on a system other than the one the RFC leg resolved: + ```powershell + . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' + Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null + & 'C:\Windows\SysWOW64\cscript.exe' //NoLogo "{RUN_TEMP}\pfcg_run.vbs" ``` Parse `SUCCESS:` / `ERROR:` plus the machine markers — `PFCG_NO_AUTH_CREATE:` (create; missing diff --git a/plugins/sap-project/skills/sap-sm30/SKILL.md b/plugins/sap-project/skills/sap-sm30/SKILL.md index b77348a..7cf432b 100644 --- a/plugins/sap-project/skills/sap-sm30/SKILL.md +++ b/plugins/sap-project/skills/sap-sm30/SKILL.md @@ -94,25 +94,34 @@ The generic table-control driver `sap_sm30_maintain.vbs` is **recorded + live-ve (KEY=VALUE: `VIEW` / `MODE=add|update` / `DATA_FILE` / `TRKORR`), so the run-time VBS carries only the Tier-3 + IO tokens. `DATA_FILE` = the TSV whose header row = DD27S FIELDNAMEs (**key fields first**, in key order) and each later row = one entry; MANDT/CLIENT are auto and skipped. Substitute the attach + -lock + IO tokens, set `SAPDEV_SESSION_PATH` (parallel-safe attach contract), write UTF-16 LE, and run -via **32-bit cscript**: +lock + IO tokens, **bake** the resolved session path (parallel-safe attach contract), write UTF-16 LE, +and run via **32-bit cscript**: ```powershell $shared = '<SAP_DEV_CORE_SHARED_DIR>\scripts' . "$shared\sap_connection_lib.ps1" -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +# BAKE the path into %%SESSION_PATH%% (attach Strategy 1) rather than exporting +# $env:SAPDEV_SESSION_PATH here: this generator is a SEPARATE process from the one +# that runs cscript, so the env var would already be gone and the attach lib would +# silently fall through to its sole-connection default (2026-08-06). +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' # or the --session value # PARAMS_FILE lines: VIEW=<name> MODE=add|update DATA_FILE=<abs tsv> TRKORR=<customizing-TR-or-empty> $vbs = [IO.File]::ReadAllText('<SKILL_DIR>\references\sap_sm30_maintain.vbs', [Text.Encoding]::UTF8) $vbs = $vbs.Replace('%%ATTACH_LIB_VBS%%', "$shared\sap_attach_lib.vbs") $vbs = $vbs.Replace('%%SESSION_LOCK_VBS%%', "$shared\sap_session_lock.vbs") -$vbs = $vbs.Replace('%%SESSION_PATH%%', '') # or the --session value +$vbs = $vbs.Replace('%%SESSION_PATH%%', $sessionPath) $vbs = $vbs.Replace('%%PARAMS_FILE%%', '{RUN_TEMP}\sm30_params.txt') $vbs = $vbs.Replace('%%OUTPUT_FILE%%', '{RUN_TEMP}\sm30_result.json') [IO.File]::WriteAllText('{RUN_TEMP}\sm30_maintain_run.vbs', $vbs, [System.Text.UnicodeEncoding]::new($false, $true)) ``` -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo "{RUN_TEMP}\sm30_maintain_run.vbs" +Declare the GUI target in the SAME block as cscript — the attach lib reads +`SAPDEV_EXPECT_SYSTEM`/`_CLIENT` from the process environment, so customizing rows +can never be written to a system other than the one the RFC leg resolved: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo "{RUN_TEMP}\sm30_maintain_run.vbs" ``` **Generic table-control driver:** the overview belongs to the *generated* program SAPL<AREA>, so it diff --git a/plugins/sap-project/skills/sap-va01/SKILL.md b/plugins/sap-project/skills/sap-va01/SKILL.md index 451fdf4..c670ace 100644 --- a/plugins/sap-project/skills/sap-va01/SKILL.md +++ b/plugins/sap-project/skills/sap-va01/SKILL.md @@ -201,14 +201,16 @@ Write `{RUN_TEMP}\sap_va01_check_run.ps1`: ```powershell $content = [System.IO.File]::ReadAllText('<SKILL_DIR>\references\sap_va01_check.vbs', [System.Text.Encoding]::UTF8) $content = $content -replace '%%ORDER_NUMBER%%','THE_ORDER_NUMBER' -# Phase 3.5 session-attach plumbing. -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# Phase 4.2 session-attach plumbing. BAKE the resolved path into %%SESSION_PATH%% +# (attach Strategy 1): this generator is a SEPARATE process from the one that runs +# cscript, so an $env:SAPDEV_SESSION_PATH exported here dies with it and the attach +# lib silently falls through to its sole-connection default (2026-08-06). . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_va01_check_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace `THE_ORDER_NUMBER` with the actual order number and `<SKILL_DIR>` with the absolute path to this skill directory. @@ -219,8 +221,13 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_va01_check_run.ps1" ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_va01_check_run.vbs +Declare the GUI target in the SAME block as cscript — the attach lib reads +`SAPDEV_EXPECT_SYSTEM`/`_CLIENT` from the process environment, so a GUI parked on +a different system than the RFC leg is refused instead of driven: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_va01_check_run.vbs' ``` **Parse the last line of output:** @@ -249,14 +256,14 @@ $content = $content -replace '%%SESSION_LOCK_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\s # popup; 'X' ONLY when the user passed --allow-incomplete. $allowIncomplete = '' $content = $content -replace '%%ALLOW_INCOMPLETE%%', $allowIncomplete -# Phase 3.5 session-attach plumbing. -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# BAKE the session path (attach Strategy 1) -- an env var exported by this +# generator process never reaches the separate process that runs cscript. . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_va01_update_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace `THE_ORDER_NUMBER`, `THE_DEFINITION_FILE` (absolute path with backslashes), and `<SKILL_DIR>`. @@ -267,8 +274,12 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_va01_update_run.ps1" ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_va01_update_run.vbs +Declare the GUI target in the SAME block as cscript (see Step 4) so a sales-order +change can never land on a system other than the one the RFC leg resolved: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_va01_update_run.vbs' ``` Proceed to Step 6 to evaluate the result. @@ -297,14 +308,14 @@ $content = $content -replace '%%SESSION_LOCK_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\s # popup; 'X' ONLY when the user passed --allow-incomplete. $allowIncomplete = '' $content = $content -replace '%%ALLOW_INCOMPLETE%%', $allowIncomplete -# Phase 3.5 session-attach plumbing. -$sessionPath = '' -$content = $content -replace '%%SESSION_PATH%%', $sessionPath -$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' +# BAKE the session path (attach Strategy 1) -- an env var exported by this +# generator process never reaches the separate process that runs cscript. . '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' -$env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$sessionPath = Get-SapCurrentSessionPath -WorkTemp '{WORK_TEMP}' +$content = $content.Replace('%%SESSION_PATH%%', $sessionPath) +$content = $content -replace '%%ATTACH_LIB_VBS%%','<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_attach_lib.vbs' [System.IO.File]::WriteAllText('{RUN_TEMP}\sap_va01_create_run.vbs', $content, [System.Text.UnicodeEncoding]::new($false, $true)) -Write-Host 'Done' +Write-Host ("Done (session_path='" + $sessionPath + "')") ``` Replace all `THE_*` placeholders and `<SKILL_DIR>`. @@ -315,8 +326,12 @@ powershell -ExecutionPolicy Bypass -File "{RUN_TEMP}\sap_va01_create_run.ps1" ### Execute -```bash -C:\Windows\SysWOW64\cscript.exe //NoLogo {RUN_TEMP}\sap_va01_create_run.vbs +Declare the GUI target in the SAME block as cscript (see Step 4) so a sales order +is only ever created on the system the RFC leg resolved: +```powershell +. '<SAP_DEV_CORE_SHARED_DIR>\scripts\sap_connection_lib.ps1' +Set-SapGuiTargetExpectation -WorkTemp '{WORK_TEMP}' | Out-Null +& 'C:\Windows\SysWOW64\cscript.exe' //NoLogo '{RUN_TEMP}\sap_va01_create_run.vbs' ``` Proceed to Step 6 to evaluate the result. diff --git a/scripts/check-consistency.mjs b/scripts/check-consistency.mjs index ea2c856..a9be0fe 100644 --- a/scripts/check-consistency.mjs +++ b/scripts/check-consistency.mjs @@ -19,14 +19,19 @@ // - A file in sap-dev-core/shared/scripts is not mentioned in CLAUDE.md's // "Current Shared Files" table (authors' discovery surface; added // 2026-07-03 after 23 undocumented scripts were found) +// - A SKILL.md fenced block regresses on either half of the cross-system +// target guard: a dead session pin (exports $env:SAPDEV_SESSION_PATH but +// launches no child), or a write-capable cscript block with no +// Set-SapGuiTargetExpectation (added 2026-08-06) // WARN-level ratchets (do not fail the build yet): Phase-4 broker hints, // build-KPI enrichment, Step-0 work_dir resolution, {WORK_TEMP}-root scratch -// (.vbs/.ps1/.json/.xml/.log/.txt), +// (.vbs/.ps1/.json/.xml/.log/.txt), and // single/zero-consumer shared-script placement (CLAUDE.md placement rule, // reverse direction of the coverage ERROR above). // Former ratchets promoted to ERROR when their counts reached zero: // bare-cscript / wscript invocations, locale-literal GUI-text branching -// (both 2026-07-10); missing screen baselines (2026-07-24, at 136/136). +// (both 2026-07-10); missing screen baselines (2026-07-24, at 136/136); both +// cross-system target-guard halves (2026-08-06, after migrating 40 skills). import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs'; import { join, dirname, resolve, basename } from 'node:path'; @@ -1125,6 +1130,128 @@ const SAFETY_GATE_SKILLS = new Map([ } } +// --------------------------------------------------------------------------- +// Cross-system GUI target gate (added 2026-08-06). +// +// A skill that touches SAP over BOTH transports resolves its target twice, and +// the two chains can disagree: Connect-SapRfc walks pin -> GUI-active -> +// default -> sole-profile, while AttachSapSession walks hint -> +// SAPDEV_SESSION_PATH -> sole-connection -> refuse. Live incident (S4D/100 vs +// S4H/400): the AI session was pinned to S4D, the only attached GUI window was +// S4H, and /sap-se38 read S4H's Z_EXCEPTION_1 while the RFC leg read S4D's -- +// same program name, 7 lines apart, neither output naming its system. +// +// Two SKILL.md-side halves keep the legs together. Both are per-FENCED-BLOCK, +// because the process boundary is what breaks them: each fenced block is run as +// its OWN process, so anything exported in one block is gone by the next. +// +// (a) Dead session pin. A generator block that does +// $env:SAPDEV_SESSION_PATH = Get-SapCurrentSessionPath ... +// and leaves cscript to a LATER block exports into a process that exits +// immediately. The attach lib then finds no env var, falls through to its +// sole-connection default (Strategy 3), and silently discards the +// AI-session pin. The fix is to resolve the path in the generator and BAKE +// it into %%SESSION_PATH%% (attach Strategy 1), which survives the process +// boundary. So: an assignment with no cscript/wscript in the SAME block is +// a dead pin. +// +// (b) Missing target expectation. Set-SapGuiTargetExpectation +// (sap_connection_lib.ps1) exports SAPDEV_EXPECT_SYSTEM/_CLIENT from the +// same profile Connect-SapRfc would pick, and AssertSapGuiTarget +// (sap_attach_lib.vbs) turns a mismatch into a hard refusal. It only works +// when it runs in the process that launches cscript -- same boundary as +// (a). A write-capable skill invoking cscript without it can still deploy +// to the wrong system. +// +// Severity: both halves are HARD ERRORS. They shipped 2026-08-06 as WARN +// ratchets over 102 offenders (58 dead pins + 44 unverified write blocks) with a +// hard error only on the six flagship deploy skills; the remaining 34 skills were +// migrated the same day and both counts reached zero, so they were promoted per +// the bare-cscript / screen-baseline precedent. Comment lines (leading # or ') +// are stripped before matching, so prose about the env var never trips the gate. +// --------------------------------------------------------------------------- + +{ + // Fenced blocks, in source order. Any ``` line toggles; the info string is + // irrelevant here because a block's LANGUAGE does not decide which process + // runs it -- the fence boundary does. + const fencedBlocks = (md) => { + const lines = md.split(/\r?\n/); + const out = []; + let open = false, buf = [], startLine = 0; + for (let i = 0; i < lines.length; i++) { + if (/^\s*```/.test(lines[i])) { + if (!open) { open = true; buf = []; startLine = i + 2; } + else { open = false; out.push({ startLine, body: buf.join('\n') }); } + continue; + } + if (open) buf.push(lines[i]); + } + return out; // an unterminated final fence is ignored + }; + const codeLines = (body) => body.split(/\r?\n/).filter(l => !/^\s*(#|')/.test(l)); + const rePin = /\$env:SAPDEV_SESSION_PATH\s*=/; + // A child process inherits the environment of the process that spawns it, so an + // export is live as long as SOMETHING is launched from the same block. cscript + // is the usual consumer, but a child powershell counts too: sap-update-addon + // deliberately bridges the pin into a 32-bit `powershell -File ...detect.ps1`, + // which resolves the session itself. Only an export with NO child launch at all + // is dead. + const spawnsChild = (body) => codeLines(body).some( + (line) => /\b(powershell|pwsh)(\.exe)?(?![\w.])/i.test(line)); + // Same invocation heuristic as the bare-cscript gate above: the token must be + // followed by an argument, so a bare mention in running text is not a call. + const invokesHost = (body) => codeLines(body).some((line) => { + const tokRe = /\b(cscript|wscript)(\.exe)?(?![\w.])/gi; + let m; + while ((m = tokRe.exec(line)) !== null) { + if (/^["']?\s+(\/\/|["'{$]|[A-Za-z]:|\.[\\/])/.test(line.slice(m.index + m[0].length))) return true; + } + return false; + }); + // A block whose ONLY VBS targets are Tier-3 exempt has nothing for the + // expectation to enforce: those templates never include %%ATTACH_LIB_VBS%% and + // never call AttachSapSession, so AssertSapGuiTarget does not run and + // SAPDEV_EXPECT_SYSTEM would be inert. This is the pre-login bootstrap set -- + // sap_check_gui_login_status.vbs probes whether a session exists at all, and + // sap_gui_security_warmup.vbs drives a Hardcopy to materialise the SAP GUI + // Security dialog. Generated copies keep the stem plus a _run suffix, so + // normalise that away before testing. A block naming no .vbs at all stays + // gated (conservative -- e.g. a variable-built path). + const onlyExemptVbs = (body) => { + const named = [...body.matchAll(/([A-Za-z0-9_.\-]+)\.vbs\b/g)] + .map((m) => m[1].replace(/_run$/, '') + '.vbs'); + return named.length > 0 && named.every((n) => TIER3_EXEMPT_VBS.has(n)); + }; + + for (const plugin of mp.plugins) { + const sourceRel = plugin.source.replace(/^\.\//, '').replace(/\/$/, ''); + const skillsDir = join(repoRoot, sourceRel, 'skills'); + if (!existsSync(skillsDir)) continue; + const writeCapable = new Set(SAFETY_GATE_SKILLS.get(plugin.name) ?? []); + for (const skillEntry of readdirSync(skillsDir)) { + const skillMdPath = join(skillsDir, skillEntry, 'SKILL.md'); + if (!existsSync(skillMdPath)) continue; + for (const block of fencedBlocks(readFileSync(skillMdPath, 'utf8'))) { + const code = codeLines(block.body).join('\n'); + const hasHost = invokesHost(block.body); + + // (a) dead pin -- exported in a block that launches no child at all. + if (rePin.test(code) && !hasHost && !spawnsChild(block.body)) { + errors.push(`${plugin.name}: skills/${skillEntry}/SKILL.md:${block.startLine} sets $env:SAPDEV_SESSION_PATH in a fenced block that launches no child process -- that process exits before the VBS runs, so the AI-session pin is discarded and the attach lib falls through to its sole-connection default (the 2026-08-06 cross-system read). Resolve the path in the generator and bake it into %%SESSION_PATH%% instead (see sap-se38 Step A)`); + } + + // (b) write-capable cscript block with no declared target. + if (hasHost && !/Set-SapGuiTargetExpectation/.test(block.body) + && !onlyExemptVbs(block.body) + && writeCapable.has(skillEntry)) { + errors.push(`${plugin.name}: skills/${skillEntry}/SKILL.md:${block.startLine} invokes cscript from a write-capable skill without calling Set-SapGuiTargetExpectation in the SAME block -- SAPDEV_EXPECT_SYSTEM/_CLIENT must be exported by the process that launches cscript, else a GUI parked on another system is written to instead of refused (shared/scripts/sap_attach_lib.vbs AssertSapGuiTarget)`); + } + } + } + } +} + if (errors.length === 0) { let summary = `OK: ${mp.plugins.length} plugins, ${totalSkills} skills, all manifests aligned at version ${mp.version}, Tier 3 attach contract clean`; if (phase4Warnings.length > 0) {