Просмотр исходного кода

Complete M3 routing contract

master
Daniel Covington 2 недель назад
Родитель
Сommit
38ab385f0b
19 измененных файлов: 558 добавлений и 103 удалений
  1. +1
    -0
      .gitattributes
  2. +4
    -1
      AGENTS.md
  3. +3
    -1
      CLAUDE.md
  4. +116
    -74
      Framework/Application.wsc
  5. +141
    -0
      Framework/Router.wsc
  6. +4
    -4
      IMPLEMENTATION_PLAN.md
  7. +1
    -1
      README.md
  8. +11
    -0
      SPEC.md
  9. +12
    -8
      docs/ARCHITECTURE.md
  10. +10
    -0
      docs/DECISIONS.md
  11. +77
    -0
      docs/TEST-RESULTS.md
  12. +6
    -2
      public/Default.asp
  13. +53
    -3
      test-app/Controllers/SelfTestController.wsc
  14. +6
    -2
      test-app/public/Default.asp
  15. +69
    -5
      tests/Test-Components.vbs
  16. +40
    -0
      tests/Test-Http.ps1
  17. +2
    -2
      tests/run-self-test.sh
  18. +1
    -0
      tools/Register-Components.ps1
  19. +1
    -0
      tools/Unregister-Components.ps1

+ 1
- 0
.gitattributes Просмотреть файл

@@ -0,0 +1 @@
*.sh text eol=lf

+ 4
- 1
AGENTS.md Просмотреть файл

@@ -36,7 +36,9 @@ User instructions > `SPEC.md` accepted decisions > this file > implementation pl
- No implicit web or filesystem access from a service merely because a host object happens to be available.

## Design and implementation workflow
For each change: explain a small intended behavior -> write/extend a test -> implement -> execute available tests -> review security and cleanup -> update docs. Keep one milestone in flight at a time. Prefer the simplest viable component contracts over prematurely generic abstractions. Keep `Default.asp` small and free from business rules.
For each change: define the contract first, including preconditions, postconditions, invariants, return/object ownership, and expected failure behavior; write or extend the closest failing test; implement the smallest production change; execute available tests; review security and cleanup; update docs. Keep one milestone in flight at a time. Prefer the simplest viable component contracts over prematurely generic abstractions. Keep `Default.asp` small and free from business rules.

Use TDD for new behavior and bug fixes. When practical, observe the test fail for the intended reason before implementation, then rerun it after the fix. If Windows/IIS access is unavailable for a host-dependent contract, mark that check NOT RUN and provide exact commands rather than treating review or parsing as a pass.

## Registration and deployment
- Each WSC class has unique, immutable CLSID and documented ProgID. Never reuse GUIDs or quietly rename registered interfaces.
@@ -50,6 +52,7 @@ For each change: explain a small intended behavior -> write/extend a test -> imp
- Test both WSH component creation and IIS HTTP behavior where applicable.
- For M1 verify `/hello` status/content-type/exact body; denied direct `.wsc` access; safe failure; reversible registration; basic concurrent requests.
- Add regression tests before fixing discovered bugs. Flag concurrency and cross-request state risks.
- Include contract tests for public APIs and trust boundaries: valid input, representative invalid input, Null/Empty/Nothing handling, object/scalar return semantics, and error paths.
- Do not claim production readiness until the relevant security/performance phases and deployment tests pass.

## Agent autonomy and self-improvement


+ 3
- 1
CLAUDE.md Просмотреть файл

@@ -9,12 +9,14 @@ Implement WSC-MVC milestone by milestone: IIS/Classic ASP host, registered VBScr
- At the start of each session, determine current milestone from implementation plan and actual tests, not from filenames alone.
- Inspect changed files and prior decisions; preserve user changes.
- For a task crossing architectural boundaries, first write a brief plan referencing the exact SPEC acceptance criteria.
- Before implementation, state the Design by Contract shape for the change: preconditions, postconditions, invariants, ownership, and expected failure behavior.
- Use small edits and run the closest available test after each meaningful increment.
- Follow TDD: write or update the failing test first where practical, confirm the failure is meaningful, then make the minimum implementation change and rerun it.
- If tool access lacks Windows or IIS, implement only what can be responsibly checked and clearly report Windows integration as NOT RUN.
- Do not turn guesses about WSC registration, COM marshaling, WSC XML semantics, IIS rewrite, or hot reload into established facts.

## Code review checklist
Confirm: VBScript `Option Explicit`; WSC `<public>` and script procedure agreement; stable COM identity; object/scalar assignment semantics; checked error paths; request-scoped state; safe response ownership; no URL-controlled arbitrary ProgID/method; no publicly accessible source/config/templates; tests updated; docs accurate.
Confirm: contract defined and tested; VBScript `Option Explicit`; WSC `<public>` and script procedure agreement; stable COM identity; object/scalar assignment semantics; checked error paths; request-scoped state; safe response ownership; no URL-controlled arbitrary ProgID/method; no publicly accessible source/config/templates; tests updated; docs accurate.

## Before concluding
Summarize modified files, what actually ran, proof of acceptance or blocked tests, and the single next milestone. Never say 'done' for a host-dependent milestone without IIS evidence. Propose rule improvements with evidence; do not silently edit architectural guardrails.


+ 116
- 74
Framework/Application.wsc Просмотреть файл

@@ -15,6 +15,7 @@
<parameter name="statusLine"/>
<parameter name="contentType"/>
<parameter name="body"/>
<parameter name="allowHeader"/>
</method>
</public>

@@ -26,93 +27,134 @@ Option Explicit
' (COM/method failure -> 500) outcomes, and one place logs them. ctx is our
' own WscMvc.RequestContext object (not an ASP intrinsic), carrying only
' primitive request data. No ASP intrinsics are referenced here.
Sub Run(ctx, applicationName, statusLine, contentType, body)
Dim ctrl, helloBody, path
Sub Run(ctx, applicationName, statusLine, contentType, body, allowHeader)
Dim router, handlerKey

path = ctx.Path
statusLine = ""
contentType = ""
body = ""
allowHeader = ""
handlerKey = ""

