# WSC-MVC — Test Results 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. Site: `WscMvc`, binding `*:8090`, physical path `C:\Projects\wsc-mvc\public` (updated 2026-09-19; project root through the M1/M2 sections below), app pool `WscMvc` (64-bit, no managed code). Date: 2026-09-19. ## M0 — Environment and feasibility | 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. ## M1 — Vertical HTTP slice ### WSH component smoke test 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 ``` ### HTTP integration test 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** ### Direct source/config access denied `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). ### Safe failure on broken/missing registration Steps: unregister both components -> request `/hello` -> observe safe 500 with no leaked path/exception -> re-register -> confirm recovery. - From the VM itself (`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). **PASS** - From a remote/tailnet client under the same unregistered condition: `HTTP/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). - After `tools\Register-Components.ps1`: HTTP and WSH tests both back to PASS (shown above). ### Reversible unregistration 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. ### Concurrency (basic) 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). ## Not yet run / out of scope for M1 - Formal 400/405 method-not-allowed handling — routing table doesn't exist until M3. - Broader concurrency/load testing — deferred to M6 per IMPLEMENTATION_PLAN. - 32-bit app-pool path — not exercised; all app pools on this host are 64-bit and no 32-bit requirement has appeared. Revisit if a future dependency needs 32-bit COM. ## M1 gate status: **PASS** 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. ## M2 — Lifecycle, central error handling, correlation-safe diagnostics ### WSH component tests (`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 ``` ### HTTP integration test (unchanged contract from the client's point of view) 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). ### Correlation-safe diagnostics (logging) 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** ### Concurrency (real, load-bearing this time) - 8 genuinely concurrent `/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. - Confirmed the 8 requests really do execute concurrently (not serialized by IIS/ASP) using a control experiment: 8 concurrent requests to a throwaway diagnostic page writing unique-per-request marker files (no shared-file contention possible) — 5 of 8 succeeded independently before the diagnostic page itself was deleted (the other 3 hit an unguarded race in the throwaway diagnostic script's own `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. ### Reversible unregistration (extended to three components) 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. ## Not yet run / out of scope for M2 - A logging mechanism with guaranteed no-loss-under-load delivery — explicitly deferred to M6 (see `docs/DECISIONS.md`). - Formal 400/405 method-not-allowed handling — still waiting on M3's route table. ## Test harness: GET /self-test (frontend-agnostic HTTP+JSON) 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** ```json {"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** ## M2 gate status: **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. ## Restructure: `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**