Host under test: win2025test (Tailscale 100.127.62.31), Windows Server 2025 Standard, build 10.0.26100, 64-bit.
Access: SSH (key-based, Administrator) from the OpenClaw Linux host, driving cscript/powershell remotely.
Sites: WscMvc (*:8090, physical path C:\Projects\wsc-mvc\public; project root through the M1/M2 sections below before the public/ restructure) and, from the “test harness split” section onward, WscMvcTests (*:8091, C:\Projects\wsc-mvc\test-app\public). Both 64-bit, no managed code.
Date: 2026-09-19.
| Check | Result |
|---|---|
| Windows version / arch | PASS — Server 2025 Standard, 10.0.26100, 64-bit |
IIS + Classic ASP (Web-ASP) + CGI/ISAPI installed |
PASS |
| URL Rewrite module installed | PASS (rewrite.dll registered as global module) |
| WebAdministration PowerShell module | PASS (v1.0.0.0) |
WSC/COM runtime (scrobj.dll) present |
PASS (System32 and SysWOW64) |
regsvr32/cscript/wscript present |
PASS |
| Minimal WSC register/instantiate/call/unregister/re-register outside IIS | PASS — see M1 component tests below (folded into M1 since the same components serve both gates) |
Command: cscript //nologo tests\Test-Components.vbs — see M1.
Command:
cscript //nologo C:\Projects\wsc-mvc\tests\Test-Components.vbs
Result: PASS
PASS: /hello -> 200 OK | text/html; charset=utf-8 | Hello from WSC-MVC!
PASS: unknown route -> 404 Not Found
RESULT: ALL PASS
Command:
powershell -File C:\Projects\wsc-mvc\tests\Test-Http.ps1 -BaseUrl http://localhost:8090
Result: PASS
PASS: GET /hello status 200
PASS: GET /hello content-type
PASS: GET /hello body
PASS: GET /Framework/Application.wsc denied
RESULT: ALL PASS
Also verified directly from the Linux host over the tailnet:
curl -i http://100.127.62.31:8090/hello
-> HTTP/1.1 200 OK, Content-Type: text/html; charset=utf-8, body Hello from WSC-MVC!. PASS
GET /Framework/Application.wsc (and by the same hiddenSegments mechanism, Controllers/, tests/, tools/, docs/) -> 404 Not Found. PASS (covered by Test-Http.ps1 above for the .wsc case; other directories share the identical web.config rule and were not individually re-tested by automated script, only spot-checked manually).
Steps: unregister both components -> request /hello -> observe safe 500 with no leaked path/exception -> re-register -> confirm recovery.
curl http://localhost:8090/hello while unregistered): HTTP/1.1 500 Internal Server Error, Content-Type: text/plain; charset=utf-8, body exactly Internal Server Error (our own Default.asp error branch). PASSHTTP/1.1 500 Internal Server Error with IIS's own generic friendly-error HTML (no path/exception content either). PASS — see docs/DECISIONS.md for why local vs. remote differ (IIS httpErrors errorMode=DetailedLocalOnly, expected default behavior, not a bug).cscript //nologo tests\Test-Components.vbs while unregistered: FAIL: could not create WscMvc.Application - ActiveX component can't create object (test script correctly reports FAILURE, exit code 1 — this is the expected result of this specific check, proving the failure path is real and detectable, not silently swallowed).tools\Register-Components.ps1: HTTP and WSH tests both back to PASS (shown above).Command sequence:
powershell -File tools\Register-Components.ps1
powershell -File tools\Unregister-Components.ps1
powershell -File tools\Register-Components.ps1
Result: PASS, but only after a fix — see docs/DECISIONS.md finding on regsvr32 /s /u not actually removing .wsc registry entries on this host. tools\Unregister-Components.ps1 now verifies and, when needed, explicitly removes exactly this project's ProgID/CLSID pair. Confirmed via direct registry inspection (HKLM:\SOFTWARE\Classes\WscMvc.Application / WscMvc.HomeController and their CLSID subtrees) before and after each step, not just exit codes.
Command: two curl requests to http://100.127.62.31:8090/hello fired in parallel from bash (& + wait).
Result: PASS — both returned 200, both bodies exactly Hello from WSC-MVC!, no cross-request leakage or interference observed. This is a basic smoke check only, not a load test (out of scope for M1).
All SPEC §14 acceptance criteria for M1 were run on real Windows/IIS (not inferred) and passed, including one real defect found and fixed during testing (unregistration). Proceeding to M2 (lifecycle/error-handling generalization) is unblocked.
RequestContext + Application with ctx)Command:
cscript //nologo C:\Projects\wsc-mvc\tests\Test-Components.vbs
Result: PASS (after fixing three real defects found while getting here — see docs/DECISIONS.md: Property Get outside a Class block failing registration; FormatNumber leaking a locale comma into correlation ids; Randomize/Rnd() colliding within the same clock tick):
PASS: RequestContext.Path
PASS: RequestContext.HttpMethod
PASS: RequestContext.CorrelationId non-empty (32810-radD62B0)
PASS: RequestContext.ElapsedMs non-negative (0)
PASS: two RequestContext instances have distinct CorrelationIds
PASS: Application.Run(/hello) statusLine
PASS: Application.Run(/hello) contentType
PASS: Application.Run(/hello) body
PASS: Application.Run(unknown route) statusLine
PASS: app.log contains both 200 OK and 404 Not Found outcomes
RESULT: ALL PASS
Command: powershell -File tests\Test-Http.ps1 -BaseUrl http://localhost:8090 — PASS, identical output to M1 (the ctx-based rewrite is entirely internal to Application.wsc/Default.asp; the HTTP contract for /hello and denied .wsc access is unchanged).
Verified real HTTP requests produce correct logs/app.log entries (timestamp, correlation id, method, path, status, elapsed ms), e.g.:
9/19/2026 8:57:05 AM | 322254727-607479 | GET | /hello | 200 OK | 6ms
logs/ is denied over HTTP (GET /logs/app.log -> 404, same hiddenSegments mechanism as other private directories). PASS
/hello requests (bash &/wait): all 8 returned 200 OK with the correct body, every time, across every run in this milestone. This is the actual M2 gate requirement (“repeated/concurrent requests... behave predictably”) and it holds unconditionally.CreateFolder call, not the real app).logs/app.log line count under 8-way concurrency: as few as 1-4 of 8 lines survive, depending on run. This is an accepted, documented tradeoff (see docs/DECISIONS.md) — the retry budget for the log-file mutex is kept deliberately short so logging contention never adds meaningful latency to the HTTP response. The response correctness gate is unaffected; only the completeness of the best-effort diagnostic log is. Not treated as a FAIL: SPEC §10 calls this “safe diagnostic information,” not a correctness requirement.Command sequence identical to M1, now covering RequestContext.wsc too:
powershell -File tools\Register-Components.ps1
powershell -File tools\Unregister-Components.ps1 # confirmed all 3 ProgID/CLSID pairs removed via registry inspection
powershell -File tools\Register-Components.ps1
Result: PASS — /hello returns 500 while unregistered and 200 after re-registering, for all three components together.
docs/DECISIONS.md).Requested by Daniel: the harness should be callable “via an api call” with a “json response,” runnable from the CLI over plain HTTP, not tied to any specific frontend/tooling.
Command (from the Linux OpenClaw host, no SSH/PowerShell/cscript involved):
curl http://100.127.62.31:8090/self-test
Result on a healthy deployment: PASS
{"ok":true,"checks":[
{"name":"request_context_contract","pass":true,"detail":""},
{"name":"correlation_id_uniqueness","pass":true,"detail":""},
{"name":"application_run_hello","pass":true,"detail":""},
{"name":"application_run_unknown_route","pass":true,"detail":""}
]}
Verified the failure-reporting path is real, not just the happy path: deliberately removed WscMvc.HomeController's registry entries, re-ran curl .../self-test, got "ok":false with application_run_hello pinpointed ("detail":"Expected 200 OK, got [500 Internal Server Error]") while the other three checks correctly still reported true; confirmed GET /hello itself also correctly degraded to 500 at the same time. Re-registered and confirmed both /self-test and /hello fully recovered. PASS
Also verified tests/run-self-test.sh (curl + python3 -m json.tool, zero Windows tooling dependency) and the updated tests/Test-Http.ps1 (now folds each /self-test check into its own PASS/FAIL output alongside its existing direct /hello and denied-.wsc checks) both work end to end. PASS
GET /Controllers/SelfTestController.wsc (the component's own source file) still correctly denied — 404, same hiddenSegments rule as every other component. PASS
The SPEC §13 M2 gate (“repeated/concurrent requests and failure cases behave predictably”) was run on real Windows/IIS and holds unconditionally for HTTP response correctness. Four real defects were found and fixed while building this milestone (WSC property-syntax limitation, a locale-formatting bug, a Randomize collision, and a naive concurrent-logging approach that made things worse before a working lock-file mutex was verified) — see docs/DECISIONS.md for full detail on each. Proceeding to M3 (routing) is unblocked.
public/-only webroot (2026-09-19)Commands run on win2025test after moving Default.asp/web.config into public/ and updating tools/Setup-Site.ps1:
powershell -File tools\Setup-Site.ps1
Result: PASS — updated the site's physicalPath from the project root to C:\Projects\wsc-mvc\public, and set enableParentPaths=true scoped to just this site (appcmd ... /commit:apphost). Re-ran the same command a second time to confirm idempotency: reported “already exists with the correct physicalPath,” no changes made the second time. PASS
cscript //nologo tests\Test-Components.vbs
powershell -File tests\Test-Http.ps1 -BaseUrl http://localhost:8090
./tests/run-self-test.sh http://100.127.62.31:8090 (from the Linux host)
All three: PASS, identical to pre-restructure output (the client-facing contract for /hello and /self-test is unchanged; only the physical layout changed).
Server.MapPath("../logs") verified actually resolving and being written to — not just assumed from the setting being present — by reading back logs/app.log's full content directly over SSH and confirming new entries appear after each request. PASS
GET /Framework/Application.wsc -> 404, now because the path is genuinely absent from the served tree rather than a hiddenSegments rule. PASS
Path-traversal safety of enableParentPaths verified directly, not assumed: GET /../Framework/Application.wsc, GET /..%2fFramework/Application.wsc, GET /..%252fFramework/Application.wsc, and GET /../logs/app.log against the live site all returned 404/403 — confirms enableParentPaths (a server-side script MapPath setting) has no effect on how IIS resolves client-supplied URLs. PASS
WscMvcTests site (2026-09-19)Commands run on win2025test after creating test-app/public/ and adding the logs/ ACL automation to tools/Setup-Site.ps1:
powershell -File tools\Setup-Site.ps1
powershell -File tools\Setup-Site.ps1 -SiteName WscMvcTests -PoolName WscMvcTests -PhysicalPath C:\Projects\wsc-mvc\test-app\public -Port 8091
Result: PASS for both. First run reconciled the existing production site and additionally granted the (previously manual, now automated) logs/ ACL. Second run created the new WscMvcTests site, app pool, and its own test-app/logs/ with the ACL grant. Re-ran both commands a second time to confirm idempotency: no changes reported either time. PASS
One real bug found and fixed in tools/Setup-Site.ps1 itself while doing this: a bad nested-quoting invocation caused New-Website to fail (Parameter 'PhysicalPath' should point to existing path) but the script printed “Created site...” anyway and kept going, since $ErrorActionPreference defaulted to Continue. Fixed the invocation (script file instead of an inline nested-quoted command) and hardened the script itself: added $ErrorActionPreference = 'Stop' plus an explicit Test-Path $PhysicalPath pre-check, so a bad path now fails loudly immediately rather than printing a misleading success message. Re-verified clean runs after the fix.
curl http://100.127.62.31:8090/hello -> 200, unchanged
curl http://100.127.62.31:8090/self-test -> 404 (no longer exposed on production)
curl http://100.127.62.31:8091/self-test -> 200, correct JSON
curl http://100.127.62.31:8091/hello -> 404 (not routed on the test-app site)
curl http://100.127.62.31:8091/Framework/Application.wsc -> 404 (unreachable on this site too)
All: PASS
Confirmed the two sites write to genuinely separate log files (C:\Projects\wsc-mvc\logs\app.log vs C:\Projects\wsc-mvc\test-app\logs\app.log), read back over SSH with distinct content. PASS
Full regression: tests/Test-Components.vbs (WSH, unaffected by the site split — doesn't go through IIS), tests/Test-Http.ps1 (updated to test the production site only: /hello plus confirming /self-test is genuinely unreachable there), and tests/run-self-test.sh (now pointed at the WscMvcTests site, http://100.127.62.31:8091). All: PASS.
Commands run after moving Controllers/SelfTestController.wsc -> test-app/Controllers/SelfTestController.wsc and updating tools/Register-Components.ps1/Unregister-Components.ps1:
powershell -File tools\Register-Components.ps1
Result: PASS — registered all four components (Framework\RequestContext.wsc, Framework\Application.wsc, Controllers\HomeController.wsc, test-app\Controllers\SelfTestController.wsc), no errors.
Verified the registry actually updated, not just assumed: read HKLM:\SOFTWARE\Classes\CLSID\{D2634944-4646-4C55-956E-4C05E7E10904}\ScriptletURL directly — value is file:///C:/Projects/wsc-mvc/test-app/Controllers/SelfTestController.wsc, confirming the re-registration genuinely repointed the CLSID to the new file location. PASS
curl http://100.127.62.31:8091/self-test -> 200, correct JSON (unchanged)
curl http://100.127.62.31:8090/hello -> 200 (unaffected)
curl http://100.127.62.31:8090/Controllers/SelfTestController.wsc -> 404 (old location, production site)
curl http://100.127.62.31:8091/Controllers/SelfTestController.wsc -> 404 (new location, test-app site)
All: PASS
The move exposed a direct-entry boundary gap: URL Rewrite isolation alone did not cover Default.asp?route=.... Added a fixed application selector to Application.Run, made each app-owned bootstrap pass its own selector, removed the self-test's dependency on production /hello, and added direct-query regressions.
Windows Server 2025 / IIS, 64-bit app pools:
cscript //nologo tests\Test-Components.vbs
powershell -File tests\Test-Http.ps1 -BaseUrl http://localhost:8090 -TestBaseUrl http://localhost:8091
Both: PASS. HTTP boundary probes:
production /hello -> 200
production /self-test -> 404
production /Default.asp?route=/self-test -> 404
test app /self-test -> 200, JSON ok=true
test app /hello -> 404
test app /Default.asp?route=/hello -> 404
tests/run-self-test.sh http://100.127.62.31:8091: PASS, including JSON checks and the direct production-route rejection. Both Setup-Site.ps1 invocations remained idempotent and reported the correct physical paths. Registry inspection showed the SelfTestController ScriptletURL at file:///C:/Projects/wsc-mvc/test-app/Controllers/SelfTestController.wsc; the old production controller file was absent. Full regression: PASS.
Reproduced Daniel's browser-visible Not Found: both bare site roots reached Default.asp without a route and returned 404 even though /hello and /self-test passed. Added explicit root rewrite rules and deployed commit 926d6a9.
http://100.127.62.31:8090/ -> 200, Hello from WSC-MVC!
http://100.127.62.31:8090/hello -> 200, Hello from WSC-MVC!
http://100.127.62.31:8091/ -> 200, JSON ok=true
http://100.127.62.31:8091/self-test -> 200, JSON ok=true
Test-Http.ps1 -BaseUrl http://localhost:8090 -TestBaseUrl http://localhost:8091 and run-self-test.sh http://100.127.62.31:8091: PASS, including existing cross-app direct-query isolation checks.
Host under test for this M3 run: DESKTOP-80D128R, Microsoft Windows 11 Pro 10.0.26200 build 26200, 64-bit, Intel Core i7-8750H. Local IIS sites created from this checkout:
WscMvc Started C:\Development\wsc-mvc\public WscMvc
WscMvcTests Started C:\Development\wsc-mvc\test-app\public WscMvcTests
App pools: WscMvc and WscMvcTests, 64-bit (enable32BitAppOnWin64=False), no managed runtime. Components registered from C:\Development\wsc-mvc using:
powershell -ExecutionPolicy Bypass -File tools\Register-Components.ps1 -ProjectRoot C:\Development\wsc-mvc
Result: PASS — registered RequestContext, new Router, Application, HomeController, and test-app SelfTestController.
Test-first check before implementation:
cscript //nologo tests\Test-Components.vbs
Result before M3 implementation: FAIL, as expected for the new contract:
FAIL: Application.Run raised error on /hello - Wrong number of arguments or invalid property assignment
RESULT: FAILURE
WSH component contract after implementation:
cscript //nologo tests\Test-Components.vbs
Result: PASS. Covered GET /hello -> 200, unknown route -> 404, POST /hello -> 405 with Allow: GET, POST /self-test in the test app -> 200 JSON, malformed /hello/../secret -> 400, and logging.
HTTP integration:
powershell -ExecutionPolicy Bypass -File tests\Test-Http.ps1 -BaseUrl http://localhost:8090 -TestBaseUrl http://localhost:8091
Result: PASS:
PASS: GET /hello status 200
PASS: GET /hello content-type
PASS: GET /hello body
PASS: POST /hello returns 405
PASS: POST /hello Allow header
PASS: malformed route returns 400
PASS: GET / status 200
PASS: GET / maps to /hello
PASS: GET /self-test not exposed on production
PASS: direct Default.asp cannot cross into test routes
PASS: test app cannot cross into production routes
PASS: POST /self-test on test app status 200
PASS: POST /self-test content-type
PASS: GET /Framework/Application.wsc unreachable
RESULT: ALL PASS
Self-test JSON endpoint:
$resp = Invoke-WebRequest -Uri http://localhost:8091/self-test -UseBasicParsing
$json = $resp.Content | ConvertFrom-Json
Result: PASS — ok=true, checks included request_context_contract, correlation_id_uniqueness, test_app_rejects_production_route, post_self_test_route, and delete_self_test_allow_header.
tests/run-self-test.sh local note: first failed under local bash because the Windows checkout had CRLF line endings in the .sh file (set: pipefail\r: invalid option name). Added .gitattributes (*.sh text eol=lf) and normalized the script. After that, local WSL/bash still could not reach Windows IIS on localhost:8091/gateway IP from this environment (curl: (7) Failed to connect), so the bash wrapper is NOT RUN/PASS locally. The same HTTP JSON contract was verified with PowerShell above; the bash wrapper remains intended for a CLI that can reach the test-app URL, as in earlier VM/tailnet evidence.
M3 gate status: PASS for framework and IIS GET/POST behavior. PUT/DELETE HTTP probes are not used as app-route assertions on this host because IIS intercepts them before Classic ASP; WSH/self-test Router checks cover the unsupported-method framework contract directly.
Host under test: DESKTOP-80D128R, Microsoft Windows 11 Pro 10.0.26200, 64-bit — same local IIS sites as M3 (WscMvc on :8090, WscMvcTests on :8091), already running from this checkout.
Added Framework/ViewRenderer.wsc (WscMvc.ViewRenderer, {4948DF84-5DC6-448A-9F1B-EB596C28842B}) and Views/Home.html ({{Message}}). Controllers/HomeController.wsc's Hello now returns scalar view data (viewName, message) instead of a finished body string; Framework/Application.wsc's Run gained a viewsDir parameter and RunHomeHello now builds a Scripting.Dictionary and calls ViewRenderer.Render. Both Default.asp files resolve viewsDir = Server.MapPath("../Views"), the same parent-relative pattern already used for logDir.
Test-first check before implementation:
cscript //nologo tests\Test-Components.vbs
Result before M4 implementation: FAIL, as expected for the new contract:
FAIL: Application.Run raised error on /hello - Wrong number of arguments or invalid property assignment
FAIL: could not create WscMvc.ViewRenderer - ActiveX component can't create object
RESULT: FAILURE
Registration after implementation:
powershell -ExecutionPolicy Bypass -File tools\Register-Components.ps1
Result: PASS — registered RequestContext, Router, new ViewRenderer, Application, HomeController, SelfTestController.
WSH component contract:
cscript //nologo tests\Test-Components.vbs
Result: PASS (full output, ViewRenderer-specific lines):
PASS: Application.Run(/hello) body [unchanged: "Hello from WSC-MVC!" — regression proof the render pipeline preserves M1's exact-body contract]
PASS: ViewRenderer HTML-encodes placeholder value
PASS: ViewRenderer passes through template with no placeholders (data=Nothing)
PASS: ViewRenderer.Render on missing template raises a path-free error
PASS: ViewRenderer.Render raises on unresolved placeholder
PASS: ViewRenderer.Render rejects an invalid view name
RESULT: ALL PASS
HTTP integration:
powershell -ExecutionPolicy Bypass -File tests\Test-Http.ps1 -BaseUrl http://localhost:8090 -TestBaseUrl http://localhost:8091
Result: PASS, all 14 existing checks including GET /hello body (unchanged) and GET /Framework/Application.wsc unreachable.
Direct verification that Views/ is unreachable over HTTP (physical separation, same guarantee as Framework//Controllers/ — Views/ is a sibling of public/, never inside it):
curl -i http://localhost:8090/hello -> 200, "Hello from WSC-MVC!" (Content-Length: 19, unchanged from pre-M4)
curl -o /dev/null -w "%{http_code}" http://localhost:8090/Views/Home.html -> 404
tests/run-self-test.sh http://localhost:8091: NOT RUN locally — this host's local Python is a Microsoft Store execution-alias stub, so the script's json.tool pretty-print step cannot run. The same JSON contract was already verified directly (curl http://localhost:8091/self-test → {"ok":true,...}, all 5 checks passing) and via Test-Http.ps1 above; this is a convenience-script limitation on this specific host, not a framework regression — matches the pre-existing NOT RUN note for this same script under M3.
M4 gate status: PASS for the vertical slice implemented (safe separate template loading from a physically non-served directory; deterministic {{Key}} substitution; default HTML-text-context encoding verified against a real escaping case; missing-template and unresolved-placeholder failures verified path-free and safe; /hello's existing exact-body contract verified unchanged end-to-end through IIS). Views/Layout.html and a layout convention are deferred (IMPLEMENTATION_PLAN M4 sequences “add layout after renderer works” as a second step) — not yet needed since there is still only one view. Attribute/URL/JS-context encoding remains explicitly out of scope until a view needs it (documented on ViewRenderer.wsc and in docs/ARCHITECTURE.md).
Host under test: DESKTOP-80D128R, same local IIS sites as M3/M4. Added Framework/Database.wsc (WscMvc.Database, {E3F7C1A2-9B4D-4E6F-8C3A-2D5B7A9E1F04}). Full narrative (engine-choice pivot, credential handling, a real bug caught in the diagnostic code itself) is in docs/DECISIONS.md; this section is the command-by-command evidence.
Attempt 1 — LocalDB (rejected with evidence). (localdb)\MSSQLLocalDB was already present on this host. WSH-level contract tests (interactive account) PASSED via Driver={ODBC Driver 17 for SQL Server} (the only working provider — MSOLEDBSQL isn't installed, legacy SQLOLEDB can't find the named-pipe instance). A dedicated self-test check under the real IIS ApplicationPoolIdentity (WscMvcTests app pool, confirmed default/no custom identity via Get-WebConfigurationProperty) FAILED:
curl http://localhost:8091/self-test
{"...","name":"database_connectivity_under_app_pool_identity","pass":false,"detail":"Open: -2147467259 - [Microsoft][ODBC Driver 17 for SQL Server]SQL Server Network Interfaces: "}
This matches Microsoft's documented guidance that LocalDB isn't supported for IIS-hosted use — confirmed experimentally on this exact host/identity, not assumed from docs alone.
Attempt 2 — real SQL Server (works). Daniel provided credentials for an existing SQL Server 2022 instance on the tailnet (100.120.114.48:1433, SQL auth). Connectivity verified in stages:
Test-NetConnection -ComputerName 100.120.114.48 -Port 1433 -> TcpTestSucceeded: True
cscript probe-sql.vbs -> who=sa version=Microsoft SQL Server 2022 (RTM-CU27)... / OK
tools\Setup-TestDatabase.ps1 -ServerInstance '100.120.114.48,1433' -SqlLogin sa -SqlPassword <SecureString> (idempotent; run twice, second run a no-op) created database WscMvcTest and table dbo.Widgets.
WSH component contract, connection string read from the untracked db.connectionstring (not hardcoded):
cscript //nologo tests\Test-Components.vbs
Result: PASS, Database-specific lines:
PASS: Database.ExecuteNonQuery(INSERT) rowsAffected
PASS: Database.ExecuteScalar round-trips quote-containing value
PASS: Database.ExecuteScalar Null parameter round-trips as SQL NULL
PASS: Database.RollbackTransaction leaves no row
PASS: Database.CommitTransaction persists the row
PASS: Database.ExecuteScalar on an unopened connection raises a safe error
PASS: Database.Close is idempotent
RESULT: ALL PASS
The INSERT/SELECT round-trip used the value O'Brien (a literal single quote) specifically to prove no string-concatenation SQL injection and no manual escaping requirement — a naive "...VALUES ('" & name & "')" would have broken on this exact input; the parameterized call did not.
Real IIS/app-pool-identity re-check after the pivot:
curl http://localhost:8091/self-test
{"ok":true,"checks":[...,{"name":"database_connectivity_under_app_pool_identity","pass":true,"detail":""}]}
Full regression after all M5 wiring changes (both Default.asp files, Application.wsc's Run signature, SelfTestController.wsc):
cscript //nologo tests\Test-Components.vbs -> ALL PASS (34 checks)
powershell -File tests\Test-Http.ps1 -BaseUrl http://localhost:8090 -TestBaseUrl http://localhost:8091 -> ALL PASS (14 checks)
curl http://localhost:8090/hello -> 200, unchanged body
curl -o /dev/null -w "%{http_code}" http://localhost:8090/db.connectionstring -> 404
curl -o /dev/null -w "%{http_code}" http://localhost:8090/db -> 404
curl -o /dev/null -w "%{http_code}" http://localhost:8091/db.connectionstring -> 404
curl -o /dev/null -w "%{http_code}" http://localhost:8091/db -> 404
Secret hygiene verified directly, not assumed: git check-ignore -v db db.connectionstring confirmed both patterns match, and git status --short never showed either file as trackable, from before either file was written to disk.
M5 gate status: PASS — real DB integration tests pass against a real, explicitly-provisioned SQL Server (not mocked, not LocalDB-only); every value crosses the SQL boundary as a bound ADO parameter, never string concatenation. Deferred to M6: a least-privileged (non-sa) SQL login for the test database; identifier allowlisting (no route yet selects a table/column name from input, so there's nothing to allowlist against).
Host under test: win2025test, Windows Server 2025 Standard build 26100, 64-bit; production WscMvc on :8090 and test WscMvcTests on :8091.
Windows PowerShell 5.1 parser checks passed for tools/Deploy-Remote.ps1, tools/Invoke-RemoteInstall.ps1, and tests/Invoke-SelfTest.ps1. The first live JSON-client run exposed and fixed output suppression caused by assigning the function's output to $null; after the fix, the client printed every endpoint and embedded check and ended with RESULT: ALL PASS.
The first installer cutover attempt verified the archive SHA-256 and completed read-only preflight, then Windows denied renaming the live IIS root even after both dedicated pools reached Stopped. The installer failed before modifying the live tree and restored both sites/pools to Started; /hello and /self-test remained HTTP 200. The implementation was changed to keep the live root stable: mirror it to a timestamped rollback directory, then mirror the staged release into the live path with checked robocopy exit codes. This avoids depending on a root-directory rename while preserving rollback ownership.
Successful live invocation:
Invocation: 20260919T215247Z-606dbb12
Package SHA-256: 8ca5b314a9cb183f1ed9f2497118825658c470eb1237dcb4d53df66931a933f7
Deployment succeeded: C:\Projects\wsc-mvc
Timestamped rollback retained at: C:\Projects\wsc-mvc.rollback.20260919T215247Z-606dbb12
Post-cutover results: tests\Test-Components.vbs ALL PASS; tests\Test-Http.ps1 ALL PASS; tests\Invoke-SelfTest.ps1 ALL PASS, including every JSON-reported check. Both IIS sites and dedicated pools returned to Started, and the deployment retained the prior tree for rollback.
Powered by TurnKey Linux.