If applicationName = "production" And path = "/hello" Then
Set ctrl = Nothing
On Error Resume Next
Set ctrl = CreateObject("WscMvc.HomeController")
If Err.Number <> 0 Then
Err.Clear
On Error Goto 0
statusLine = "500 Internal Server Error"
contentType = "text/plain; charset=utf-8"
body = "Internal Server Error"
LogOutcome ctx, statusLine
Exit Sub
End If
Set router = Nothing
On Error Resume Next
Set router = CreateObject("WscMvc.Router")
If Err.Number <> 0 Then
Err.Clear
On Error Goto 0
InternalServerError statusLine, contentType, body, allowHeader
LogOutcome ctx, statusLine
Exit Sub
End If
On Error Goto 0

helloBody = ""
On Error Resume Next
ctrl.Hello helloBody
If Err.Number <> 0 Then
Err.Clear
On Error Goto 0
statusLine = "500 Internal Server Error"
contentType = "text/plain; charset=utf-8"
body = "Internal Server Error"
Set ctrl = Nothing
LogOutcome ctx, statusLine
Exit Sub
End If
On Error Resume Next
router.Match ctx, applicationName, statusLine, contentType, body, allowHeader, handlerKey
If Err.Number <> 0 Then
Err.Clear
On Error Goto 0
Set router = Nothing
InternalServerError statusLine, contentType, body, allowHeader
LogOutcome ctx, statusLine
Exit Sub
End If
On Error Goto 0

statusLine = "200 OK"
contentType = "text/html; charset=utf-8"
body = helloBody
Set ctrl = Nothing
ElseIf applicationName = "tests" And path = "/self-test" Then
Set ctrl = Nothing
On Error Resume Next
Set ctrl = CreateObject("WscMvc.SelfTestController")
If Err.Number <> 0 Then
Err.Clear
On Error Goto 0
statusLine = "500 Internal Server Error"
contentType = "text/plain; charset=utf-8"
body = "Internal Server Error"
LogOutcome ctx, statusLine
Exit Sub
End If
Set router = Nothing

If Len(handlerKey) = 0 Then
LogOutcome ctx, statusLine
Exit Sub
End If

Select Case handlerKey
Case "Home.Hello"
RunHomeHello ctx, statusLine, contentType, body, allowHeader
Case "SelfTest.RunSelfTest"
RunSelfTest ctx, statusLine, contentType, body, allowHeader
Case Else
InternalServerError statusLine, contentType, body, allowHeader
End Select

LogOutcome ctx, statusLine
End Sub

Sub RunHomeHello(ctx, statusLine, contentType, body, allowHeader)
Dim ctrl, helloBody

Set ctrl = Nothing
On Error Resume Next
Set ctrl = CreateObject("WscMvc.HomeController")
If Err.Number <> 0 Then
Err.Clear
On Error Goto 0
InternalServerError statusLine, contentType, body, allowHeader
Exit Sub
End If
On Error Goto 0

Dim selfTestBody
selfTestBody = ""
On Error Resume Next
ctrl.RunSelfTest ctx.LogDir, selfTestBody
If Err.Number <> 0 Then
Err.Clear
On Error Goto 0
statusLine = "500 Internal Server Error"
contentType = "text/plain; charset=utf-8"
body = "Internal Server Error"
Set ctrl = Nothing
LogOutcome ctx, statusLine
Exit Sub
End If
helloBody = ""
On Error Resume Next
ctrl.Hello helloBody
If Err.Number <> 0 Then
Err.Clear
On Error Goto 0
Set ctrl = Nothing
InternalServerError statusLine, contentType, body, allowHeader
Exit Sub
End If
On Error Goto 0

statusLine = "200 OK"
contentType = "text/html; charset=utf-8"
body = helloBody
allowHeader = ""
Set ctrl = Nothing
End Sub

Sub RunSelfTest(ctx, statusLine, contentType, body, allowHeader)
Dim ctrl, selfTestBody

Set ctrl = Nothing
On Error Resume Next
Set ctrl = CreateObject("WscMvc.SelfTestController")
If Err.Number <> 0 Then
Err.Clear
On Error Goto 0
InternalServerError statusLine, contentType, body, allowHeader
Exit Sub
End If
On Error Goto 0

' Always 200: the self-test HTTP call itself succeeded. Whether the
' underlying checks passed is reported in the JSON body's "ok" field
' and per-check "pass" fields, not the HTTP status - this matches
' conventional health-check endpoint design (5xx is reserved for the
' diagnostics mechanism itself being broken, handled above).
statusLine = "200 OK"
contentType = "application/json; charset=utf-8"
body = selfTestBody
selfTestBody = ""
On Error Resume Next
ctrl.RunSelfTest ctx.LogDir, selfTestBody
If Err.Number <> 0 Then
Err.Clear
On Error Goto 0
Set ctrl = Nothing
Else
' Expected outcome, not a failure: no matching route yet (M3 adds a real table).
statusLine = "404 Not Found"
contentType = "text/plain; charset=utf-8"
body = "Not Found"
InternalServerError statusLine, contentType, body, allowHeader
Exit Sub
End If
On Error Goto 0

LogOutcome ctx, statusLine
' Always 200: the self-test HTTP call itself succeeded. Whether the
' underlying checks passed is reported in the JSON body's "ok" field
' and per-check "pass" fields, not the HTTP status - this matches
' conventional health-check endpoint design (5xx is reserved for the
' diagnostics mechanism itself being broken, handled above).
statusLine = "200 OK"
contentType = "application/json; charset=utf-8"
body = selfTestBody
allowHeader = ""
Set ctrl = Nothing
End Sub

Sub InternalServerError(statusLine, contentType, body, allowHeader)
statusLine = "500 Internal Server Error"
contentType = "text/plain; charset=utf-8"
body = "Internal Server Error"
allowHeader = ""
End Sub

' Best-effort diagnostics only: a logging failure must never affect the


+ 141
- 0
Framework/Router.wsc Просмотреть файл

@@ -0,0 +1,141 @@
<?xml version="1.0"?>
<?component error="true" debug="false"?>
<component>
<registration
description="WscMvc explicit route table"
progid="WscMvc.Router"
version="1.00"
classid="{C92F9338-B478-4EAD-B865-892FFB1E1C51}">
</registration>

