diff --git a/Controllers/HomeController.wsc b/Controllers/HomeController.wsc
new file mode 100644
index 0000000..c73e8ec
--- /dev/null
+++ b/Controllers/HomeController.wsc
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Default.asp b/Default.asp
new file mode 100644
index 0000000..571c0f7
--- /dev/null
+++ b/Default.asp
@@ -0,0 +1,43 @@
+<%@ Language="VBScript" %>
+<% Option Explicit %>
+<%
+Dim route, app, statusLine, contentType, body
+
+route = Request.QueryString("route")
+
+Set app = Nothing
+On Error Resume Next
+Set app = Server.CreateObject("WscMvc.Application")
+If Err.Number <> 0 Then
+ Err.Clear
+ On Error Goto 0
+ Response.Status = "500 Internal Server Error"
+ Response.ContentType = "text/plain; charset=utf-8"
+ Response.Write "Internal Server Error"
+ Response.End
+End If
+On Error Goto 0
+
+statusLine = ""
+contentType = ""
+body = ""
+
+On Error Resume Next
+app.Run route, statusLine, contentType, body
+If Err.Number <> 0 Then
+ Err.Clear
+ On Error Goto 0
+ Set app = Nothing
+ Response.Status = "500 Internal Server Error"
+ Response.ContentType = "text/plain; charset=utf-8"
+ Response.Write "Internal Server Error"
+ Response.End
+End If
+On Error Goto 0
+
+Response.Status = statusLine
+Response.ContentType = contentType
+Response.Write body
+
+Set app = Nothing
+%>
diff --git a/Framework/Application.wsc b/Framework/Application.wsc
new file mode 100644
index 0000000..128e9e5
--- /dev/null
+++ b/Framework/Application.wsc
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
new file mode 100644
index 0000000..50c783a
--- /dev/null
+++ b/docs/ARCHITECTURE.md
@@ -0,0 +1,40 @@
+# WSC-MVC — Architecture (as implemented through M1)
+
+## Request flow
+
+```
+GET /hello
+ -> IIS URL Rewrite rule "WscMvc-Hello" (^hello/?$)
+ -> /Default.asp?route=/hello
+ -> Server.CreateObject("WscMvc.Application")
+ -> Application.Run("/hello", statusLine, contentType, body) [Framework/Application.wsc]
+ -> CreateObject("WscMvc.HomeController")
+ -> HomeController.Hello(body) [Controllers/HomeController.wsc]
+ -> Default.asp sets Response.Status/ContentType, writes body
+```
+
+## Component boundary contract
+
+- `Default.asp` is the only file that touches ASP intrinsic objects (`Request`, `Response`, `Server`). It contains no business logic — only reading the `route` query parameter, invoking `WscMvc.Application`, and writing the response.
+- `Framework/Application.wsc` and `Controllers/HomeController.wsc` never reference `Request`/`Response`/`Server`/`Session`. All data crosses the ASP-to-WSC and WSC-to-WSC boundaries as VBScript scalars (strings), passed by VBScript's default ByRef semantics for "out" values. See `docs/DECISIONS.md` for why this sidesteps SPEC §15's open question about passing ASP intrinsics into WSC.
+- Routing in M1 is a single hardcoded `If pathInfo = "/hello"` check inside `Application.Run`. This is intentionally minimal and will be replaced by the explicit allowlisted route table in M3 (`Router.wsc`); do not extend it ad hoc before that milestone.
+
+## COM identity
+
+| Component | ProgID | CLSID |
+|---|---|---|
+| `Framework/Application.wsc` | `WscMvc.Application` | `{851C7763-1638-42FE-A166-BF3DD3A96A88}` |
+| `Controllers/HomeController.wsc` | `WscMvc.HomeController` | `{87488446-60BE-4068-8368-0B709BB68F3F}` |
+
+CLSIDs are fixed at creation and must never be recycled for a different component (AGENTS.md).
+
+## IIS site (test host: win2025test, 100.127.62.31)
+
+- Site: `WscMvc`, binding `*:8090`, physical path `C:\Projects\wsc-mvc`.
+- App pool: `WscMvc`, 64-bit, no managed code.
+- `web.config` hides `Framework/`, `Controllers/`, `tests/`, `tools/`, `docs/` (path-segment based, anywhere in the tree) and denies `.wsc/.vbs/.ps1/.md` by extension.
+- Default document is `Default.asp`; `/hello` is served via a rewrite rule, not the default document.
+
+## Deferred to later milestones (do not implement early)
+
+Per SPEC §3 non-goals and IMPLEMENTATION_PLAN M2+: request/response context object, central error mapping, explicit route table, HTML views/templates, ADODB, auth, logging. M1's error handling is intentionally local (`Default.asp` and `Application.wsc` each guard their own `CreateObject`/method-call boundary) rather than centralized.
diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md
new file mode 100644
index 0000000..d78c4bb
--- /dev/null
+++ b/docs/DECISIONS.md
@@ -0,0 +1,37 @@
+# WSC-MVC — Decisions and Experiment Log
+
+## M0 — Environment inventory (2026-09-19, win2025test VM, 100.127.62.31)
+
+Host: Windows Server 2025 Standard, build 10.0.26100, 64-bit, AMD Ryzen 5 1500X (4c).
+Verified via SSH (key-based, `Administrator`) + PowerShell 5.1.
+
+- IIS: installed (`Web-Server`) with `Web-ASP`, `Web-CGI`, `Web-ISAPI-Ext`, `Web-ISAPI-Filter`, management tools/console/scripting tools all Installed.
+- URL Rewrite module: present (`%SystemRoot%\system32\inetsrv\rewrite.dll`, registered as `RewriteModule` in global modules).
+- WebAdministration PowerShell module: available (v1.0.0.0).
+- WSC/COM runtime: `scrobj.dll` present in both `System32` and `SysWOW64`.
+- `regsvr32.exe`, `cscript.exe`, `wscript.exe`: present at standard `C:\WINDOWS\system32` paths.
+- Existing app pools are all 64-bit (`enable32BitAppOnWin64 = False`); no 32-bit requirement observed. WSC-MVC will target 64-bit and re-verify if this changes.
+- Existing sites: `Default Web Site` (`*:80`), `AspClassicUnifiedFramework` (`*:8080`, unrelated pre-existing project). No port conflict for a new dedicated site on `*:8090`.
+- Disk: `C:` only (106 GB, ~80 GB free). No `D:`/`E:`/`F:` volumes despite drive letters being visible (0 bytes each — not usable). Project deployed under `C:\Projects\wsc-mvc`.
+- Firewall: `World Wide Web Services (HTTP Traffic-In)` inbound rule enabled.
+- HTTP reachability: VM only reachable over the private Tailscale tailnet (100.127.62.31); no public exposure.
+
+No blocking gaps found for M0/M1. Proceeding to M1.
+
+## Open question #1 — Can WSC receive ASP intrinsic objects across the IIS/COM boundary?
+
+**Decision: not attempted for M1.** Per SPEC and AGENTS.md hard rules, business logic must never reference ASP `Request`/`Response`/`Server`/`Session` anyway, so this is avoided by design rather than tested as a compatibility question. `Application.Run` and `HomeController.Hello` use a **primitive-only, ByRef-out contract**: all parameters are strings (`pathInfo` in; `statusLine`, `contentType`, `body` out via VBScript's default ByRef parameter passing — no `ByVal`/`ByRef` keywords are available in WSC `` XML, so behavior follows the `Sub`/`Function` signature in the script block). `Default.asp` owns all ASP host object access and is the only file that touches `Request`/`Response`/`Server`.
+
+This resolves the open question pragmatically: no experiment needed because the architecture never crosses that boundary. Revisit only if a future milestone needs to pass richer data (e.g. multi-value POST bodies) — prefer additional scalar out-params or a `Scripting.Dictionary` (a standard COM component, not an ASP intrinsic) over passing ASP objects into a WSC.
+
+## Registration tooling
+
+`regsvr32.exe /s ".wsc"` registers a Windows Script Component directly (scrobj.dll is the underlying handler associated with the `.wsc` extension). Verified working for both `Application.wsc` and `HomeController.wsc` on this host — see `docs/TEST-RESULTS.md`. `/s /u` unregisters. Both scripts require elevation (registry write); the deployment/registration step is intentionally separate from the request-handling pipeline per SPEC §10 and AGENTS.md.
+
+## Static/source protection approach
+
+Used IIS `requestFiltering/hiddenSegments` (blocks any URL path containing `Framework`, `Controllers`, `tests`, `tools`, `docs` as a path segment, anywhere in the tree) plus `requestFiltering/fileExtensions` denylist for `.wsc`, `.vbs`, `.ps1`, `.md`. This keeps `Default.asp` at the project root (matching the SPEC §5 target tree) while denying direct access to source/config/test files, rather than moving `Default.asp` into a separate `public/` webroot. Verified with `tests/Test-Http.ps1` (direct `.wsc` request must return 404).
+
+## Site/port allocation
+
+New dedicated site `WscMvc`, app pool `WscMvc` (64-bit, no managed code), binding `*:8090`, physical path `C:\Projects\wsc-mvc`. Chosen to avoid the existing `*:80` and `*:8080` bindings already in use on this VM.
diff --git a/tests/Test-Components.vbs b/tests/Test-Components.vbs
new file mode 100644
index 0000000..bfe27eb
--- /dev/null
+++ b/tests/Test-Components.vbs
@@ -0,0 +1,69 @@
+Option Explicit
+
+Dim app, ctrl, statusLine, contentType, body, pass
+pass = True
+
+On Error Resume Next
+Set app = CreateObject("WscMvc.Application")
+If Err.Number <> 0 Then
+ WScript.Echo "FAIL: could not create WscMvc.Application - " & Err.Description
+ pass = False
+ Err.Clear
+End If
+On Error Goto 0
+
+If pass Then
+ statusLine = ""
+ contentType = ""
+ body = ""
+ On Error Resume Next
+ app.Run "/hello", statusLine, contentType, body
+ If Err.Number <> 0 Then
+ WScript.Echo "FAIL: Application.Run raised error on /hello - " & Err.Description
+ pass = False
+ Err.Clear
+ End If
+ On Error Goto 0
+End If
+
+If pass Then
+ If statusLine = "200 OK" And contentType = "text/html; charset=utf-8" And body = "Hello from WSC-MVC!" Then
+ WScript.Echo "PASS: /hello -> " & statusLine & " | " & contentType & " | " & body
+ Else
+ WScript.Echo "FAIL: unexpected /hello result -> [" & statusLine & "] [" & contentType & "] [" & body & "]"
+ pass = False
+ End If
+End If
+
+If pass Then
+ statusLine = ""
+ contentType = ""
+ body = ""
+ On Error Resume Next
+ app.Run "/does-not-exist", statusLine, contentType, body
+ If Err.Number <> 0 Then
+ WScript.Echo "FAIL: Application.Run raised error on unknown route - " & Err.Description
+ pass = False
+ Err.Clear
+ End If
+ On Error Goto 0
+End If
+
+If pass Then
+ If statusLine = "404 Not Found" Then
+ WScript.Echo "PASS: unknown route -> " & statusLine
+ Else
+ WScript.Echo "FAIL: unexpected unknown-route result -> [" & statusLine & "]"
+ pass = False
+ End If
+End If
+
+Set app = Nothing
+
+If pass Then
+ WScript.Echo "RESULT: ALL PASS"
+ WScript.Quit 0
+Else
+ WScript.Echo "RESULT: FAILURE"
+ WScript.Quit 1
+End If
diff --git a/tests/Test-Http.ps1 b/tests/Test-Http.ps1
new file mode 100644
index 0000000..53dc106
--- /dev/null
+++ b/tests/Test-Http.ps1
@@ -0,0 +1,50 @@
+[CmdletBinding()]
+param(
+ [Parameter(Mandatory = $true)]
+ [string]$BaseUrl
+)
+
+$script:failures = 0
+
+function Report {
+ param($ok, $label, $detail)
+ if ($ok) {
+ Write-Output "PASS: $label"
+ } else {
+ Write-Output "FAIL: $label ($detail)"
+ $script:failures++
+ }
+}
+
+try {
+ $resp = Invoke-WebRequest -Uri "$BaseUrl/hello" -UseBasicParsing
+ Report ($resp.StatusCode -eq 200) "GET /hello status 200" "got $($resp.StatusCode)"
+ Report ($resp.Headers['Content-Type'] -eq 'text/html; charset=utf-8') "GET /hello content-type" "got $($resp.Headers['Content-Type'])"
+ Report ($resp.Content -eq 'Hello from WSC-MVC!') "GET /hello body" "got '$($resp.Content)'"
+} catch {
+ Report $false "GET /hello request" $_.Exception.Message
+}
+
+try {
+ $wscResp = Invoke-WebRequest -Uri "$BaseUrl/Framework/Application.wsc" -UseBasicParsing
+ Report $false "GET /Framework/Application.wsc denied" "got $($wscResp.StatusCode)"
+} catch [System.Net.WebException] {
+ $webResp = $_.Exception.Response
+ if ($webResp) {
+ $code = [int]$webResp.StatusCode
+ Report ($code -eq 404) "GET /Framework/Application.wsc denied" "got $code"
+ } else {
+ Report $false "GET /Framework/Application.wsc denied" $_.Exception.Message
+ }
+} catch {
+ Report $false "GET /Framework/Application.wsc denied" $_.Exception.Message
+}
+
+Write-Output "---"
+if ($script:failures -eq 0) {
+ Write-Output "RESULT: ALL PASS"
+ exit 0
+} else {
+ Write-Output "RESULT: $($script:failures) FAILURE(S)"
+ exit 1
+}
diff --git a/tools/Register-Components.ps1 b/tools/Register-Components.ps1
new file mode 100644
index 0000000..28846df
--- /dev/null
+++ b/tools/Register-Components.ps1
@@ -0,0 +1,22 @@
+[CmdletBinding()]
+param(
+ [string]$ProjectRoot = (Split-Path -Parent $PSScriptRoot)
+)
+
+$components = @(
+ (Join-Path $ProjectRoot 'Framework\Application.wsc'),
+ (Join-Path $ProjectRoot 'Controllers\HomeController.wsc')
+)
+
+foreach ($path in $components) {
+ if (-not (Test-Path $path)) {
+ throw "Component not found: $path"
+ }
+ Write-Output "Registering $path"
+ $proc = Start-Process -FilePath 'regsvr32.exe' -ArgumentList "/s `"$path`"" -PassThru -Wait -WindowStyle Hidden
+ if ($proc.ExitCode -ne 0) {
+ throw "regsvr32 failed for $path (exit $($proc.ExitCode))"
+ }
+}
+
+Write-Output "All WSC-MVC components registered."
diff --git a/tools/Unregister-Components.ps1 b/tools/Unregister-Components.ps1
new file mode 100644
index 0000000..a2a434c
--- /dev/null
+++ b/tools/Unregister-Components.ps1
@@ -0,0 +1,23 @@
+[CmdletBinding()]
+param(
+ [string]$ProjectRoot = (Split-Path -Parent $PSScriptRoot)
+)
+
+$components = @(
+ (Join-Path $ProjectRoot 'Controllers\HomeController.wsc'),
+ (Join-Path $ProjectRoot 'Framework\Application.wsc')
+)
+
+foreach ($path in $components) {
+ if (-not (Test-Path $path)) {
+ Write-Warning "Component not found, skipping: $path"
+ continue
+ }
+ Write-Output "Unregistering $path"
+ $proc = Start-Process -FilePath 'regsvr32.exe' -ArgumentList "/s /u `"$path`"" -PassThru -Wait -WindowStyle Hidden
+ if ($proc.ExitCode -ne 0) {
+ throw "regsvr32 /u failed for $path (exit $($proc.ExitCode))"
+ }
+}
+
+Write-Output "All WSC-MVC components unregistered."
diff --git a/web.config b/web.config
new file mode 100644
index 0000000..010e330
--- /dev/null
+++ b/web.config
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+