<public>
<method name="Match">
<parameter name="ctx"/>
<parameter name="applicationName"/>
<parameter name="statusLine"/>
<parameter name="contentType"/>
<parameter name="body"/>
<parameter name="allowHeader"/>
<parameter name="handlerKey"/>
</method>
</public>

<script language="VBScript">
<![CDATA[
Option Explicit

' Explicit allowlisted route table. This component decides only route
' metadata; Application.wsc performs the corresponding hardcoded handler
' calls. No URL segment is ever treated as a ProgID or method name.
Sub Match(ctx, applicationName, statusLine, contentType, body, allowHeader, handlerKey)
Dim rawPath, normalizedPath, httpMethod

statusLine = ""
contentType = ""
body = ""
allowHeader = ""
handlerKey = ""

rawPath = ctx.Path
httpMethod = UCase(Trim(CStr(ctx.HttpMethod)))

If Not TryNormalizePath(rawPath, normalizedPath) Then
BadRequest statusLine, contentType, body
Exit Sub
End If

' Literal routes are intentionally listed directly. Parameterized routes,
' when added, must be checked after these literal comparisons.
If applicationName = "production" Then
If normalizedPath = "/hello" Then
MatchMethod httpMethod, "GET", "Home.Hello", statusLine, contentType, body, allowHeader, handlerKey
Exit Sub
End If
ElseIf applicationName = "tests" Then
If normalizedPath = "/self-test" Then
MatchMethod httpMethod, "GET, POST", "SelfTest.RunSelfTest", statusLine, contentType, body, allowHeader, handlerKey
Exit Sub
End If
End If

statusLine = "404 Not Found"
contentType = "text/plain; charset=utf-8"
body = "Not Found"
End Sub

Function TryNormalizePath(rawPath, normalizedPath)
Dim path

TryNormalizePath = False
normalizedPath = ""

If IsNull(rawPath) Or IsEmpty(rawPath) Then
path = "/"
Else
path = Trim(CStr(rawPath))
If Len(path) = 0 Then path = "/"
End If

If Left(path, 1) <> "/" Then Exit Function
If InStr(path, "\") > 0 Then Exit Function
If InStr(path, "%") > 0 Then Exit Function
If InStr(path, "?") > 0 Then Exit Function
If InStr(path, "#") > 0 Then Exit Function
If InStr(path, "//") > 0 Then Exit Function
If InStr(path, "/./") > 0 Then Exit Function
If InStr(path, "/../") > 0 Then Exit Function
If Right(path, 2) = "/." Then Exit Function
If Right(path, 3) = "/.." Then Exit Function
If ContainsControlCharacter(path) Then Exit Function

If Len(path) > 1 And Right(path, 1) = "/" Then
path = Left(path, Len(path) - 1)
End If

normalizedPath = LCase(path)
TryNormalizePath = True
End Function

Function ContainsControlCharacter(value)
Dim i, code
ContainsControlCharacter = False
For i = 1 To Len(value)
code = Asc(Mid(value, i, 1))
If code < 32 Or code = 127 Then
ContainsControlCharacter = True
Exit Function
End If
Next
End Function

Sub MatchMethod(httpMethod, allowedMethods, matchedHandlerKey, statusLine, contentType, body, allowHeader, handlerKey)
If IsAllowedMethod(httpMethod, allowedMethods) Then
handlerKey = matchedHandlerKey
Else
statusLine = "405 Method Not Allowed"
contentType = "text/plain; charset=utf-8"
body = "Method Not Allowed"
allowHeader = allowedMethods
End If
End Sub

Function IsAllowedMethod(httpMethod, allowedMethods)
Dim methods, i
methods = Split(allowedMethods, ",")
IsAllowedMethod = False
For i = 0 To UBound(methods)
If httpMethod = Trim(methods(i)) Then
IsAllowedMethod = True
Exit Function
End If
Next
End Function

Sub BadRequest(statusLine, contentType, body)
statusLine = "400 Bad Request"
contentType = "text/plain; charset=utf-8"
body = "Bad Request"
End Sub
]]>
</script>
</component>

+ 4
- 4
IMPLEMENTATION_PLAN.md Просмотреть файл

@@ -26,10 +26,10 @@ Gate: all M1 SPEC acceptance tests pass on IIS, or mark blocked with evidence; d
Gate: repeated/concurrent requests and failure cases behave predictably. **MET** — see `docs/TEST-RESULTS.md`. HTTP response correctness holds unconditionally under concurrency (8/8 correct every run); best-effort logging completeness under heavy concurrency is an accepted, documented tradeoff, not a gate failure.

## M3 — Routing
- [ ] Explicit route map with GET/POST, literal routes first.
- [ ] Safe decoding/validation, 400/404/405, Allow header.
- [ ] No arbitrary URL-to-ProgID or URL-to-method dispatch.
Gate: route matrix and malformed URL tests pass.
- [x] Explicit route map with GET/POST, literal routes first.
- [x] Safe decoding/validation, 400/404/405, Allow header.
- [x] No arbitrary URL-to-ProgID or URL-to-method dispatch.
Gate: route matrix and malformed URL tests pass. **MET** — see `docs/TEST-RESULTS.md`. WSH covers full Router/Application routing contract; IIS HTTP covers GET/POST-routed behavior and malformed route handling. PUT/DELETE can be intercepted by IIS before Classic ASP on the tested host, so those are not treated as framework HTTP-route assertions.

## M4 — Views
- [ ] Safe separate template loading, text-context encoding, default-deny for private templates.


+ 1
- 1
README.md Просмотреть файл

@@ -35,4 +35,4 @@ powershell -File tests\Test-Http.ps1 -BaseUrl http://localhost:8090 -TestBaseUrl

## Status

M0–M2 gated PASS with real test evidence on Windows/IIS; see `docs/TEST-RESULTS.md`. Next milestone: M3 (explicit route table) per `IMPLEMENTATION_PLAN.md`.
M0–M3 gated PASS with real test evidence on Windows/IIS; see `docs/TEST-RESULTS.md`. Next milestone: M4 (safe separate HTML templates and encoding) per `IMPLEMENTATION_PLAN.md`.

+ 11
- 0
SPEC.md Просмотреть файл

@@ -6,6 +6,8 @@ Status: Proposed; implementation behavior must be verified on the target Windows
## 1. Mission
Build a small, maintainable, WSC-first MVC framework for Classic ASP. IIS receives HTTP traffic; a single ASP bootstrap dispatches requests to VBScript Windows Script Components registered as COM objects. Application behavior lives in WSC files; presentation lives in separate HTML templates. Ship verified increments rather than an elaborate untested framework.

Development uses Design by Contract and test-driven development: define the observable contract first, write or extend the failing test that proves the contract, then implement the smallest change that makes the test pass.

## 2. Fixed decisions
- Runtime: Windows, IIS, Classic ASP, VBScript; no ASP.NET, PHP, Node.js, or JScript runtime dependency.
- Object architecture: registered `.wsc` COM components; stable ProgIDs and CLSIDs.
@@ -123,6 +125,14 @@ The tree is a target, not an instruction to create placeholder classes before th
- Define a testable strategy for already-committed responses. Never pretend an exception can undo a sent body.
- Distinguish an expected not-found/validation result from a programmer/COM failure.

## 11A. Development method contract
- Use Design by Contract for every public component/API change: document the caller obligations (preconditions), promised outcomes (postconditions), stable assumptions across calls (invariants), error behavior, and object/scalar ownership before or alongside implementation.
- Contracts must be concrete enough to test in VBScript/WSH, IIS HTTP tests, or a documented manual host check. Avoid vague promises such as "handles invalid input" without named cases.
- Use TDD for new behavior and bug fixes: add or update the closest failing test first, confirm it fails for the intended reason where practical, then implement the minimum production change and rerun the test.
- Keep contract checks explicit at trust boundaries: route input, COM public methods, template data, database parameters, filesystem paths, and deployment scripts.
- If a host-dependent contract cannot be executed in the current environment, mark it NOT RUN with the exact Windows/IIS command that must verify it; do not treat static inspection as a passing test.
- Do not weaken an existing contract to make a test pass unless the SPEC or `docs/DECISIONS.md` is deliberately updated with the reason and evidence.

## 12. Definition of done for each component
1. Contract and source file exist; XML is well-formed.
2. Public declarations match implemented functions/procedures and documented names.
@@ -133,6 +143,7 @@ The tree is a target, not an instruction to create placeholder classes before th
7. Security review covers direct HTTP exposure and untrusted input.
8. README or architecture docs reflect actual implementation, not intentions.
9. Tests are reported as PASS, FAIL, or NOT RUN with commands, host details, and evidence.
10. Preconditions, postconditions, invariants, and representative invalid inputs have automated tests or an explicit NOT RUN/manual verification note.

## 13. Milestone sequence and gates
M0: Inspect repository/environment; record OS/IIS/ASP/bitness/permissions and missing tools. No claim of execution on Linux/macOS.


+ 12
- 8
docs/ARCHITECTURE.md Просмотреть файл

@@ -1,4 +1,4 @@
# WSC-MVC — Architecture (as implemented through M2)
# WSC-MVC — Architecture (as implemented through M3)

## Request flow

@@ -8,19 +8,22 @@ GET / or GET /hello
-> /Default.asp?route=/hello
-> Server.CreateObject("WscMvc.RequestContext"); ctx.Initialize path, httpMethod, logDir
-> Server.CreateObject("WscMvc.Application")
-> Application.Run(ctx, "production", statusLine, contentType, body) [Framework/Application.wsc]
-> Application.Run(ctx, "production", statusLine, contentType, body, allowHeader) [Framework/Application.wsc]
-> CreateObject("WscMvc.Router")
-> Router.Match(ctx, "production", ..., handlerKey) [Framework/Router.wsc]
-> CreateObject("WscMvc.HomeController")
-> HomeController.Hello(body) [Controllers/HomeController.wsc]
-> LogOutcome ctx, statusLine (best-effort append to logs/app.log)
-> Default.asp sets Response.Status/ContentType, writes body
-> Default.asp sets Response.Status/ContentType, optional Allow header, 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 and `REQUEST_METHOD`, resolving `logs/`'s physical path via `Server.MapPath`, invoking `WscMvc.RequestContext` and `WscMvc.Application`, and writing the response.
- `Framework/RequestContext.wsc`, `Framework/Application.wsc`, `Controllers/HomeController.wsc`, and `test-app/Controllers/SelfTestController.wsc` never reference `Request`/`Response`/`Server`/`Session`. All data crosses the ASP-to-WSC and WSC-to-WSC boundaries as either VBScript scalars (strings) or our own `RequestContext` COM object (not an ASP intrinsic) — never an ASP host object. See `docs/DECISIONS.md` for why the original SPEC §15 open question about passing ASP intrinsics into a WSC never needed a direct experiment: the architecture never crosses that boundary by design.
- `Framework/RequestContext.wsc`, `Framework/Router.wsc`, `Framework/Application.wsc`, `Controllers/HomeController.wsc`, and `test-app/Controllers/SelfTestController.wsc` never reference `Request`/`Response`/`Server`/`Session`. All data crosses the ASP-to-WSC and WSC-to-WSC boundaries as either VBScript scalars (strings) or our own `RequestContext` COM object (not an ASP intrinsic) — never an ASP host object. See `docs/DECISIONS.md` for why the original SPEC §15 open question about passing ASP intrinsics into a WSC never needed a direct experiment: the architecture never crosses that boundary by design.
- WSC public members are exposed as plain `<method>` entries backed by ordinary `Sub`/`Function` procedures, never `<property>`/`Property Get`. VBScript's `Property Get/Let/Set` requires a `Class...End Class` block and cannot appear at a WSC's top-level script scope — confirmed experimentally (see `docs/DECISIONS.md`), not assumed from general WSC documentation.
- Routing in M1/M2 is still a minimal hardcoded allowlist inside `Application.Run`. Every call includes a fixed application name (`production` or `tests`) supplied by that app's own bootstrap; a route must match both the application and path. This prevents direct `Default.asp?route=...` requests from crossing between apps. M3 will replace these checks with the explicit route table in `Router.wsc`.
- Routing is an explicit literal allowlist in `Framework/Router.wsc`. Every call includes a fixed application name (`production` or `tests`) supplied by that app's own bootstrap; a route must match both the application and normalized path. Literal routes are checked directly before any future parameterized route support. Router decisions return either a hardcoded handler key for `Application.wsc` to dispatch with explicit `Select Case` branches, or a completed expected response (`400`, `404`, `405` with `Allow`). No URL segment is ever treated as a ProgID, method name, template name, or filesystem path.
- Route path handling is deliberately conservative in M3: ASP/IIS has already decoded the `route` query parameter once, so `Router.wsc` does not decode again. It rejects remaining `%` escapes, backslashes, query/fragment markers, doubled slashes, dot-segments, and control characters, then lowercases and trims one trailing slash for literal matching. The original path remains available through `RequestContext.Path` for logging.
- `Application.Run` is the single central point that decides expected (404, ordinary control flow) vs. unexpected (COM/method failure, 500) outcomes, and the single point that logs every outcome. `Default.asp` still independently guards its own three sequential calls (`RequestContext` creation, `Initialize`, `Application` creation, `Run`) since a WSC failing to even instantiate happens outside `Application.Run`'s reach.

## Per-request lifetime and diagnostics
@@ -31,7 +34,7 @@ GET / or GET /hello

## Test harness: GET /self-test — its own app, only Framework/ is shared

`test-app/Controllers/SelfTestController.wsc` (`WscMvc.SelfTestController`) exposes framework checks over plain HTTP and returns JSON — no PowerShell, cscript, or SSH access to the VM required. Both the test site's root (`GET /`) and `GET /self-test` rewrite to the test app's `/self-test` route. Production similarly maps both `GET /` and `GET /hello` to `/hello`.
`test-app/Controllers/SelfTestController.wsc` (`WscMvc.SelfTestController`) exposes framework checks over plain HTTP and returns JSON — no PowerShell, cscript, or SSH access to the VM required. Both the test site's root (`GET /`) and `GET /self-test` rewrite to the test app's `/self-test` route. The route table allows both `GET` and `POST` for `/self-test`. Production similarly maps both `GET /` and `GET /hello` to `/hello`, which is `GET` only.

**This lives on its own IIS site (`test-app/`), not the production site.** Changed 2026-09-19 at Daniel's explicit direction: a diagnostics endpoint permanently reachable on the same site/port as real traffic was the wrong shape. `test-app/public/` is a second, separate IIS site's physical path (own site name, own app pool, own port, own `logs/`), whose `web.config` routes `/self-test` and nothing else. The production site's `web.config` no longer has a `/self-test` rewrite rule at all — `GET /self-test` against the production site now returns a plain `404` (nothing rewrites that path to `Default.asp`).

@@ -55,11 +58,12 @@ curl http://100.127.62.31:8091/self-test
| Component | ProgID | CLSID |
|---|---|---|
| `Framework/RequestContext.wsc` | `WscMvc.RequestContext` | `{1C36FA55-34DF-4974-94B9-D657389362B2}` |
| `Framework/Router.wsc` | `WscMvc.Router` | `{C92F9338-B478-4EAD-B865-892FFB1E1C51}` |
| `Framework/Application.wsc` | `WscMvc.Application` | `{851C7763-1638-42FE-A166-BF3DD3A96A88}` |
| `Controllers/HomeController.wsc` | `WscMvc.HomeController` | `{87488446-60BE-4068-8368-0B709BB68F3F}` |
| `test-app/Controllers/SelfTestController.wsc` | `WscMvc.SelfTestController` | `{D2634944-4646-4C55-956E-4C05E7E10904}` |

CLSIDs are fixed at creation and must never be recycled for a different component (AGENTS.md). `Application`'s public `Run` signature changed between M1 and M2 (added a leading `ctx` parameter) under the same CLSID; acceptable because this is active pre-release (v0.1) development with exactly one caller (`Default.asp`, updated in lockstep) — not a claim that live interface changes are safe for a published/external client.
CLSIDs are fixed at creation and must never be recycled for a different component (AGENTS.md). `Application`'s public `Run` signature changed during pre-release development (M2 added a leading `ctx` parameter; M3 added an `allowHeader` output parameter) under the same CLSID; acceptable because this is active pre-release (v0.1) development with exactly one caller shape (`Default.asp`, updated in lockstep) — not a claim that live interface changes are safe for a published/external client.

## IIS sites (test host: win2025test, 100.127.62.31)

@@ -83,4 +87,4 @@ Both are 64-bit, no managed code, anonymous auth identity `IUSR`. Each site's `p

## Deferred to later milestones (do not implement early)

Per SPEC §3 non-goals and IMPLEMENTATION_PLAN M3+: explicit route table, HTML views/templates, ADODB, auth. A more robust logging mechanism (if complete coverage under concurrent load is ever required) is deferred to M6 — see the concurrency finding in `docs/DECISIONS.md`.
Per SPEC §3 non-goals and IMPLEMENTATION_PLAN M4+: HTML views/templates, ADODB, auth. A more robust logging mechanism (if complete coverage under concurrent load is ever required) is deferred to M6 — see the concurrency finding in `docs/DECISIONS.md`.

+ 10
- 0
docs/DECISIONS.md Просмотреть файл

@@ -107,3 +107,13 @@ Follow-up direction from Daniel right after the previous entry: "Test app should
**Fix**: moved `Controllers/SelfTestController.wsc` -> `test-app/Controllers/SelfTestController.wsc` and updated registration tooling to reference the app-owned path. Then closed a subtler boundary bypass: removing rewrite rules alone was insufficient because callers can address `Default.asp?route=...` directly. Each app's bootstrap now passes a fixed application name into shared `Application.Run`; the route allowlist matches both application and path. Production therefore cannot activate `/self-test`, and the test app cannot activate `/hello`, even through direct `Default.asp` requests. `SelfTestController` no longer invokes production's `/hello` route or `HomeController`; it verifies that the `tests` route set rejects `/hello`.

COM registration for a `.wsc` records the exact file path in the registry (`HKLM:\SOFTWARE\Classes\CLSID\{guid}\ScriptletURL`), so moving the file requires re-registration. `tools/Register-Components.ps1` and `tools/Unregister-Components.ps1` now point to `test-app/Controllers/SelfTestController.wsc`; verification includes checking that registry value and exercising both HTTP boundaries.

## M3 — Explicit route table and method handling (2026-09-19)

Added `Framework/Router.wsc` (`WscMvc.Router`) as the explicit route allowlist. It receives the per-request `RequestContext` and fixed application selector (`production` or `tests`), then returns either a hardcoded handler key or a completed expected response. `Application.wsc` dispatches only through explicit `Select Case` branches for those handler keys; no URL text is ever used as a ProgID or method name.

Route path validation is intentionally conservative for M3. ASP/IIS has already decoded the `route` query parameter once, so the router does not decode it again. Any remaining `%` escape, backslash, query/fragment marker, doubled slash, dot-segment, or control character is rejected with `400 Bad Request`; otherwise the route is lowercased and one trailing slash is trimmed for literal matching. The original path remains preserved in `RequestContext.Path` and logs.

The response contract gained an `allowHeader` out parameter from `Application.Run`. `Default.asp` is still the only layer that touches ASP `Response`; it emits the `Allow` header only when the framework returns one. This keeps routing/framework code ASP-intrinsic-free while still allowing correct HTTP behavior for app-routed `405 Method Not Allowed` responses.

IIS/Classic ASP on the local Windows 11 test host intercepted `PUT`/`DELETE` requests before they reached the application, returning IIS's own `Allow: GET, HEAD, OPTIONS, TRACE`. Therefore HTTP integration tests assert framework 405 behavior using `POST /hello`, which Classic ASP does deliver to the app and which returns the framework's `Allow: GET`. The full Router/Application unsupported-method contract, including `DELETE /self-test -> Allow: GET, POST`, is covered by WSH component tests and the self-test controller's direct `Router.Match` checks. This matches the M3 scope: support GET and POST initially; distinguish unsupported methods where the request reaches the framework.

+ 77
- 0
docs/TEST-RESULTS.md Просмотреть файл

@@ -277,3 +277,80 @@ 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.

## M3 — Explicit route table, 400/404/405, GET/POST dispatch (2026-09-19)

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.

+ 6
- 2
public/Default.asp Просмотреть файл

@@ -1,7 +1,7 @@
<%@ Language="VBScript" %>
<% Option Explicit %>
<%
Dim route, httpMethod, logDir, ctx, app, applicationName, statusLine, contentType, body
Dim route, httpMethod, logDir, ctx, app, applicationName, statusLine, contentType, body, allowHeader

route = Request.QueryString("route")
httpMethod = Request.ServerVariables("REQUEST_METHOD")
@@ -54,9 +54,10 @@ On Error Goto 0
statusLine = ""
contentType = ""
body = ""
allowHeader = ""

On Error Resume Next
app.Run ctx, applicationName, statusLine, contentType, body
app.Run ctx, applicationName, statusLine, contentType, body, allowHeader
If Err.Number <> 0 Then
Err.Clear
On Error Goto 0
@@ -71,6 +72,9 @@ On Error Goto 0

Response.Status = statusLine
Response.ContentType = contentType
If Len(allowHeader) > 0 Then
Response.AddHeader "Allow", allowHeader
End If
Response.Write body

Set app = Nothing


+ 53
- 3
test-app/Controllers/SelfTestController.wsc Просмотреть файл

@@ -79,15 +79,15 @@ Sub RunSelfTest(logDir, body)

' --- Shared Application must isolate route sets. The test app must not
' activate or test production's HomeController. ---
Dim app, ctxUnknown, unkStatus, unkType, unkBody, unkOk, unkDetail
Dim app, ctxUnknown, unkStatus, unkType, unkBody, unkAllow, unkOk, unkDetail
unkOk = False
unkDetail = ""
On Error Resume Next
Set app = CreateObject("WscMvc.Application")
Set ctxUnknown = CreateObject("WscMvc.RequestContext")
ctxUnknown.Initialize "/hello", "GET", logDir
unkStatus = "" : unkType = "" : unkBody = ""
app.Run ctxUnknown, "tests", unkStatus, unkType, unkBody
unkStatus = "" : unkType = "" : unkBody = "" : unkAllow = ""
app.Run ctxUnknown, "tests", unkStatus, unkType, unkBody, unkAllow
If Err.Number <> 0 Then
unkDetail = Err.Description
Err.Clear
@@ -100,9 +100,59 @@ Sub RunSelfTest(logDir, body)
checks = AppendCheck(checks, "test_app_rejects_production_route", unkOk, unkDetail)
allPass = allPass And unkOk

' --- Method dispatch contract: test app self-test route supports both
' GET and POST, and reports an Allow header for unsupported methods. ---
Dim router, ctxPost, postStatus, postType, postBody, postAllow, postHandler, postOk, postDetail
postOk = False
postDetail = ""
On Error Resume Next
Set router = CreateObject("WscMvc.Router")
Set ctxPost = CreateObject("WscMvc.RequestContext")
ctxPost.Initialize "/self-test", "POST", logDir
postStatus = "" : postType = "" : postBody = "" : postAllow = "" : postHandler = ""
router.Match ctxPost, "tests", postStatus, postType, postBody, postAllow, postHandler
If Err.Number <> 0 Then
postDetail = Err.Description
Err.Clear
ElseIf postHandler <> "SelfTest.RunSelfTest" Then
postDetail = "Expected SelfTest.RunSelfTest handler, got [" & postHandler & "]"
ElseIf postStatus <> "" Then
postDetail = "Expected empty status for matched route, got [" & postStatus & "]"
Else
postOk = True
End If
On Error Goto 0
checks = AppendCheck(checks, "post_self_test_route", postOk, postDetail)
allPass = allPass And postOk

Dim ctxDelete, delStatus, delType, delBody, delAllow, delHandler, delOk, delDetail
delOk = False
delDetail = ""
On Error Resume Next
Set ctxDelete = CreateObject("WscMvc.RequestContext")
ctxDelete.Initialize "/self-test", "DELETE", logDir
delStatus = "" : delType = "" : delBody = "" : delAllow = "" : delHandler = ""
router.Match ctxDelete, "tests", delStatus, delType, delBody, delAllow, delHandler
If Err.Number <> 0 Then
delDetail = Err.Description
Err.Clear
ElseIf delStatus <> "405 Method Not Allowed" Then
delDetail = "Expected 405 Method Not Allowed, got [" & delStatus & "]"
ElseIf delAllow <> "GET, POST" Then
delDetail = "Expected Allow [GET, POST], got [" & delAllow & "]"
Else
delOk = True
End If
On Error Goto 0
checks = AppendCheck(checks, "delete_self_test_allow_header", delOk, delDetail)
allPass = allPass And delOk

Set ctx1 = Nothing
Set ctx2 = Nothing
Set ctxUnknown = Nothing
Set ctxPost = Nothing
Set ctxDelete = Nothing
Set router = Nothing
Set app = Nothing

body = "{""ok"":" & LCase(CStr(allPass)) & ",""checks"":[" & checks & "]}"


+ 6
- 2
test-app/public/Default.asp Просмотреть файл

@@ -5,7 +5,7 @@
' set in the shared framework. Production's bootstrap selects "production".
' Keeping this value inside each app prevents direct Default.asp?route=...
' requests from crossing the application boundary.
Dim route, httpMethod, logDir, ctx, app, applicationName, statusLine, contentType, body
Dim route, httpMethod, logDir, ctx, app, applicationName, statusLine, contentType, body, allowHeader

route = Request.QueryString("route")
httpMethod = Request.ServerVariables("REQUEST_METHOD")
@@ -58,9 +58,10 @@ On Error Goto 0
statusLine = ""
contentType = ""
body = ""
allowHeader = ""

On Error Resume Next
app.Run ctx, applicationName, statusLine, contentType, body
app.Run ctx, applicationName, statusLine, contentType, body, allowHeader
If Err.Number <> 0 Then
Err.Clear
On Error Goto 0
@@ -75,6 +76,9 @@ On Error Goto 0

Response.Status = statusLine
Response.ContentType = contentType
If Len(allowHeader) > 0 Then
Response.AddHeader "Allow", allowHeader
End If
Response.Write body

Set app = Nothing


+ 69
- 5
tests/Test-Components.vbs Просмотреть файл

@@ -16,6 +16,14 @@ Function NewContext(path, httpMethod)
Set NewContext = ctx
End Function

Sub RunApplication(app, path, httpMethod, applicationName, statusLine, contentType, body, allowHeader)
Dim ctx
Set ctx = NewContext(path, httpMethod)
statusLine = "" : contentType = "" : body = "" : allowHeader = ""
app.Run ctx, applicationName, statusLine, contentType, body, allowHeader
Set ctx = Nothing
End Sub

Sub CheckEqual(actual, expected, label)
If actual = expected Then
WScript.Echo "PASS: " & label
@@ -66,7 +74,7 @@ If pass Then
End If

' --- Application.Run happy path via ctx ---
Dim app, statusLine, contentType, body
Dim app, statusLine, contentType, body, allowHeader
If pass Then
On Error Resume Next
Set app = CreateObject("WscMvc.Application")
@@ -79,9 +87,8 @@ If pass Then
End If

If pass Then
statusLine = "" : contentType = "" : body = ""
On Error Resume Next
app.Run ctx1, "production", statusLine, contentType, body
RunApplication app, "/hello", "GET", "production", statusLine, contentType, body, allowHeader
If Err.Number <> 0 Then
WScript.Echo "FAIL: Application.Run raised error on /hello - " & Err.Description
pass = False
@@ -94,13 +101,13 @@ If pass Then
CheckEqual statusLine, "200 OK", "Application.Run(/hello) statusLine"
CheckEqual contentType, "text/html; charset=utf-8", "Application.Run(/hello) contentType"
CheckEqual body, "Hello from WSC-MVC!", "Application.Run(/hello) body"
CheckEqual allowHeader, "", "Application.Run(/hello) Allow header"
End If

' --- Application.Run unknown route via ctx (expected 404, not an error) ---
If pass Then
statusLine = "" : contentType = "" : body = ""
On Error Resume Next
app.Run ctx2, "production", statusLine, contentType, body
RunApplication app, "/does-not-exist", "GET", "production", statusLine, contentType, body, allowHeader
If Err.Number <> 0 Then
WScript.Echo "FAIL: Application.Run raised error on unknown route - " & Err.Description
pass = False
@@ -111,6 +118,63 @@ End If

If pass Then
CheckEqual statusLine, "404 Not Found", "Application.Run(unknown route) statusLine"
CheckEqual allowHeader, "", "Application.Run(unknown route) Allow header"
End If

' --- Router method contract: existing route with unsupported method is 405 + Allow ---
If pass Then
On Error Resume Next
RunApplication app, "/hello", "POST", "production", statusLine, contentType, body, allowHeader
If Err.Number <> 0 Then
WScript.Echo "FAIL: Application.Run raised error on method mismatch - " & Err.Description
pass = False
Err.Clear
End If
On Error Goto 0
End If

If pass Then
CheckEqual statusLine, "405 Method Not Allowed", "Application.Run(POST /hello) statusLine"
CheckEqual contentType, "text/plain; charset=utf-8", "Application.Run(POST /hello) contentType"
CheckEqual body, "Method Not Allowed", "Application.Run(POST /hello) body"
CheckEqual allowHeader, "GET", "Application.Run(POST /hello) Allow header"
End If

' --- Router app isolation and POST support for test app route ---
If pass Then
On Error Resume Next
RunApplication app, "/self-test", "POST", "tests", statusLine, contentType, body, allowHeader
If Err.Number <> 0 Then
WScript.Echo "FAIL: Application.Run raised error on POST /self-test - " & Err.Description
pass = False
Err.Clear
End If
On Error Goto 0
End If

If pass Then
CheckEqual statusLine, "200 OK", "Application.Run(POST /self-test tests) statusLine"
CheckEqual contentType, "application/json; charset=utf-8", "Application.Run(POST /self-test tests) contentType"
CheckEqual allowHeader, "", "Application.Run(POST /self-test tests) Allow header"
End If

' --- Router malformed path contract ---
If pass Then
On Error Resume Next
RunApplication app, "/hello/../secret", "GET", "production", statusLine, contentType, body, allowHeader
If Err.Number <> 0 Then
WScript.Echo "FAIL: Application.Run raised error on malformed path - " & Err.Description
pass = False
Err.Clear
End If
On Error Goto 0
End If

If pass Then
CheckEqual statusLine, "400 Bad Request", "Application.Run(malformed path) statusLine"
CheckEqual contentType, "text/plain; charset=utf-8", "Application.Run(malformed path) contentType"
CheckEqual body, "Bad Request", "Application.Run(malformed path) body"
CheckEqual allowHeader, "", "Application.Run(malformed path) Allow header"
End If

' --- Logging: best-effort log file was written with both outcomes ---


+ 40
- 0
tests/Test-Http.ps1 Просмотреть файл

@@ -26,6 +26,37 @@ try {
Report $false "GET /hello request" $_.Exception.Message
}

try {
$postHelloResp = Invoke-WebRequest -Uri "$BaseUrl/hello" -Method Post -UseBasicParsing
Report $false "POST /hello returns 405" "got $($postHelloResp.StatusCode)"
} catch [System.Net.WebException] {
$webResp = $_.Exception.Response
if ($webResp) {
$code = [int]$webResp.StatusCode
Report ($code -eq 405) "POST /hello returns 405" "got $code"
Report ($webResp.Headers['Allow'] -eq 'GET') "POST /hello Allow header" "got '$($webResp.Headers['Allow'])'"
} else {
Report $false "POST /hello returns 405" $_.Exception.Message
}
} catch {
Report $false "POST /hello returns 405" $_.Exception.Message
}

try {
$badPathResp = Invoke-WebRequest -Uri "$BaseUrl/Default.asp?route=/hello/../secret" -UseBasicParsing
Report $false "malformed route returns 400" "got $($badPathResp.StatusCode)"
} catch [System.Net.WebException] {
$webResp = $_.Exception.Response
if ($webResp) {
$code = [int]$webResp.StatusCode
Report ($code -eq 400) "malformed route returns 400" "got $code"
} else {
Report $false "malformed route returns 400" $_.Exception.Message
}
} catch {
Report $false "malformed route returns 400" $_.Exception.Message
}

try {
$rootResp = Invoke-WebRequest -Uri "$BaseUrl/" -UseBasicParsing
Report ($rootResp.StatusCode -eq 200) "GET / status 200" "got $($rootResp.StatusCode)"
@@ -86,6 +117,15 @@ if ($TestBaseUrl) {
} catch {
Report $false "test app cannot cross into production routes" $_.Exception.Message
}

try {
$postSelfTestResp = Invoke-WebRequest -Uri "$TestBaseUrl/self-test" -Method Post -UseBasicParsing
Report ($postSelfTestResp.StatusCode -eq 200) "POST /self-test on test app status 200" "got $($postSelfTestResp.StatusCode)"
Report ($postSelfTestResp.Headers['Content-Type'] -eq 'application/json; charset=utf-8') "POST /self-test content-type" "got $($postSelfTestResp.Headers['Content-Type'])"
} catch {
Report $false "POST /self-test on test app" $_.Exception.Message
}

}

# Framework/ is a sibling of the "public" webroot, not a rule-denied


+ 2
- 2
tests/run-self-test.sh Просмотреть файл

@@ -3,10 +3,10 @@
# no cscript, no SSH access to the VM required. Runnable from any CLI that
# can reach the site over HTTP.
#
# Usage: ./run-self-test.sh http://100.127.62.31:8090
# Usage: ./run-self-test.sh http://100.127.62.31:8091
set -euo pipefail

BASE_URL="${1:?Usage: run-self-test.sh <base-url>, e.g. http://100.127.62.31:8090}"
BASE_URL="${1:?Usage: run-self-test.sh <base-url>, e.g. http://100.127.62.31:8091}"

response=$(curl -sf "$BASE_URL/self-test")



+ 1
- 0
tools/Register-Components.ps1 Просмотреть файл

@@ -13,6 +13,7 @@ $components = @(
# is test-app-specific and lives under test-app/ instead. See
# docs/ARCHITECTURE.md.
(Join-Path $ProjectRoot 'Framework\RequestContext.wsc'),
(Join-Path $ProjectRoot 'Framework\Router.wsc'),
(Join-Path $ProjectRoot 'Framework\Application.wsc'),
(Join-Path $ProjectRoot 'Controllers\HomeController.wsc'),
(Join-Path $ProjectRoot 'test-app\Controllers\SelfTestController.wsc')


+ 1
- 0
tools/Unregister-Components.ps1 Просмотреть файл

@@ -18,6 +18,7 @@ $components = @(
@{ Path = (Join-Path $ProjectRoot 'test-app\Controllers\SelfTestController.wsc'); ProgId = 'WscMvc.SelfTestController'; ClassId = '{D2634944-4646-4C55-956E-4C05E7E10904}' },
@{ Path = (Join-Path $ProjectRoot 'Controllers\HomeController.wsc'); ProgId = 'WscMvc.HomeController'; ClassId = '{87488446-60BE-4068-8368-0B709BB68F3F}' },
@{ Path = (Join-Path $ProjectRoot 'Framework\Application.wsc'); ProgId = 'WscMvc.Application'; ClassId = '{851C7763-1638-42FE-A166-BF3DD3A96A88}' },
@{ Path = (Join-Path $ProjectRoot 'Framework\Router.wsc'); ProgId = 'WscMvc.Router'; ClassId = '{C92F9338-B478-4EAD-B865-892FFB1E1C51}' },
@{ Path = (Join-Path $ProjectRoot 'Framework\RequestContext.wsc'); ProgId = 'WscMvc.RequestContext'; ClassId = '{1C36FA55-34DF-4974-94B9-D657389362B2}' }
)



Загрузка…
Отмена
Сохранить

Powered by TurnKey Linux.