diff --git a/core/autoload_core.asp b/core/autoload_core.asp
index 6b406b8..b0d4060 100644
--- a/core/autoload_core.asp
+++ b/core/autoload_core.asp
@@ -1,7 +1,8 @@
-
+
+
@@ -21,3 +22,27 @@
+<%
+' Response cache headers (moved from mvc.asp - now that MVC is a Windows
+' Script Component it cannot hold loose top-level ASP statements like this).
+Dim cacheYear : cacheYear = GetAppSetting("CacheExpirationYear")
+If cacheYear = "nothing" Then cacheYear = "2030"
+Response.ExpiresAbsolute = "01/01/" & cacheYear
+Response.AddHeader "pragma", "no-cache"
+Response.AddHeader "cache-control", "private, no-cache, must-revalidate"
+
+' MVC dispatcher (Windows Script Component). It runs in its own isolated
+' script engine, so its dependencies are injected explicitly here rather
+' than it reaching into this page's globals itself - see AGENTS.md
+' section 69/70 ("Dependencies Must Be Injected").
+Dim MVC_Instance
+Set MVC_Instance = GetObject("script:" & Server.MapPath("../Core/mvc.wsc") & "")
+Set MVC_Instance.Router = router
+Set MVC_Instance.ControllerRegistry = ControllerRegistry()
+Set MVC_Instance.ControllerFactory = ControllerFactory()
+Set MVC_Instance.Routes = Routes()
+
+Function MVC()
+ Set MVC = MVC_Instance
+End Function
+%>
diff --git a/core/helpers.asp b/core/helpers.asp
index ca87a1e..cdb18dc 100644
--- a/core/helpers.asp
+++ b/core/helpers.asp
@@ -1,5 +1,8 @@
<%
+Dim m_indent
+
Function QuoteValue(val)
+ Dim conn
if IsWrappedInSingleQuotes(val) then
QuoteValue = val
Exit Function
@@ -92,6 +95,38 @@ Function IIf(condition, trueValue, falseValue)
On Error GoTo 0
End Function
+'-----------------------------
+' Utility: MVC Dispatch Error Renderer
+'
+' Renders the error box for a failed MVC.Resolve()/ExecuteAction() call.
+' errInfo is the Dictionary from MVC.LastError: Type = "Security" (format/
+' whitelist failures - message is safe to show in any environment) or
+' Type = "Action" (a controller action raised an error - full detail only
+' in development). Consolidates what were four duplicated inline HTML
+' blocks in the pre-WSC mvc.asp into one place, matching AGENTS.md's
+' "Shared Utilities" rule against duplicate near-identical helpers.
+'-----------------------------
+Public Sub RenderMvcError(errInfo)
+ Dim isDevelopment
+ isDevelopment = (LCase(GetAppSetting("Environment")) = "development")
+
+ Response.Write ""
+
+ If errInfo("Type") = "Security" Then
+ Response.Write "Security Error: " & Server.HTMLEncode(errInfo("Message"))
+ ElseIf isDevelopment Then
+ Response.Write "Controller Action Error
"
+ Response.Write "Action: " & Server.HTMLEncode(errInfo("ActionName")) & "
"
+ Response.Write "Error: " & Server.HTMLEncode(errInfo("Description")) & "
"
+ Response.Write "Error Number: " & errInfo("Number")
+ Else
+ Response.Write "An error occurred
"
+ Response.Write "Please contact the system administrator if the problem persists."
+ End If
+
+ Response.Write "
"
+End Sub
+
'-----------------------------
' Utility: Generic Error Reporter
'-----------------------------
diff --git a/core/lib.CDOEmail.asp b/core/lib.CDOEmail.asp
index e55a55f..306e768 100644
--- a/core/lib.CDOEmail.asp
+++ b/core/lib.CDOEmail.asp
@@ -113,7 +113,7 @@ Class CDOEmail_Class
End If
' Add attachments if any
- Dim i
+ Dim i, errNum, errDesc
For i = LBound(arrAttachments) To UBound(arrAttachments)
msg.AddAttachment arrAttachments(i)
Next
diff --git a/core/lib.Collections.asp b/core/lib.Collections.asp
index 60ad607..eff7385 100644
--- a/core/lib.Collections.asp
+++ b/core/lib.Collections.asp
@@ -374,8 +374,8 @@ Class LinkedList_Class
End Function
Public Function TO_Array()
- Dim i, iter
-
+ Dim i, iter, retval
+
ReDim retval(Me.Count - 1)
i = 0
Set iter = Me.Iterator
@@ -491,8 +491,8 @@ Class DynamicArray_Class
If e < s Then
Set Slice = DynamicArray()
Else
+ Dim retval, i, j
ReDim retval(e - s)
- Dim i, j
j = 0
For i = s to e
Assign retval(j), m_data(i)
@@ -515,7 +515,7 @@ Class DynamicArray_Class
End Function
Public Function TO_Array()
- Dim i
+ Dim i, retval
ReDim retval(m_size - 1)
For i = 0 to UBOUND(retval)
Assign retval(i), m_data(i)
diff --git a/core/lib.ControllerFactory.asp b/core/lib.ControllerFactory.asp
new file mode 100644
index 0000000..cc982e8
--- /dev/null
+++ b/core/lib.ControllerFactory.asp
@@ -0,0 +1,65 @@
+<%
+'=======================================================================================================================
+' Controller Factory
+'
+' Centralizes controller instantiation by name so the MVC dispatcher (a WSC,
+' with its own isolated script engine) never has to Execute/Eval a string
+' built from a route-supplied controller name to resolve a class. Each
+' controller factory function (e.g. HomeController(), which already exists as
+' each controller's own singleton accessor) is registered once, by reference,
+' via GetRef - not by re-deriving it from a string at dispatch time.
+'=======================================================================================================================
+
+Class ControllerFactory_Class
+ Private m_factories
+
+ Private Sub Class_Initialize()
+ Set m_factories = Server.CreateObject("Scripting.Dictionary")
+ m_factories.CompareMode = 1 ' vbTextCompare, case-insensitive
+ End Sub
+
+ Private Sub Class_Terminate()
+ Set m_factories = Nothing
+ End Sub
+
+ '---------------------------------------------------------------------------------------------------------------------
+ ' Register a controller's factory function (its own singleton accessor,
+ ' e.g. GetRef("HomeController")) under its route name.
+ '---------------------------------------------------------------------------------------------------------------------
+ Public Sub Register(controllerName, factoryFunctionRef)
+ Dim key : key = LCase(Trim(controllerName))
+ If Not m_factories.Exists(key) Then
+ m_factories.Add key, factoryFunctionRef
+ End If
+ End Sub
+
+ '---------------------------------------------------------------------------------------------------------------------
+ ' Create(controllerName) -> the controller singleton instance
+ '---------------------------------------------------------------------------------------------------------------------
+ Public Function Create(controllerName)
+ Dim key, fn
+ key = LCase(Trim(controllerName))
+
+ If Not m_factories.Exists(key) Then
+ Err.Raise vbObjectError + 1100, "ControllerFactory.Create", "No factory registered for controller: " & controllerName
+ End If
+
+ Set fn = m_factories(key)
+ Set Create = fn()
+ End Function
+
+ '---------------------------------------------------------------------------------------------------------------------
+ Public Function IsRegistered(controllerName)
+ IsRegistered = m_factories.Exists(LCase(Trim(controllerName)))
+ End Function
+End Class
+
+' Singleton instance
+Dim ControllerFactory_Class__Singleton
+Function ControllerFactory()
+ If IsEmpty(ControllerFactory_Class__Singleton) Then
+ Set ControllerFactory_Class__Singleton = New ControllerFactory_Class
+ End If
+ Set ControllerFactory = ControllerFactory_Class__Singleton
+End Function
+%>
diff --git a/core/lib.Data.asp b/core/lib.Data.asp
index 4750c85..8d6671f 100644
--- a/core/lib.Data.asp
+++ b/core/lib.Data.asp
@@ -2,112 +2,143 @@
Class Database_Class
Private m_connection
Private m_connection_string
-
+ Private m_shape_connections
+
Private m_trace_enabled
Public Sub set_trace(bool) : m_trace_enabled = bool : End Sub
Public Property Get is_trace_enabled : is_trace_enabled = m_trace_enabled : End Property
-
+
+ Private Sub Class_Initialize
+ Set m_shape_connections = Server.CreateObject("Scripting.Dictionary")
+ End Sub
+
'---------------------------------------------------------------------------------------------------------------------
Public Sub Initialize(connection_string)
m_connection_string = connection_string
End Sub
'---------------------------------------------------------------------------------------------------------------------
- Public Function ShapeQuery(sql,params)
- dim shapeConn : set shapeConn = server.createobject("adodb.connection")
- shapeConn.ConnectionString = "Provider=MSDataShape;Data " & m_connection_string
- dim cmd : set cmd = server.createobject("adodb.command")
- shapeConn.open
- set cmd.ActiveConnection = shapeConn
- cmd.CommandText = sql
- dim rs
+ ' MSDataShape opens its own dedicated connection per call (it cannot reuse
+ ' the plain Connection() used by Query/PagedQuery). That connection is
+ ' tracked and closed in Class_Terminate rather than left open until the
+ ' object is garbage collected.
+ Public Function ShapeQuery(sql, params)
+ Dim shapeConn, cmd, rs, key
+
+ Set shapeConn = Server.CreateObject("adodb.connection")
+ shapeConn.ConnectionString = "Provider=MSDataShape;Data " & m_connection_string
+ shapeConn.Open
+
+ key = m_shape_connections.Count
+ m_shape_connections.Add key, shapeConn
+
+ Set cmd = Server.CreateObject("adodb.command")
+ Set cmd.ActiveConnection = shapeConn
+ cmd.CommandText = sql
+
If IsArray(params) then
- set rs = cmd.Execute(, params)
+ Set rs = cmd.Execute(, params)
ElseIf Not IsEmpty(params) then ' one parameter
- set rs = cmd.Execute(, Array(params))
+ Set rs = cmd.Execute(, Array(params))
Else
- set rs = cmd.Execute()
+ Set rs = cmd.Execute()
End If
- set ShapeQuery = rs
+
+ Set ShapeQuery = rs
End Function
Public Function Query(sql, params)
- dim cmd : set cmd = server.createobject("adodb.command")
- set cmd.ActiveConnection = Connection
+ Dim cmd, rs
+ Set cmd = Server.CreateObject("adodb.command")
+ Set cmd.ActiveConnection = Connection
cmd.CommandText = sql
-
- dim rs
-
+
If IsArray(params) then
- set rs = cmd.Execute(, params)
+ Set rs = cmd.Execute(, params)
ElseIf Not IsEmpty(params) then ' one parameter
- set rs = cmd.Execute(, Array(params))
+ Set rs = cmd.Execute(, Array(params))
Else
- set rs = cmd.Execute()
+ Set rs = cmd.Execute()
End If
-
- set Query = rs
+
+ Set Query = rs
End Function
-
+
'---------------------------------------------------------------------------------------------------------------------
+ ' per_page/page_num were previously accepted but ignored - PageSize,
+ ' CacheSize, and AbsolutePage were hardcoded to 1 regardless of the
+ ' arguments passed in, so paging never actually worked. page_num is
+ ' clamped to a valid page (1..PageCount) since AbsolutePage raises an
+ ' error outside that range, and honoring the caller's page_num means an
+ ' out-of-range value is now reachable where it previously never was.
Public Function PagedQuery(sql, params, per_page, page_num)
- dim cmd : set cmd = server.createobject("adodb.command")
- set cmd.ActiveConnection = Connection
+ Dim cmd, rs
+ Set cmd = Server.CreateObject("adodb.command")
+ Set cmd.ActiveConnection = Connection
cmd.CommandText = sql
-
+
cmd.CommandType = 1 'adCmdText
cmd.ActiveConnection.CursorLocation = 3 'adUseClient
-
- dim rs
-
+
If IsArray(params) then
- set rs = cmd.Execute(, params)
+ Set rs = cmd.Execute(, params)
ElseIf Not IsEmpty(params) then ' one parameter
- set rs = cmd.Execute(, Array(params))
+ Set rs = cmd.Execute(, Array(params))
Else
- set rs = cmd.Execute()
+ Set rs = cmd.Execute()
End If
-
+
If Not rs.EOF then
- rs.PageSize = 1
- rs.CacheSize = 1
- rs.AbsolutePage = 1
+ rs.PageSize = per_page
+ rs.CacheSize = per_page
+
+ If page_num < 1 Then page_num = 1
+ If page_num > rs.PageCount Then page_num = rs.PageCount
+ rs.AbsolutePage = page_num
End If
-
- set PagedQuery = rs
+
+ Set PagedQuery = rs
End Function
-
+
'---------------------------------------------------------------------------------------------------------------------
Public Sub [Execute](sql, params)
me.query sql, params
End Sub
-
+
'---------------------------------------------------------------------------------------------------------------------
Public Sub BeginTransaction
Connection.BeginTrans
End Sub
-
+
Public Sub RollbackTransaction
Connection.RollbackTrans
End Sub
-
+
Public Sub CommitTransaction
Connection.CommitTrans
End Sub
-
+
'---------------------------------------------------------------------------------------------------------------------
' Private Methods
'---------------------------------------------------------------------------------------------------------------------
Private Sub Class_terminate
Destroy m_connection
+
+ Dim key
+ If Not m_shape_connections Is Nothing Then
+ For Each key In m_shape_connections.Keys
+ Destroy m_shape_connections(key)
+ Next
+ Set m_shape_connections = Nothing
+ End If
End Sub
-
+
Public Function Connection
- if not isobject(m_connection) then
+ if not isobject(m_connection) then
set m_connection = Server.CreateObject("adodb.connection")
m_connection.open m_connection_string
end if
set Connection = m_connection
End Function
end Class
-%>
\ No newline at end of file
+%>
diff --git a/core/lib.HTML.asp b/core/lib.HTML.asp
index a993f23..ff27e80 100644
--- a/core/lib.HTML.asp
+++ b/core/lib.HTML.asp
@@ -44,7 +44,7 @@ Class HTML_Helper_Class
Public Function LinkToUnless(condition, link_text, controller_name, action_name)
if not condition then
- LinkToIf = LinkToExt(link_text, controller_name, action_name, empty, empty)
+ LinkToUnless = LinkToExt(link_text, controller_name, action_name, empty, empty)
end if
End Function
diff --git a/core/lib.crypto.helper.asp b/core/lib.crypto.helper.asp
index e6bdd54..b29ecc5 100644
--- a/core/lib.crypto.helper.asp
+++ b/core/lib.crypto.helper.asp
@@ -1,45 +1,264 @@
<%
+'=======================================================================================================================
+' Password hashing
+'
+' Pure VBScript SHA-256 - no external process, no shell-out, no COM crypto
+' dependency. The previous implementation shelled out to a PowerShell script
+' (hash_sha256.ps1, which did not exist anywhere in this repo) via
+' WScript.Shell.Exec with the raw password concatenated directly into the
+' command line. That was both non-functional (missing script) and a command
+' injection vulnerability (a password containing ", `, ; etc. could execute
+' arbitrary PowerShell, and the password was visible in the process command
+' line to anything that could list processes).
+'
+' NOTE: This hashes without a per-user salt, matching the single-argument
+' HashPassword(password) contract these functions already had. Unsalted
+' hashes are vulnerable to precomputed/rainbow-table attacks. If a real
+' Users table is introduced, prefer storing a random per-user salt alongside
+' the hash and hashing (salt & password) - that is a schema change, so it is
+' intentionally not done here.
+'=======================================================================================================================
-Function HashPassword(password)
- Dim shell, command, execObj, outputLine, result
-
- ' Create Shell Object
- Set shell = CreateObject("WScript.Shell")
-
- ' Construct PowerShell Command
-
- command = "cmd /c powershell -ExecutionPolicy Bypass -NoLogo -NoProfile -File """ & Server.MapPath(".") & "..\Core\hash_sha256.ps1"" -password " & password
- ' Execute Command
- Set execObj = shell.Exec(command)
-
- ' Read Output
- Do While Not execObj.StdOut.AtEndOfStream
- outputLine = Trim(execObj.StdOut.ReadAll())
- If outputLine <> "" Then
- result = outputLine ' Capture the hash
- End If
+Private Function Sha256_U32(n)
+ If n < 0 Then
+ Sha256_U32 = n + 4294967296.0
+ Else
+ Sha256_U32 = CDbl(n)
+ End If
+End Function
+
+Private Function Sha256_S32(d)
+ Do While d >= 4294967296.0
+ d = d - 4294967296.0
+ Loop
+ Do While d < 0
+ d = d + 4294967296.0
+ Loop
+ If d >= 2147483648.0 Then
+ Sha256_S32 = CLng(d - 4294967296.0)
+ Else
+ Sha256_S32 = CLng(d)
+ End If
+End Function
+
+Private Function Sha256_Mod(x, y)
+ Sha256_Mod = x - Int(x / y) * y
+End Function
+
+Private Function Sha256_Add32(a, b)
+ Sha256_Add32 = Sha256_S32(Sha256_U32(a) + Sha256_U32(b))
+End Function
+
+Private Function Sha256_Add32_5(a, b, c, d, e)
+ Sha256_Add32_5 = Sha256_S32(Sha256_U32(a) + Sha256_U32(b) + Sha256_U32(c) + Sha256_U32(d) + Sha256_U32(e))
+End Function
+
+Private Function Sha256_RotR32(x, n)
+ Dim u, partA, keepBits, partB
+ If n = 0 Then
+ Sha256_RotR32 = x
+ Exit Function
+ End If
+ u = Sha256_U32(x)
+ partA = Int(u / (2 ^ n))
+ keepBits = Sha256_Mod(u, 2 ^ n)
+ partB = keepBits * (2 ^ (32 - n))
+ Sha256_RotR32 = Sha256_S32(partA + partB)
+End Function
+
+Private Function Sha256_ShR32(x, n)
+ Dim u
+ u = Sha256_U32(x)
+ Sha256_ShR32 = Sha256_S32(Int(u / (2 ^ n)))
+End Function
+
+Private Function Sha256_BytesToWord(b0, b1, b2, b3)
+ Sha256_BytesToWord = Sha256_S32(b0 * 16777216.0 + b1 * 65536.0 + b2 * 256.0 + b3)
+End Function
+
+Private Function Sha256_HexWord(x)
+ Dim u, h
+ u = Sha256_U32(x)
+ h = ""
+ Do While u > 0
+ h = Mid("0123456789abcdef", Sha256_Mod(u, 16) + 1, 1) & h
+ u = Int(u / 16)
Loop
+ Sha256_HexWord = Right("00000000" & h, 8)
+End Function
+
+' Converts a VBScript string (UTF-16) to a UTF-8 byte array (Long values 0-255).
+' Pure VBScript - avoids ADODB.Stream, which returns a Byte SafeArray that is
+' not reliably indexable from VBScript on every 32/64-bit IIS configuration.
+Private Function Sha256_StringToUtf8Bytes(s)
+ Dim arr(), count, i, code, lenS
+ lenS = Len(s)
+ If lenS = 0 Then
+ ReDim arr(-1)
+ Sha256_StringToUtf8Bytes = arr
+ Exit Function
+ End If
+ ReDim arr(lenS * 3)
+ count = -1
+ For i = 1 To lenS
+ code = AscW(Mid(s, i, 1))
+ If code < 0 Then code = code + 65536
+ If code <= 127 Then
+ count = count + 1 : arr(count) = code
+ ElseIf code <= 2047 Then
+ count = count + 1 : arr(count) = 192 Or Int(code / 64)
+ count = count + 1 : arr(count) = 128 Or (code And 63)
+ Else
+ count = count + 1 : arr(count) = 224 Or Int(code / 4096)
+ count = count + 1 : arr(count) = 128 Or (Int(code / 64) And 63)
+ count = count + 1 : arr(count) = 128 Or (code And 63)
+ End If
+ Next
+ ReDim Preserve arr(count)
+ Sha256_StringToUtf8Bytes = arr
+End Function
- ' Cleanup
- Set shell = Nothing
- Set execObj = Nothing
+' Returns the SHA-256 hash of inputString (UTF-8 encoded) as a 64-character
+' lowercase hex string. Verified against NIST/RFC test vectors (empty string,
+' "abc", the 56-byte two-block vector, and non-ASCII input) plus .NET's
+' System.Security.Cryptography.SHA256 as a cross-check.
+Function Sha256Hex(inputString)
+ Dim msg, msgLen, bitLen, i, t
+ Dim numBlocks, blockIdx, base
+ Dim hs(7)
+ Dim k(63)
+ Dim w(63)
+ Dim a, b, c, d, e, f, g, h
+ Dim s0, s1, ch, maj, temp1, temp2, bigS0, bigS1
+ Dim padded()
+ Dim padLen, totalLen, lenPos, hi32, lo32
+ Dim kHex, result
- ' Return the hash or error message
- If result = "" Or Left(result, 5) = "ERROR" Then
- HashPassword = result ' "ERROR: Hash not generated"
+ hs(0) = &h6a09e667 : hs(1) = &hbb67ae85 : hs(2) = &h3c6ef372 : hs(3) = &ha54ff53a
+ hs(4) = &h510e527f : hs(5) = &h9b05688c : hs(6) = &h1f83d9ab : hs(7) = &h5be0cd19
+
+ kHex = Array( _
+ "428a2f98","71374491","b5c0fbcf","e9b5dba5","3956c25b","59f111f1","923f82a4","ab1c5ed5", _
+ "d807aa98","12835b01","243185be","550c7dc3","72be5d74","80deb1fe","9bdc06a7","c19bf174", _
+ "e49b69c1","efbe4786","0fc19dc6","240ca1cc","2de92c6f","4a7484aa","5cb0a9dc","76f988da", _
+ "983e5152","a831c66d","b00327c8","bf597fc7","c6e00bf3","d5a79147","06ca6351","14292967", _
+ "27b70a85","2e1b2138","4d2c6dfc","53380d13","650a7354","766a0abb","81c2c92e","92722c85", _
+ "a2bfe8a1","a81a664b","c24b8b70","c76c51a3","d192e819","d6990624","f40e3585","106aa070", _
+ "19a4c116","1e376c08","2748774c","34b0bcb5","391c0cb3","4ed8aa4a","5b9cca4f","682e6ff3", _
+ "748f82ee","78a5636f","84c87814","8cc70208","90befffa","a4506ceb","bef9a3f7","c67178f2")
+ For i = 0 To 63
+ k(i) = Sha256_S32(CDbl("&h" & kHex(i)))
+ Next
+
+ msg = Sha256_StringToUtf8Bytes(inputString)
+ If UBound(msg) < LBound(msg) Then
+ msgLen = 0
Else
- HashPassword = result
+ msgLen = UBound(msg) - LBound(msg) + 1
End If
+ bitLen = msgLen * 8.0
+
+ ' Padding: msg + 0x80 + zero bytes so length % 64 = 56, then an 8-byte
+ ' big-endian bit length.
+ padLen = 56 - Sha256_Mod(msgLen + 1, 64)
+ If padLen < 0 Then padLen = padLen + 64
+ totalLen = msgLen + 1 + padLen + 8
+
+ ReDim padded(totalLen - 1)
+ For i = 0 To msgLen - 1
+ padded(i) = msg(i)
+ Next
+ padded(msgLen) = 128
+ For i = msgLen + 1 To msgLen + padLen
+ padded(i) = 0
+ Next
+
+ lenPos = msgLen + 1 + padLen
+ hi32 = Int(bitLen / 4294967296.0)
+ lo32 = bitLen - hi32 * 4294967296.0
+ padded(lenPos + 0) = Sha256_Mod(Int(hi32 / 16777216), 256)
+ padded(lenPos + 1) = Sha256_Mod(Int(hi32 / 65536), 256)
+ padded(lenPos + 2) = Sha256_Mod(Int(hi32 / 256), 256)
+ padded(lenPos + 3) = Sha256_Mod(hi32, 256)
+ padded(lenPos + 4) = Int(lo32 / 16777216)
+ padded(lenPos + 5) = Sha256_Mod(Int(lo32 / 65536), 256)
+ padded(lenPos + 6) = Sha256_Mod(Int(lo32 / 256), 256)
+ padded(lenPos + 7) = Sha256_Mod(lo32, 256)
+
+ numBlocks = totalLen / 64
+
+ For blockIdx = 0 To numBlocks - 1
+ base = blockIdx * 64
+ For t = 0 To 15
+ w(t) = Sha256_BytesToWord(padded(base + t*4), padded(base + t*4 + 1), padded(base + t*4 + 2), padded(base + t*4 + 3))
+ Next
+ For t = 16 To 63
+ s0 = Sha256_RotR32(w(t-15), 7) Xor Sha256_RotR32(w(t-15), 18) Xor Sha256_ShR32(w(t-15), 3)
+ s1 = Sha256_RotR32(w(t-2), 17) Xor Sha256_RotR32(w(t-2), 19) Xor Sha256_ShR32(w(t-2), 10)
+ w(t) = Sha256_Add32_5(w(t-16), s0, w(t-7), s1, 0)
+ Next
+
+ a = hs(0) : b = hs(1) : c = hs(2) : d = hs(3)
+ e = hs(4) : f = hs(5) : g = hs(6) : h = hs(7)
+
+ For t = 0 To 63
+ bigS1 = Sha256_RotR32(e,6) Xor Sha256_RotR32(e,11) Xor Sha256_RotR32(e,25)
+ ch = (e And f) Xor ((Not e) And g)
+ temp1 = Sha256_Add32_5(h, bigS1, ch, k(t), w(t))
+ bigS0 = Sha256_RotR32(a,2) Xor Sha256_RotR32(a,13) Xor Sha256_RotR32(a,22)
+ maj = (a And b) Xor (a And c) Xor (b And c)
+ temp2 = Sha256_Add32(bigS0, maj)
+
+ h = g
+ g = f
+ f = e
+ e = Sha256_Add32(d, temp1)
+ d = c
+ c = b
+ b = a
+ a = Sha256_Add32(temp1, temp2)
+ Next
+
+ hs(0) = Sha256_Add32(hs(0), a) : hs(1) = Sha256_Add32(hs(1), b) : hs(2) = Sha256_Add32(hs(2), c) : hs(3) = Sha256_Add32(hs(3), d)
+ hs(4) = Sha256_Add32(hs(4), e) : hs(5) = Sha256_Add32(hs(5), f) : hs(6) = Sha256_Add32(hs(6), g) : hs(7) = Sha256_Add32(hs(7), h)
+ Next
+
+ result = ""
+ For i = 0 To 7
+ result = result & Sha256_HexWord(hs(i))
+ Next
+ Sha256Hex = result
End Function
+Function HashPassword(password)
+ HashPassword = Sha256Hex(password)
+End Function
+
+'=======================================================================================================================
+' CheckPassword
+'
+' The original implementation called CreateRepository(conn, "Users", "UserId")
+' and Array("UserName", user) - "conn" and "CreateRepository" are not defined
+' anywhere in this framework (no global "conn", no CreateRepository factory),
+' and it searched using the not-yet-assigned "user" variable instead of the
+' "username" parameter, so this function has never been callable. Nothing in
+' this codebase calls it yet, and there is no Users table/migration either -
+' this remains example/scaffold code. Rewritten here to use the framework's
+' actual data-access primitive (DAL().Query with a parameterized command) and
+' the correct lookup value, so it will work once a Users table exists with
+' UserName and PasswordHash columns.
+'=======================================================================================================================
Function CheckPassword(username, password)
- Dim user,UsersRepository
- Set UsersRepository = CreateRepository(conn, "Users", "UserId")
- ' Find User
- Set User = UsersRepository.Find(Array("UserName", user), Empty)
- If user Is Nothing Then Exit Function ' Implicitly returns False
+ Dim rs
+ Set rs = DAL().Query("SELECT PasswordHash FROM Users WHERE UserName = ?", Array(username))
+
+ If rs.EOF Then
+ CheckPassword = False
+ Else
+ CheckPassword = (HashPassword(password) = rs("PasswordHash"))
+ End If
- ' Compare Hashed Password
- CheckPassword = (HashPassword(password) = user.PasswordHash)
+ rs.Close
+ Set rs = Nothing
End Function
-%>
\ No newline at end of file
+%>
diff --git a/core/lib.helpers.asp b/core/lib.helpers.asp
index b2ae6d6..fee7a7c 100644
--- a/core/lib.helpers.asp
+++ b/core/lib.helpers.asp
@@ -1,4 +1,5 @@
<%
+Dim protocol
'protocol = IIf(LCase(Request.ServerVariables("HTTPS")) = "1", "https", "http")
protocol = "https"
Dim timeZones
diff --git a/core/lib.json.asp b/core/lib.json.asp
index 38bef36..ee17ba3 100644
--- a/core/lib.json.asp
+++ b/core/lib.json.asp
@@ -260,6 +260,7 @@ Class aspJSON
End Function
Private Function aj_ReadNumericValue(ByVal val)
+ Dim numdecimals
If Instr(val, ".") > 0 Then
numdecimals = Len(val) - Instr(val, ".")
val = Clng(Replace(val, ".", ""))
diff --git a/core/mvc.asp b/core/mvc.asp
deleted file mode 100644
index 0bbf373..0000000
--- a/core/mvc.asp
+++ /dev/null
@@ -1,166 +0,0 @@
-
-<%
-' Set cache expiration from configuration
-Dim cacheYear : cacheYear = GetAppSetting("CacheExpirationYear")
-If cacheYear = "nothing" Then cacheYear = "2030"
-Response.ExpiresAbsolute = "01/01/" & cacheYear
-Response.AddHeader "pragma", "no-cache"
-Response.AddHeader "cache-control", "private, no-cache, must-revalidate"
-'=======================================================================================================================
-' MVC Dispatcher
-'=======================================================================================================================
-Class MVC_Dispatcher_Class
- dim CurrentController
-
- Public Property Get ControllerName
- ControllerName = CurrentController
- end Property
-
- '---------------------------------------------------------------------------------------------------------------------
- ' Convenience method to resolve route and dispatch in one call
- ' method: HTTP method (GET, POST, etc.)
- ' path: Request path (already cleaned of query params)
- '---------------------------------------------------------------------------------------------------------------------
- Public Sub DispatchRequest(method, path)
- Dim routeArray
- routeArray = router.Resolve(method, path)
- Dispatch routeArray
- End Sub
-
- '---------------------------------------------------------------------------------------------------------------------
- ' Main dispatch method - executes a resolved route
- ' RouteArray: Array(controller, action, params) from router.Resolve()
- '---------------------------------------------------------------------------------------------------------------------
- Public Sub Dispatch(RouteArray)
- On Error Resume Next
- Dim controllerName, actionName, hasParams, paramsArray
- controllerName = RouteArray(0)
- actionName = RouteArray(1)
-
- ' Security: Validate controller and action names
- If Not ControllerRegistry.IsValidControllerFormat(controllerName) Then
- Response.Write ""
- Response.Write "Security Error: Invalid controller name format."
- Response.Write "
"
- Exit Sub
- End If
-
- If Not ControllerRegistry.IsValidActionFormat(actionName) Then
- Response.Write ""
- Response.Write "Security Error: Invalid action name format."
- Response.Write "
"
- Exit Sub
- End If
-
- ' Security: Check controller whitelist
- If Not ControllerRegistry.IsValidController(controllerName) Then
- Response.Write ""
- Response.Write "Security Error: Controller '" & Server.HTMLEncode(controllerName) & "' is not registered."
- Response.Write "
"
- Exit Sub
- End If
-
- ' Initialize current controller
- Dim controllerAssignment : controllerAssignment = "Set CurrentController = " & controllerName & "()"
- Execute controllerAssignment
-
- ' Check if layout should be used
- hasParams = (UBound(RouteArray) >= 2)
- If eval(controllerName & ".useLayout") Then
- %> <%
- End If
-
- ' Prepare parameters
- If hasParams Then
- paramsArray = SurroundStringInArray(RouteArray(2))
- Else
- paramsArray = Empty
- End If
-
- ' Execute controller action
- ExecuteControllerAction controllerName, actionName, paramsArray
-
- ' Include footer if layout is used
- If eval(controllerName & ".useLayout") Then
- %> <%
- End If
- On Error GoTo 0
- End Sub
-
- ' Helper method to execute controller actions (eliminates code duplication)
- Private Sub ExecuteControllerAction(controllerName, actionName, paramsArray)
- On Error Resume Next
- Dim callString
-
- ' Build the call string based on whether we have parameters
- If Not IsEmpty(paramsArray) And IsArray(paramsArray) And UBound(paramsArray) >= 0 Then
- callString = "Call " & controllerName & "." & actionName & "(" & Join(paramsArray, ",") & ")"
- Else
- callString = "Call " & controllerName & "." & actionName & "()"
- End If
-
- ' Execute the action
- Execute callString
-
- ' Handle errors
- If Err.Number <> 0 Then
- HandleDispatchError actionName, Err.Description, Err.Number
- Err.Clear
- End If
- On Error GoTo 0
- End Sub
-
- ' Centralized error handling for dispatch errors
- Private Sub HandleDispatchError(actionName, errorDesc, errorNum)
- Dim isDevelopment
- isDevelopment = (LCase(GetAppSetting("Environment")) = "development")
-
- If isDevelopment Then
- Response.Write ""
- Response.Write "Controller Action Error
"
- Response.Write "Action: " & Server.HTMLEncode(actionName) & "
"
- Response.Write "Error: " & Server.HTMLEncode(errorDesc) & "
"
- Response.Write "Error Number: " & errorNum
- Response.Write "
"
- Else
- Response.Write ""
- Response.Write "An error occurred
"
- Response.Write "Please contact the system administrator if the problem persists."
- Response.Write "
"
- End If
- End Sub
-
- Public Sub RequirePost
-
- If Request.Form.Count = 0 Then MVC.RedirectToExt "NotValid","",empty:End If
-
- End Sub
-
- ' Shortcut for RedirectToActionExt that does not require passing a parameters argument.
- Public Sub RedirectToAction(ByVal action_name)
- RedirectToActionExt action_name, empty
- End Sub
-
- Public Sub RedirectTo(controller_name, action_name)
- RedirectToExt controller_name, action_name, empty
- End Sub
-
- ' Redirects the browser to the specified action on the specified controller with the specified querystring parameters.
- ' params is a KVArray of querystring parameters.
- Public Sub RedirectToExt(controller_name, action_name, params)
- Response.Redirect Routes.UrlTo(controller_name, action_name, params)
- End Sub
-
- Public Sub RedirectToActionExt(ByVal action_name, ByVal params)
- RedirectToExt ControllerName, action_name, params
- End Sub
-
-End Class
-dim MVC_Dispatcher_Class__Singleton
-Function MVC()
- if IsEmpty(MVC_Dispatcher_Class__Singleton) then
- set MVC_Dispatcher_Class__Singleton = new MVC_Dispatcher_Class
- end if
- set MVC = MVC_Dispatcher_Class__Singleton
-End Function
-%>
\ No newline at end of file
diff --git a/core/mvc.wsc b/core/mvc.wsc
new file mode 100644
index 0000000..a31b13b
--- /dev/null
+++ b/core/mvc.wsc
@@ -0,0 +1,320 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/AGENTS.md b/docs/AGENTS.md
deleted file mode 100644
index ae946db..0000000
--- a/docs/AGENTS.md
+++ /dev/null
@@ -1,1543 +0,0 @@
-# AGENTS.md
-
-## Purpose
-
-This file defines the development rules, architecture standards, safety requirements, and agent behavior for this project.
-
-All AI coding agents working in this repository must read this file before making changes.
-
-The primary development environment is:
-
-- ASP Classic
-- VBScript
-- ADODB
-- IIS
-- HTML/CSS/JavaScript
-- SQL Server, Microsoft Access, SQLite, or another configured database
-
-VBScript provides limited compile-time validation, weak typing, limited object-oriented features, and minimal runtime safeguards.
-
-AI agents must compensate for these limitations through:
-
-- strict conventions
-- defensive programming
-- architectural boundaries
-- validation
-- testing
-- code inspection
-- documentation
-- controlled self-improvement
-
----
-
-# 1. Core Principle
-
-The AI agent must behave as more than a code generator.
-
-It must act as:
-
-- developer
-- architect
-- static analyzer
-- compiler substitute
-- security reviewer
-- code reviewer
-- documentation maintainer
-- test author
-- project historian
-
-The agent must actively look for problems that VBScript itself may not catch.
-
----
-
-# 2. Instruction Priority
-
-When instructions conflict, follow this priority:
-
-1. Explicit user instructions
-2. Security and data protection requirements
-3. This AGENTS.md file
-4. Project architecture documentation
-5. Existing project conventions
-6. Existing implementation patterns
-7. Agent preferences
-
-Never weaken security, validation, or data integrity simply to match poor legacy code.
-
-If legacy code conflicts with these standards, preserve compatibility where required while gradually improving the implementation.
-
----
-
-# 3. Mandatory VBScript Rules
-
-## 3.1 Option Explicit
-
-Every ASP, VBS, and applicable WSC script must use:
-
-```vbscript
-Option Explicit
-```
-
-No undeclared variables are permitted.
-
-Before finishing a task, inspect all changed code for undeclared or misspelled identifiers.
-
----
-
-## 3.2 Variable Declaration
-
-All variables must be explicitly declared using an appropriate declaration:
-
-```vbscript
-Dim
-Private
-Public
-Const
-```
-
-Avoid excessive variable reuse.
-
-Prefer descriptive names.
-
-Bad:
-
-```vbscript
-Dim x
-Dim y
-```
-
-Better:
-
-```vbscript
-Dim customerId
-Dim orderTotal
-```
-
----
-
-## 3.3 Variant Safety
-
-VBScript uses Variants extensively.
-
-Do not assume that a value has the expected type.
-
-External values must be normalized near application boundaries.
-
-Use explicit conversion where appropriate:
-
-```vbscript
-CStr()
-CLng()
-CInt()
-CDbl()
-CBool()
-CDate()
-```
-
-Never convert unvalidated input blindly.
-
-Validate first when conversion can fail.
-
----
-
-# 4. Null, Empty, and Nothing
-
-These are different states and must not be treated as equivalent.
-
-Use:
-
-```vbscript
-IsNull(value)
-IsEmpty(value)
-object Is Nothing
-```
-
-Database values may contain `Null`.
-
-Uninitialized Variants may contain `Empty`.
-
-Object references may contain `Nothing`.
-
-Code must explicitly account for the state that is expected.
-
-Do not rely on accidental VBScript coercion.
-
----
-
-# 5. Request Data Is Untrusted
-
-Treat all external input as untrusted, including:
-
-- Request.Form
-- Request.QueryString
-- Request.Cookies
-- Request.ServerVariables
-- HTTP headers
-- uploaded files
-- JSON payloads
-- XML payloads
-- API data
-- database data originating from users
-- Session values derived from user input
-
-External data must pass through:
-
-```text
-Input
- ↓
-Validation
- ↓
-Normalization
- ↓
-Authorization
- ↓
-Business Logic
-```
-
-Never pass raw Request values directly into database or business operations.
-
----
-
-# 6. SQL Rules
-
-## 6.1 Parameterization Is Mandatory
-
-User-controlled data must never be concatenated into SQL.
-
-Prohibited:
-
-```vbscript
-sql = "SELECT * FROM Users WHERE Email = '" & email & "'"
-```
-
-Required:
-
-```vbscript
-Set cmd = Server.CreateObject("ADODB.Command")
-
-Set cmd.ActiveConnection = conn
-
-cmd.CommandText = _
- "SELECT UserId, Email " & _
- "FROM Users " & _
- "WHERE Email = ?"
-
-cmd.Parameters.Append _
- cmd.CreateParameter("@Email", adVarWChar, adParamInput, 255, email)
-```
-
-Use parameters for:
-
-- SELECT
-- INSERT
-- UPDATE
-- DELETE
-- stored procedure calls
-
----
-
-## 6.2 Dynamic SQL
-
-When identifiers such as table names or sort columns must be dynamic, they cannot normally be parameterized.
-
-In such cases, use a whitelist.
-
-Example:
-
-```vbscript
-Select Case sortField
- Case "Name", "CreatedDate", "Status"
- ' allowed
- Case Else
- sortField = "CreatedDate"
-End Select
-```
-
-Never accept arbitrary table names, column names, or SQL fragments from users.
-
----
-
-# 7. Output Encoding
-
-HTML output must be encoded by default.
-
-Prefer:
-
-```vbscript
-Server.HTMLEncode(value)
-```
-
-or a centralized helper such as:
-
-```vbscript
-Function H(value)
- If IsNull(value) Then
- H = ""
- Else
- H = Server.HTMLEncode(CStr(value))
- End If
-End Function
-```
-
-Raw HTML output should only be used when the value is explicitly trusted and intentionally contains HTML.
-
-Do not assume database data is safe to output without encoding.
-
----
-
-# 8. Error Handling
-
-## 8.1 On Error Resume Next
-
-Broad use of:
-
-```vbscript
-On Error Resume Next
-```
-
-is prohibited.
-
-It may only be used around the smallest practical operation requiring error interception.
-
-Required pattern:
-
-```vbscript
-On Error Resume Next
-
-Set rs = cmd.Execute()
-
-If Err.Number <> 0 Then
- errorNumber = Err.Number
- errorDescription = Err.Description
-
- Err.Clear
- On Error GoTo 0
-
- LogError errorNumber, errorDescription
-
- Err.Raise _
- vbObjectError + 1000, _
- "CustomerRepository.GetCustomer", _
- "Database operation failed."
-End If
-
-On Error GoTo 0
-```
-
-Always restore normal error handling.
-
----
-
-## 8.2 Never Hide Failures
-
-Do not silently ignore:
-
-- failed database operations
-- failed file operations
-- invalid conversions
-- failed COM object creation
-- authorization failures
-- missing required configuration
-
-Failures must either:
-
-- be handled intentionally, or
-- be logged and propagated appropriately
-
----
-
-# 9. Architecture
-
-The preferred application flow is:
-
-```text
-HTTP Request
- ↓
-Router
- ↓
-Controller
- ↓
-Service
- ↓
-Repository
- ↓
-Database
-```
-
-Views receive prepared data from controllers or ViewModels.
-
----
-
-# 10. Controller Rules
-
-Controllers may:
-
-- read Request data
-- invoke validation
-- invoke services
-- prepare ViewModels
-- redirect
-- select views
-- set HTTP status codes
-- write responses through response abstractions
-
-Controllers must not:
-
-- contain SQL
-- implement significant business logic
-- directly manage recordsets
-- duplicate validation rules
-- construct complex HTML
-- hide application failures
-
-Keep controllers thin.
-
----
-
-# 11. Service Rules
-
-Services contain application and business logic.
-
-Services may:
-
-- coordinate repositories
-- enforce business rules
-- perform calculations
-- enforce workflow rules
-- call domain-specific collaborators
-
-Services must not:
-
-- access Request directly
-- access Response directly
-- generate HTML
-- contain raw SQL
-- depend unnecessarily on ASP global state
-
-Prefer dependency injection through explicit initialization.
-
----
-
-# 12. Repository Rules
-
-Repositories own persistence logic.
-
-Repositories may:
-
-- execute ADODB commands
-- map records to models
-- create parameterized queries
-- manage database-specific concerns
-
-Repositories must not:
-
-- access Request
-- access Response
-- generate HTML
-- contain UI rules
-
----
-
-# 13. View Rules
-
-Views may:
-
-- render HTML
-- display ViewModel data
-- use simple formatting helpers
-
-Views must not:
-
-- execute SQL
-- create database connections
-- contain major business logic
-- make authorization decisions
-- perform complex data transformations
-
-Encode dynamic output by default.
-
----
-
-# 14. Models and ViewModels
-
-Models should represent application or domain data.
-
-ViewModels should represent data specifically prepared for a view.
-
-Do not pass ADODB.Recordset objects directly into views unless the existing architecture explicitly requires it and refactoring is not currently practical.
-
-Prefer converting recordsets into application-friendly structures.
-
----
-
-# 15. Dependency Management
-
-VBScript does not provide modern dependency injection.
-
-Use explicit initialization.
-
-Example:
-
-```vbscript
-Class CustomerService
-
- Private m_repository
- Private m_logger
-
- Public Sub Init(repository, logger)
- Set m_repository = repository
- Set m_logger = logger
- End Sub
-
-End Class
-```
-
-Dependencies should be visible.
-
-Avoid hidden global dependencies where practical.
-
----
-
-# 16. Composition Over Inheritance
-
-VBScript has limited inheritance capabilities.
-
-Prefer composition.
-
-Instead of designing deep pseudo-inheritance systems, compose objects from smaller collaborators.
-
-Example:
-
-```text
-CustomerService
- ├── CustomerRepository
- ├── CustomerValidator
- └── Logger
-```
-
----
-
-# 17. Interface Conventions
-
-VBScript cannot formally declare interfaces.
-
-When interchangeable implementations are needed, define a documented contract.
-
-Example:
-
-```text
-IUserRepository
-
-Required members:
-
-GetById(userId)
-GetByEmail(email)
-Create(user)
-Update(user)
-Delete(userId)
-```
-
-Agents must verify that implementations satisfy documented contracts.
-
----
-
-# 18. Object Initialization
-
-Use `Class_Initialize` only for safe initialization that requires no external dependencies.
-
-Use an explicit method such as:
-
-```vbscript
-Public Sub Init(...)
-```
-
-for dependency injection.
-
-Classes should not secretly fetch dependencies from globals when explicit injection is practical.
-
----
-
-# 19. Session and Application State
-
-Avoid storing custom COM objects, recordsets, service instances, or application classes in:
-
-```vbscript
-Session
-Application
-```
-
-Prefer primitive values:
-
-```vbscript
-Session("UserId") = CLng(userId)
-Session("Username") = CStr(username)
-```
-
-Use Application state carefully and protect shared writes appropriately.
-
----
-
-# 20. ADODB Resource Management
-
-Explicitly close database resources.
-
-Example:
-
-```vbscript
-If Not rs Is Nothing Then
- If rs.State <> 0 Then
- rs.Close
- End If
-
- Set rs = Nothing
-End If
-```
-
-Connections must also be closed when owned by the current operation.
-
-Do not leave recordsets or connections open unnecessarily.
-
----
-
-# 21. Connection Ownership
-
-The project should clearly define who owns a database connection.
-
-A method that opens a connection is normally responsible for closing it.
-
-Do not close a connection owned by another component unless the contract explicitly states that responsibility.
-
----
-
-# 22. Small Functions
-
-Prefer small cohesive procedures.
-
-Target guideline:
-
-```text
-Function:
-10–40 lines where practical
-
-Class:
-100–300 lines where practical
-
-Controller:
-preferably under 200 lines
-```
-
-These are guidelines, not absolute limits.
-
-When code becomes difficult to understand, split it by responsibility.
-
----
-
-# 23. Naming Conventions
-
-Use consistent naming because VBScript is case-insensitive and offers limited compiler assistance.
-
-Preferred examples:
-
-```text
-customerId
-customerName
-orderRepository
-customerService
-isValid
-hasPermission
-```
-
-Class names:
-
-```text
-CustomerService
-CustomerRepository
-CustomerValidator
-OrderController
-```
-
-Private fields may use:
-
-```text
-m_repository
-m_logger
-m_customerId
-```
-
-Follow established project conventions if they are consistent and clear.
-
----
-
-# 24. Boolean Naming
-
-Prefer names that read naturally:
-
-```text
-isValid
-isActive
-hasAccess
-hasPermission
-canDelete
-shouldRetry
-```
-
-Avoid ambiguous names such as:
-
-```text
-flag
-value1
-test
-status2
-```
-
----
-
-# 25. Magic Values
-
-Avoid unexplained magic values.
-
-Bad:
-
-```vbscript
-If status = 4 Then
-```
-
-Better:
-
-```vbscript
-Const STATUS_COMPLETED = 4
-```
-
-Use constants or centralized configuration where appropriate.
-
----
-
-# 26. Configuration
-
-Environment-specific settings must not be scattered throughout application code.
-
-Centralize:
-
-- connection strings
-- filesystem paths
-- URLs
-- email settings
-- feature flags
-- API keys
-- environment names
-
-Secrets must not be committed to source control.
-
----
-
-# 27. Logging
-
-Important errors should be logged with useful context.
-
-Prefer structured information such as:
-
-```text
-Timestamp
-RequestId
-UserId if appropriate
-Component
-Operation
-Error number
-Error description
-Relevant safe context
-```
-
-Never log:
-
-- passwords
-- authentication tokens
-- full credit card data
-- secret keys
-- highly sensitive personal data unless explicitly necessary and protected
-
----
-
-# 28. Authentication and Authorization
-
-Authentication answers:
-
-```text
-Who is this user?
-```
-
-Authorization answers:
-
-```text
-Is this user allowed to perform this operation?
-```
-
-They are not interchangeable.
-
-Authorization must be enforced server-side.
-
-Never rely solely on hidden buttons, JavaScript, or UI restrictions.
-
----
-
-# 29. CSRF Protection
-
-State-changing requests should use CSRF protection where practical.
-
-This includes:
-
-- POST
-- PUT-like actions
-- DELETE-like actions
-- administrative operations
-
-Do not perform destructive actions solely through unprotected GET requests.
-
----
-
-# 30. HTTP Method Semantics
-
-Prefer:
-
-```text
-GET → retrieve data
-POST → create/change state
-```
-
-Avoid state-changing GET endpoints unless required by legacy compatibility.
-
----
-
-# 31. Redirect Safety
-
-Do not redirect to arbitrary user-provided URLs.
-
-Validate redirects against:
-
-- known internal routes
-- allowed hosts
-- approved paths
-
----
-
-# 32. File Handling
-
-Uploaded files must be treated as untrusted.
-
-Validate:
-
-- filename
-- extension
-- actual content where practical
-- size
-- target path
-- permissions
-
-Prevent path traversal.
-
-Never construct filesystem paths directly from arbitrary user input.
-
----
-
-# 33. Include File Discipline
-
-Includes are dependencies, not architecture.
-
-Avoid large global include chains.
-
-Prefer explicit grouped includes such as:
-
-```text
-/config
-/framework
-/app/controllers
-/app/services
-/app/repositories
-/app/models
-/app/views
-```
-
-Before adding a new include, check whether the functionality belongs in an existing component.
-
----
-
-# 34. Shared Utilities
-
-Before creating a utility function, search for an existing implementation.
-
-Common shared helpers may include:
-
-```text
-HtmlEncode
-JsonEncode
-JsonEscape
-Nz
-ToInteger
-ToLong
-ToBoolean
-ToDate
-ValidateEmail
-ExecuteScalar
-ExecuteNonQuery
-Logger
-UrlEncode
-GenerateGuid
-```
-
-Do not create duplicate helpers with slightly different behavior.
-
----
-
-# 35. JSON
-
-Do not build complex JSON through unsafe string concatenation.
-
-Use the project's JSON serializer or centralized JSON utilities.
-
-Escape:
-
-- quotes
-- backslashes
-- control characters
-- line breaks
-
-Ensure output is valid JSON.
-
----
-
-# 36. JavaScript Boundaries
-
-Server-side validation is mandatory even when client-side validation exists.
-
-JavaScript validation is for user experience.
-
-VBScript validation is for correctness and security.
-
-Never trust browser-side validation alone.
-
----
-
-# 37. Testing Expectations
-
-Every significant change should be evaluated for testability.
-
-Where practical, create tests for:
-
-- validation
-- business rules
-- conversions
-- repositories
-- security-sensitive logic
-- regressions
-
-Business logic should be separated from ASP globals so it can be tested independently.
-
----
-
-# 38. Regression Prevention
-
-When fixing a bug:
-
-1. understand the root cause
-2. identify the violated invariant
-3. fix the underlying problem
-4. add or update a test where practical
-5. search for similar occurrences elsewhere
-6. update documentation if the rule was previously unclear
-
-Do not patch symptoms repeatedly.
-
----
-
-# 39. Agent Pre-Change Procedure
-
-Before modifying code, the agent should:
-
-1. Read this AGENTS.md.
-2. Read CLAUDE.md if present.
-3. Inspect the relevant project files.
-4. Search for existing implementations.
-5. Understand the dependency direction.
-6. Identify security implications.
-7. Determine whether tests exist.
-8. Avoid creating duplicate abstractions.
-
-For small changes, do this proportionally.
-
----
-
-# 40. Agent Post-Change Review
-
-Before declaring work complete, review changed code for:
-
-- missing Option Explicit
-- undeclared variables
-- misspelled identifiers
-- missing Set statements
-- incorrect parameter counts
-- Null handling
-- Empty handling
-- Nothing handling
-- unsafe implicit conversion
-- SQL injection
-- unparameterized SQL
-- XSS
-- missing HTML encoding
-- unsafe redirects
-- path traversal
-- authorization bypass
-- broad On Error Resume Next
-- swallowed errors
-- open recordsets
-- open database connections
-- excessive global state
-- duplicate utilities
-- architectural boundary violations
-- unreachable code
-- dead code
-- missing cleanup
-- poor naming
-
----
-
-# 41. Missing Set Check
-
-VBScript requires `Set` for object assignment.
-
-Agents must specifically inspect object assignments.
-
-Incorrect:
-
-```vbscript
-repo = New CustomerRepository
-```
-
-Correct:
-
-```vbscript
-Set repo = New CustomerRepository
-```
-
-This should be part of every static review.
-
----
-
-# 42. Return Value Check
-
-VBScript functions return values by assigning to the function name.
-
-Example:
-
-```vbscript
-Function AddNumbers(a, b)
- AddNumbers = a + b
-End Function
-```
-
-Agents must verify all execution paths return the expected value when a return value is required.
-
----
-
-# 43. ByRef Awareness
-
-VBScript parameters are `ByRef` by default.
-
-This can unintentionally mutate caller values.
-
-Prefer explicitly declaring intent.
-
-Use:
-
-```vbscript
-Function ValidateCustomer(ByVal customer)
-```
-
-when modification of the caller's variable is not intended.
-
-Use `ByRef` intentionally.
-
----
-
-# 44. Parentheses and Call Syntax
-
-VBScript procedure-call syntax can be confusing.
-
-Agents must generate syntactically valid VBScript calls.
-
-Be especially careful with:
-
-- `Call`
-- parentheses
-- functions used for return values
-- Subs invoked without `Call`
-
-Prefer simple consistent calling patterns.
-
----
-
-# 45. Date Handling
-
-Do not assume date string formats.
-
-Prefer true Date values internally.
-
-Validate and convert input explicitly.
-
-Be aware of:
-
-- server locale
-- database date formats
-- regional settings
-
-Use parameterized database values rather than embedding formatted date strings in SQL.
-
----
-
-# 46. Numeric Handling
-
-Do not assume values from Request are numeric.
-
-Validate before conversion.
-
-Example logic:
-
-```text
-Read input
-↓
-Trim
-↓
-Check required/optional
-↓
-Validate numeric format
-↓
-Convert
-```
-
-Handle overflow and invalid values appropriately.
-
----
-
-# 47. Database Null Mapping
-
-When mapping database records to models, define expected behavior for Null fields.
-
-Do not allow arbitrary Null propagation unless the domain explicitly permits it.
-
-Consider centralized helpers.
-
----
-
-# 48. Performance
-
-ASP Classic is synchronous.
-
-Avoid unnecessary:
-
-- database round trips
-- repeated queries inside loops
-- filesystem calls
-- external HTTP requests
-- COM object creation
-- large Session values
-
-Watch for N+1 query patterns.
-
-Prefer set-based SQL.
-
----
-
-# 49. Caching
-
-Caching may be used where appropriate but must have:
-
-- clear ownership
-- expiration behavior
-- invalidation rules
-- concurrency awareness
-
-Never cache sensitive per-user data globally without proper separation.
-
----
-
-# 50. Application State Concurrency
-
-ASP `Application` state can be shared across requests.
-
-When modifying shared Application values, consider:
-
-```vbscript
-Application.Lock
-Application.Unlock
-```
-
-Keep locked regions minimal.
-
----
-
-# 51. Backward Compatibility
-
-When modifying legacy code:
-
-1. identify existing behavior
-2. determine whether other code depends on it
-3. preserve public contracts where practical
-4. avoid unrelated rewrites
-5. improve internals incrementally
-
-Do not modernize code purely for style if it introduces unnecessary risk.
-
----
-
-# 52. Refactoring Rule
-
-Refactoring should preserve observable behavior unless changing behavior is an explicit goal.
-
-Separate:
-
-```text
-behavior change
-```
-
-from:
-
-```text
-structural cleanup
-```
-
-where practical.
-
----
-
-# 53. Documentation Rule
-
-When introducing:
-
-- a new architecture convention
-- a new shared component
-- a new security requirement
-- a new dependency
-- a new recurring coding pattern
-
-update relevant documentation.
-
-Do not leave important architectural knowledge only in source code.
-
----
-
-# 54. Self-Improvement System
-
-This file is allowed to evolve.
-
-Agents may propose or make updates to AGENTS.md when project experience reveals that the instructions are:
-
-- incomplete
-- outdated
-- ambiguous
-- repeatedly violated
-- causing bugs
-- missing an important security rule
-- missing an architectural convention
-- inconsistent with the actual project
-- superseded by a better proven pattern
-
-Self-improvement must be controlled.
-
----
-
-# 55. When the Agent Should Update AGENTS.md
-
-Consider updating this file when:
-
-1. the same mistake occurs more than once
-2. a bug reveals a missing development rule
-3. a new framework component establishes a reusable pattern
-4. a new security requirement becomes necessary
-5. project architecture materially changes
-6. a repeated manual review step can become an explicit rule
-7. a legacy convention is officially replaced
-8. new infrastructure becomes standard
-9. a user explicitly establishes a permanent project rule
-
-Do not update this file for trivial one-time implementation details.
-
----
-
-# 56. Self-Update Safety
-
-An agent must never silently weaken these core protections:
-
-- parameterized SQL
-- output encoding
-- authorization
-- input validation
-- controlled error handling
-- explicit variable declarations
-- Option Explicit
-- resource cleanup
-- security boundaries
-
-Changes affecting these protections require explicit justification.
-
----
-
-# 57. Rule Classification
-
-Rules may be considered:
-
-```text
-CORE
-PROJECT
-ADVISORY
-TEMPORARY
-```
-
-CORE rules should rarely change.
-
-Examples:
-
-```text
-CORE:
-SQL parameterization
-Option Explicit
-server-side authorization
-output encoding
-
-PROJECT:
-folder structure
-repository naming
-routing conventions
-
-ADVISORY:
-function length guidelines
-
-TEMPORARY:
-migration-specific compatibility rules
-```
-
-When adding an important rule, identify its conceptual category if useful.
-
----
-
-# 58. Learning From Bugs
-
-After fixing a significant bug, ask:
-
-```text
-Could a rule have prevented this?
-```
-
-If yes:
-
-- update AGENTS.md, or
-- update a specialized project document, or
-- add a test, or
-- improve a framework abstraction
-
-Prefer systemic prevention over repeated manual correction.
-
----
-
-# 59. Learning From Code Review
-
-If a code review repeatedly identifies the same problem, convert the feedback into:
-
-- an explicit agent rule
-- a reusable helper
-- a test
-- an architectural constraint
-
-The goal is continuous reduction of repeated mistakes.
-
----
-
-# 60. Update History
-
-Meaningful changes to AGENTS.md should be recorded below.
-
-Keep entries concise.
-
-Format:
-
-```text
-YYYY-MM-DD
-- Added rule:
-- Reason:
-```
-
-Example:
-
-```text
-2026-09-02
-- Added explicit ByVal guidance.
-- Reason: VBScript defaults to ByRef and accidental mutations were difficult to detect.
-```
-
----
-
-# 61. Do Not Turn This File Into a Dumping Ground
-
-AGENTS.md should contain durable development knowledge.
-
-Do not store:
-
-- task-specific notes
-- temporary debugging output
-- one-time TODOs
-- credentials
-- secrets
-- personal information
-- giant code examples
-
-Move specialized guidance into focused files when this file becomes too large.
-
----
-
-# 62. Specialized Skills
-
-As the project grows, agents may create focused guidance such as:
-
-```text
-/docs/architecture.md
-/docs/security.md
-/docs/database.md
-/docs/testing.md
-
-/skills/asp-classic.md
-/skills/adodb.md
-/skills/security.md
-/skills/testing.md
-```
-
-AGENTS.md remains the central index and policy authority.
-
----
-
-# 63. Creating New Skills
-
-Agents may create a new skill or focused guidance document when:
-
-- a task is repeated frequently
-- the knowledge is specialized
-- the instructions are too detailed for AGENTS.md
-- the process can be reused
-- consistent execution would materially improve quality
-
-A new skill should state:
-
-```text
-Purpose
-When to use it
-Inputs
-Procedure
-Rules
-Validation
-Common failure modes
-Output expectations
-```
-
----
-
-# 64. Updating Existing Skills
-
-When project behavior changes, update the existing relevant skill instead of creating a duplicate.
-
-Before creating a new document, search for an existing location for that knowledge.
-
----
-
-# 65. Agent Decision Journal
-
-For major architectural changes, record concise rationale in the appropriate documentation.
-
-Document:
-
-```text
-Decision
-Context
-Alternatives
-Reason selected
-Consequences
-```
-
-Do not record private chain-of-thought.
-
-Record only useful engineering rationale.
-
----
-
-# 66. Definition of Done
-
-A task is not complete merely because the code runs.
-
-A change is complete when appropriate checks have been performed for:
-
-```text
-Correctness
-Security
-Architecture
-Compatibility
-Validation
-Error handling
-Resource cleanup
-Testing
-Documentation
-Maintainability
-```
-
----
-
-# 67. Final Agent Checklist
-
-Before finishing a coding task, answer internally:
-
-```text
-[ ] Did I read the relevant instructions?
-[ ] Did I inspect existing code before creating new code?
-[ ] Did I preserve architecture boundaries?
-[ ] Is all external input validated?
-[ ] Is SQL parameterized?
-[ ] Is dynamic HTML encoded?
-[ ] Is authorization enforced server-side?
-[ ] Are errors handled intentionally?
-[ ] Are database resources cleaned up?
-[ ] Are object assignments using Set?
-[ ] Did I account for Null, Empty, and Nothing?
-[ ] Did I account for ByRef behavior?
-[ ] Did I avoid unnecessary global state?
-[ ] Did I avoid duplicating utilities?
-[ ] Did I update tests where appropriate?
-[ ] Did I update documentation if a durable rule changed?
-[ ] Did this task reveal something the agent rules should learn?
-```
-
----
-
-# 68. Guiding Philosophy
-
-Do not fight VBScript.
-
-Compensate for its weaknesses.
-
-Prefer simple, explicit, understandable code over clever abstractions.
-
-The desired result is:
-
-```text
-Classic ASP simplicity
- +
-strict engineering discipline
- +
-AI-assisted static review
- +
-modern security practices
- =
-maintainable ASP Classic applications
-```
-
----
-
-# Update History
-
-2026-09-02
-- Initial ASP Classic/VBScript AI development policy created.
-- Added controlled self-improvement rules.
-- Added architecture, security, validation, ADODB, error handling, testing, and agent review requirements.
diff --git a/docs/CLAUDE.md b/docs/CLAUDE.md
deleted file mode 100644
index c19f92d..0000000
--- a/docs/CLAUDE.md
+++ /dev/null
@@ -1,994 +0,0 @@
-# CLAUDE.md
-
-## Purpose
-
-This file defines how Claude and Claude-compatible coding agents should operate in this repository.
-
-Read `AGENTS.md` first.
-
-`AGENTS.md` is the primary development policy.
-
-This file defines agent workflow, reasoning discipline, project learning, and self-improvement behavior.
-
----
-
-# 1. Startup Procedure
-
-At the beginning of a development task:
-
-1. Read `AGENTS.md`.
-2. Read this `CLAUDE.md`.
-3. Inspect relevant files before editing.
-4. Search the repository for similar implementations.
-5. Identify existing helpers and abstractions.
-6. Determine the affected architectural layers.
-7. Identify security-sensitive boundaries.
-8. Identify tests relevant to the change.
-
-Do not immediately generate a new abstraction before understanding the existing project.
-
----
-
-# 2. Treat VBScript as a Language Requiring Agent-Level Static Analysis
-
-VBScript lacks many safeguards found in modern compiled languages.
-
-Therefore, while working, actively inspect for:
-
-```text
-undeclared variables
-misspelled identifiers
-missing Set
-wrong argument counts
-implicit Variant conversions
-Null propagation
-Empty values
-Nothing references
-unexpected ByRef mutation
-invalid function return paths
-unsafe SQL
-unsafe HTML output
-broad error suppression
-resource leaks
-global-state coupling
-```
-
-Act as a compiler substitute.
-
----
-
-# 3. Think in Boundaries
-
-Before changing code, determine which layer owns the behavior.
-
-Preferred direction:
-
-```text
-Request
- ↓
-Controller
- ↓
-Service
- ↓
-Repository
- ↓
-Database
-```
-
-Views should receive prepared data.
-
-Do not bypass layers for convenience.
-
----
-
-# 4. Avoid Architectural Drift
-
-When adding functionality, prefer the project's existing good pattern.
-
-Do not create:
-
-```text
-CustomerManager
-CustomerHandler
-CustomerEngine
-CustomerProcessor
-CustomerHelper
-```
-
-if `CustomerService` already represents the business layer.
-
-Reuse terminology consistently.
-
----
-
-# 5. Search Before Creating
-
-Before creating:
-
-- a helper
-- service
-- repository
-- validator
-- utility
-- model
-- WSC component
-- framework function
-
-search for an existing implementation.
-
-Prefer extending an existing appropriate abstraction over creating duplicates.
-
----
-
-# 6. Make Small Changes
-
-Prefer the smallest coherent change that solves the problem.
-
-Avoid unrelated cleanup unless necessary.
-
-If a task exposes a larger architectural problem:
-
-1. solve the requested problem safely
-2. document the larger concern
-3. improve the architecture when justified
-
-Do not turn every bug fix into a rewrite.
-
----
-
-# 7. Never Hide Errors to Make Code Appear Functional
-
-Do not use broad:
-
-```vbscript
-On Error Resume Next
-```
-
-to make failures disappear.
-
-Errors should be:
-
-```text
-prevented
-handled
-logged
-or propagated
-```
-
-Never silently swallowed.
-
----
-
-# 8. Security Review Is Mandatory
-
-For each change involving input, output, authentication, database access, file handling, or state changes, inspect for:
-
-```text
-SQL injection
-XSS
-CSRF
-authorization bypass
-open redirects
-path traversal
-unsafe file upload
-sensitive logging
-session misuse
-```
-
-Security checks are not optional even if the user did not explicitly request them.
-
----
-
-# 9. SQL Generation
-
-Always prefer parameterized ADODB commands.
-
-If existing code concatenates SQL, do not copy the unsafe pattern into new code.
-
-When touching the unsafe section directly, improve it if doing so is reasonably scoped and compatibility can be preserved.
-
----
-
-# 10. HTML Generation
-
-Dynamic text should be HTML encoded by default.
-
-Do not output raw Request or database values into HTML.
-
-When raw HTML is intentionally supported, make that trust boundary explicit.
-
----
-
-# 11. Validate at the Boundary
-
-Convert raw external values into trusted application values early.
-
-Think:
-
-```text
-Raw Request value
- ↓
-Trim / normalize
- ↓
-Required check
- ↓
-Format validation
- ↓
-Type conversion
- ↓
-Business validation
- ↓
-Use
-```
-
-Do not allow raw Request values to flow deeply through the system.
-
----
-
-# 12. Prefer Explicit Code
-
-In VBScript, explicit code is usually safer than clever code.
-
-Prefer:
-
-```vbscript
-Dim customerId
-customerId = CLng(value)
-```
-
-over relying on automatic coercion.
-
-Prefer clear control flow over compressed expressions.
-
----
-
-# 13. Functions and Procedures
-
-Keep procedures focused.
-
-If a function performs several unrelated operations, extract meaningful collaborators.
-
-Prefer names that describe intent.
-
-Bad:
-
-```text
-ProcessData
-HandleStuff
-DoWork
-RunThing
-```
-
-Better:
-
-```text
-ValidateCustomer
-CalculateOrderTotal
-LoadCustomerById
-SaveAppointment
-```
-
----
-
-# 14. ByVal and ByRef
-
-Remember:
-
-VBScript defaults parameters to `ByRef`.
-
-When mutation is not intended, prefer:
-
-```vbscript
-ByVal
-```
-
-Use `ByRef` only when caller mutation is intentional.
-
-Review changed method signatures for accidental ByRef behavior.
-
----
-
-# 15. Object Assignment
-
-Remember that object assignment requires `Set`.
-
-Review every new or modified object assignment.
-
-Example:
-
-```vbscript
-Set service = New CustomerService
-```
-
-not:
-
-```vbscript
-service = New CustomerService
-```
-
----
-
-# 16. Function Return Values
-
-VBScript functions return values by assigning to their own name.
-
-Review all paths.
-
-Example:
-
-```vbscript
-Function IsAllowed(ByVal userId)
-
- If userId <= 0 Then
- IsAllowed = False
- Exit Function
- End If
-
- IsAllowed = True
-
-End Function
-```
-
-Do not accidentally leave an important return value as Empty.
-
----
-
-# 17. COM and ADODB Cleanup
-
-When creating:
-
-- ADODB.Connection
-- ADODB.Command
-- ADODB.Recordset
-- filesystem objects
-- external COM components
-
-understand ownership and cleanup behavior.
-
-Explicitly close database resources when appropriate.
-
-Set references to `Nothing` when ownership ends.
-
----
-
-# 18. Avoid Global State
-
-Prefer explicit dependencies.
-
-Do not add Session or Application state merely because it is convenient.
-
-Ask:
-
-```text
-Does this value truly need to survive across requests?
-```
-
-If not, keep it request-scoped.
-
----
-
-# 19. Testing Strategy
-
-When possible, isolate business logic from ASP globals.
-
-Favor code that can be invoked with plain VBScript values or mock collaborators.
-
-When fixing a defect, add a regression test where practical.
-
----
-
-# 20. Comments
-
-Comments should explain:
-
-```text
-why
-constraints
-non-obvious behavior
-compatibility requirements
-security rationale
-```
-
-Do not add comments that merely repeat the code.
-
-Bad:
-
-```vbscript
-' Set customer id
-customerId = 10
-```
-
-Useful:
-
-```vbscript
-' Legacy import files use 0 to represent an unknown customer.
-If customerId = 0 Then
-```
-
----
-
-# 21. Preserve Valuable Legacy Behavior
-
-ASP Classic projects may contain old but important behavior.
-
-Do not assume unfamiliar code is wrong.
-
-Before removing or replacing something:
-
-1. search references
-2. understand callers
-3. identify side effects
-4. inspect related documentation
-5. preserve compatibility where required
-
----
-
-# 22. Self-Improvement
-
-This repository is designed to allow agents to improve their own operating instructions.
-
-Self-improvement should make future work:
-
-- safer
-- more consistent
-- easier to verify
-- less repetitive
-- more maintainable
-
-Do not change instructions merely to suit the current implementation.
-
-Improve the implementation when practical rather than weakening good rules.
-
----
-
-# 23. Detect Learnable Events
-
-A learnable event occurs when:
-
-- a repeated bug appears
-- a review catches the same mistake repeatedly
-- a new reusable pattern proves successful
-- a project architecture decision becomes permanent
-- a new security requirement becomes necessary
-- a repeated workflow can be standardized
-- a user establishes a lasting convention
-- an existing instruction becomes inaccurate
-
-When a learnable event occurs, determine whether project documentation should change.
-
----
-
-# 24. Where Learning Should Go
-
-Use the smallest appropriate scope.
-
-Update:
-
-```text
-AGENTS.md
-```
-
-for project-wide durable policies.
-
-Update:
-
-```text
-CLAUDE.md
-```
-
-for agent workflow and Claude-specific operating behavior.
-
-Update:
-
-```text
-/docs/*
-```
-
-for architectural or project documentation.
-
-Update:
-
-```text
-/skills/*
-```
-
-for reusable specialized procedures.
-
-Update source-code comments only for code-local constraints.
-
-Do not place every lesson into AGENTS.md.
-
----
-
-# 25. Self-Update Decision
-
-Before modifying an instruction file, ask:
-
-```text
-Is this lesson likely to matter again?
-```
-
-If no, do not persist it.
-
-Then ask:
-
-```text
-Is this project-wide?
-```
-
-If yes, consider AGENTS.md.
-
-Otherwise use a specialized document.
-
----
-
-# 26. Never Self-Weaken Core Safety Rules
-
-Claude must not autonomously remove or weaken requirements involving:
-
-```text
-Option Explicit
-parameterized SQL
-server-side authorization
-input validation
-output encoding
-safe error handling
-resource cleanup
-secret protection
-```
-
-If an unusual project requirement appears to conflict with one of these rules, preserve safety and document the conflict.
-
----
-
-# 27. Avoid Instruction Inflation
-
-Do not add a new rule for every isolated mistake.
-
-Prefer:
-
-```text
-general principle
-+
-reusable example
-```
-
-rather than hundreds of hyper-specific rules.
-
-A rule should prevent a category of mistakes.
-
----
-
-# 28. Improve Through Abstractions Too
-
-Not every lesson belongs in documentation.
-
-If the same safety requirement appears repeatedly, consider encoding it into the framework.
-
-Examples:
-
-```text
-Safe HTML helper
-Parameterized database wrapper
-Request conversion helper
-Validation library
-Authorization service
-Logging wrapper
-Repository base conventions
-```
-
-The best rule is sometimes one the architecture makes difficult to violate.
-
----
-
-# 29. Skill Creation
-
-Claude may create a new skill when the task is:
-
-- specialized
-- recurring
-- procedural
-- reusable across multiple features
-
-Possible skills:
-
-```text
-/skills/asp-classic-controller.md
-/skills/adodb-repository.md
-/skills/sql-parameterization.md
-/skills/security-review.md
-/skills/vbscript-static-review.md
-/skills/testing.md
-```
-
-Do not create a skill for a one-time task.
-
----
-
-# 30. Suggested Skill Format
-
-Each skill should contain:
-
-```text
-# Skill Name
-
-## Purpose
-
-## Use When
-
-## Inputs
-
-## Procedure
-
-## Required Rules
-
-## Validation Checklist
-
-## Common Mistakes
-
-## Output
-
-## Improvement Notes
-```
-
----
-
-# 31. Agent Review Loop
-
-For meaningful tasks, use this cycle:
-
-```text
-Understand
- ↓
-Inspect
- ↓
-Design
- ↓
-Implement
- ↓
-Review
- ↓
-Test
- ↓
-Learn
-```
-
-The `Learn` step asks whether the repository's instructions or abstractions should improve.
-
----
-
-# 32. Design Before Implementation
-
-For significant changes, identify:
-
-```text
-affected layers
-new components
-existing components
-data flow
-validation boundaries
-security boundaries
-failure behavior
-testing strategy
-```
-
-before writing substantial code.
-
-For small tasks, keep this lightweight.
-
----
-
-# 33. Avoid Premature Framework Expansion
-
-Do not add framework components simply because another language would have them.
-
-ASP Classic should remain simple.
-
-Only introduce an abstraction when it solves a real recurring problem.
-
----
-
-# 34. Prefer Composition
-
-When extending behavior, first consider:
-
-```text
-small object
-+
-small collaborator
-```
-
-instead of large pseudo-inheritance systems.
-
-VBScript is better suited to simple composition.
-
----
-
-# 35. Stable Public Contracts
-
-Treat established routes, APIs, class methods, and database contracts carefully.
-
-When changing them:
-
-1. search callers
-2. assess compatibility
-3. update all consumers
-4. document breaking changes
-
-Avoid accidental interface changes.
-
----
-
-# 36. Database Changes
-
-For schema changes, consider:
-
-```text
-backward compatibility
-migration order
-existing data
-Null behavior
-indexes
-constraints
-application deployment timing
-rollback implications
-```
-
-Do not modify schema casually.
-
----
-
-# 37. Performance Review
-
-When working with database code, look for:
-
-```text
-queries inside loops
-SELECT *
-unnecessary recordset traversal
-missing filters
-repeated connection creation
-large Session values
-unbounded result sets
-unnecessary COM calls
-```
-
-Prefer database-side filtering and set operations.
-
----
-
-# 38. Request Lifecycle Awareness
-
-Remember that ASP Classic operates per request.
-
-Avoid assumptions that local variables persist between requests.
-
-Use Session or persistent storage only when the application truly requires cross-request state.
-
----
-
-# 39. Application Lifecycle Awareness
-
-`Application` state is global to the ASP application.
-
-Do not store user-specific data there.
-
-Be mindful of concurrent requests.
-
----
-
-# 40. Avoid Hidden Dependencies
-
-A component should not unexpectedly require:
-
-```text
-Request
-Response
-Session
-Application
-global connection object
-global logger
-global configuration
-```
-
-unless that dependency is part of the documented architecture.
-
-Prefer passing dependencies explicitly.
-
----
-
-# 41. Compatibility First During Refactoring
-
-When modernizing legacy code:
-
-```text
-First preserve behavior.
-Then improve structure.
-```
-
-Avoid combining major behavior changes with major refactors unless necessary.
-
----
-
-# 42. Repository Review After Significant Changes
-
-After a meaningful feature or refactor, search for:
-
-- outdated documentation
-- duplicate helpers
-- now-dead code
-- inconsistent naming
-- old patterns replaced by the new pattern
-- opportunities to update an existing skill
-
-Keep the repository internally consistent.
-
----
-
-# 43. Learn From User Corrections
-
-If the user corrects Claude on a durable project convention, treat that as a possible learnable event.
-
-Examples:
-
-```text
-"We always use this router."
-"Repositories never return recordsets."
-"All AJAX endpoints use this response format."
-"Use WSC components for controllers."
-```
-
-If the instruction is durable, update the appropriate project guidance.
-
-Do not persist one-time preferences as permanent architecture rules.
-
----
-
-# 44. Change Documentation Conservatively
-
-When modifying AGENTS.md, CLAUDE.md, or skills:
-
-- preserve useful existing rules
-- remove duplicates
-- clarify contradictions
-- avoid unnecessary rewrites
-- add an update-history entry where appropriate
-
-Instruction files should become clearer over time, not merely longer.
-
----
-
-# 45. Conflict Detection
-
-If project documentation contradicts itself:
-
-1. identify the conflict
-2. use higher-priority instructions
-3. resolve the contradiction where safe
-4. update documentation so future agents do not face the same conflict
-
-Do not silently choose different interpretations from task to task.
-
----
-
-# 46. Self-Improvement Is Not Autonomous Product Redesign
-
-Claude may improve:
-
-```text
-guidelines
-skills
-documentation
-developer workflow
-internal abstractions
-```
-
-when justified.
-
-Claude must not independently redefine:
-
-```text
-business requirements
-user-visible behavior
-pricing
-authorization policy
-data retention policy
-major architecture goals
-```
-
-without task requirements supporting the change.
-
----
-
-# 47. Final Review Mode
-
-Before completing a task, temporarily switch from implementation thinking to adversarial review.
-
-Ask:
-
-```text
-How could this fail?
-
-What would VBScript fail to warn me about?
-
-What input breaks this?
-
-What happens with Null?
-
-What happens with Empty?
-
-What happens with Nothing?
-
-Did I forget Set?
-
-Could ByRef change the caller?
-
-Could SQL injection occur?
-
-Could XSS occur?
-
-Could authorization be bypassed?
-
-Could an error be swallowed?
-
-Could a recordset remain open?
-
-Did I accidentally duplicate existing functionality?
-```
-
-Fix issues found before finishing.
-
----
-
-# 48. Final Summary
-
-When reporting completed coding work, mention material items such as:
-
-```text
-what changed
-important architecture choices
-tests performed
-security implications
-documentation or rule updates
-remaining limitations
-```
-
-Do not produce long explanations for trivial changes.
-
----
-
-# 49. Core Philosophy
-
-Claude should help make ASP Classic behave like a disciplined modern development environment without destroying the language's simplicity.
-
-The goal is not to turn VBScript into C#.
-
-The goal is to provide the safeguards that VBScript lacks.
-
-Think:
-
-```text
-VBScript
-+
-clear architecture
-+
-strict conventions
-+
-reusable framework components
-+
-agent-level static analysis
-+
-continuous learning
-=
-reliable ASP Classic development
-```
-
----
-
-# Update History
-
-2026-09-02
-- Initial Claude operating instructions created.
-- Added VBScript static-review behavior.
-- Added controlled self-improvement workflow.
-- Added learnable-event detection and skill creation guidance.
diff --git a/public/Default.asp b/public/Default.asp
index 61c5a91..0c9c28a 100644
--- a/public/Default.asp
+++ b/public/Default.asp
@@ -1,3 +1,12 @@
+<%
+' Classic ASP splices #include files together as raw text before compiling,
+' and Option Explicit is only legal once, as the literal first statement of
+' the resulting page. Default.asp pulls in the entire application (core/*,
+' app/controllers/*, app/views/*) via autoload_core.asp below, so this single
+' declaration enforces explicit variable declaration across all of it. Do not
+' add another Option Explicit to any included file - see AGENTS.md.
+Option Explicit
+%>
<%
@@ -7,7 +16,31 @@
router.AddRoute "GET", "", "HomeController", "Index"
router.AddRoute "GET", "/404", "ErrorController", "NotFound"
- ' Dispatch the request (resolves route and executes controller action)
- MVC.DispatchRequest Request.ServerVariables("REQUEST_METHOD"), _
- TrimQueryParams(Request.ServerVariables("HTTP_X_ORIGINAL_URL"))
+ ' Resolve the route/controller/action. MVC is a Windows Script Component
+ ' and (per AGENTS.md's "Controllers Coordinate; They Do Not Render") never
+ ' writes HTML itself - Resolve()/ExecuteAction() only ever report success
+ ' or populate LastError, and rendering happens here. It also cannot use
+ ' Server-Side Includes, so the layout wrapping that used to happen inside
+ ' Dispatch() now happens here, guarded by MVC.UseLayout.
+ Dim requestResolved
+ requestResolved = MVC.Resolve(Request.ServerVariables("REQUEST_METHOD"), _
+ TrimQueryParams(Request.ServerVariables("HTTP_X_ORIGINAL_URL")))
+
+ If Not requestResolved Then
+ RenderMvcError MVC.LastError
+ Else
+ If MVC.UseLayout Then
+%><%
+ End If
+
+ MVC.ExecuteAction()
+
+ If Not (MVC.LastError Is Nothing) Then
+ RenderMvcError MVC.LastError
+ End If
+
+ If MVC.UseLayout Then
+%><%
+ End If
+ End If
%>
diff --git a/run_site.cmd b/run_site.cmd
index 2f87205..e564eb5 100644
--- a/run_site.cmd
+++ b/run_site.cmd
@@ -1,4 +1,16 @@
@echo off
setlocal
set "ASPC_STARTER_ROOT=%~dp0"
-"C:\Program Files\IIS Express\iisexpress.exe" /config:"%~dp0applicationhost.config"
\ No newline at end of file
+set "IISEXPRESS=C:\Program Files\IIS Express\iisexpress.exe"
+
+rem IIS Express only runs ONE site per process, even when applicationhost.config
+rem defines several with serverAutoStart="true" (that attribute is honored by
+rem full IIS/W3SVC, not by a single ad-hoc iisexpress.exe invocation - per
+rem "iisexpress /?": "/config:config-file ... runs the first site in the
+rem specified configuration file"). So each site gets its own process here.
+
+echo Starting production site (http://localhost:8080/) ...
+start "IIS Express - Production (8080)" "%IISEXPRESS%" /config:"%~dp0applicationhost.config" /site:"Development Web Site"
+
+echo Starting tests site (http://localhost:8081/) ...
+start "IIS Express - Tests (8081)" "%IISEXPRESS%" /config:"%~dp0applicationhost.config" /site:"Tests Web Site"
diff --git a/tests/component/web.config b/tests/component/web.config
index 3bf406c..e67b96c 100644
--- a/tests/component/web.config
+++ b/tests/component/web.config
@@ -6,7 +6,7 @@
-
+
diff --git a/tests/integration/TestMvcDispatch.asp b/tests/integration/TestMvcDispatch.asp
index 9a8d803..78cafb7 100644
--- a/tests/integration/TestMvcDispatch.asp
+++ b/tests/integration/TestMvcDispatch.asp
@@ -1,6 +1,7 @@
-
+
+
<%
Class TestDispatchController_Class
@@ -31,12 +32,70 @@ Function TestDispatchController()
Set TestDispatchController = TestDispatchController_Class__Singleton
End Function
+' Named distinctly from TestDispatchController so the "Controller" suffix
+' stripping performed by MVC.ControllerName is unambiguous to assert on.
+Class TitleTestController_Class
+ Private m_useLayout
+
+ Private Sub Class_Initialize()
+ m_useLayout = False
+ End Sub
+
+ Public Property Get useLayout
+ useLayout = m_useLayout
+ End Property
+
+ Public Property Let useLayout(value)
+ m_useLayout = value
+ End Property
+
+ Public Sub Smoke()
+ dispatchActionRan = True
+ End Sub
+End Class
+
+Dim TitleTestController_Class__Singleton
+Function TitleTestController()
+ If IsEmpty(TitleTestController_Class__Singleton) Then
+ Set TitleTestController_Class__Singleton = New TitleTestController_Class
+ End If
+ Set TitleTestController = TitleTestController_Class__Singleton
+End Function
+
+' MVC is now a Windows Script Component (core/mvc.wsc) with its own isolated
+' script engine, so - like router.wsc via EnsureTestRouter() below - it is
+' instantiated once and has its dependencies (Router, ControllerRegistry,
+' ControllerFactory, Routes) injected explicitly rather than reaching into
+' this page's globals itself.
+Dim MVC_Instance
+Function MVC()
+ Set MVC = MVC_Instance
+End Function
+
+Sub EnsureTestMvc()
+ Call EnsureTestRouter()
+ ' IsObject(Nothing) is True in VBScript, so - matching EnsureTestRouter()
+ ' above - both conditions are needed: "never assigned" and "explicitly
+ ' reset to Nothing" are different states here.
+ If (Not IsObject(MVC_Instance)) Then
+ Set MVC_Instance = GetObject("script:" & ResolveProjectPath("core\\mvc.wsc"))
+ ElseIf MVC_Instance Is Nothing Then
+ Set MVC_Instance = GetObject("script:" & ResolveProjectPath("core\\mvc.wsc"))
+ End If
+ Set MVC_Instance.Router = router
+ Set MVC_Instance.ControllerRegistry = ControllerRegistry()
+ Set MVC_Instance.ControllerFactory = ControllerFactory()
+ Set MVC_Instance.Routes = Routes()
+End Sub
+
Call ASPUnit.AddModule( _
ASPUnit.CreateModule( _
"MVC Dispatch Smoke Tests", _
Array( _
ASPUnit.CreateTest("RootRouteResolvesToHomeController"), _
- ASPUnit.CreateTest("KnownRouteDispatchCompletesWithoutLookupFailure") _
+ ASPUnit.CreateTest("KnownRouteDispatchCompletesWithoutLookupFailure"), _
+ ASPUnit.CreateTest("ControllerNameReturnsDispatchedControllerNameAsString"), _
+ ASPUnit.CreateTest("ControllerNameFeedsUrlGenerationForRedirects") _
), _
ASPUnit.CreateLifeCycle("SetupMvcDispatch", "TeardownMvcDispatch") _
) _
@@ -47,20 +106,29 @@ Call ASPUnit.Run()
Sub SetupMvcDispatch()
Call ResetTestRuntime()
On Error Resume Next
- MVC_Dispatcher_Class__Singleton = Empty
TestDispatchController_Class__Singleton = Empty
+ TitleTestController_Class__Singleton = Empty
+ Set MVC_Instance = Nothing
On Error GoTo 0
Call ExecuteGlobal("Dim dispatchActionRan")
dispatchActionRan = False
Call RegisterDefaultRoutes()
+ Call EnsureTestMvc()
+
Call ControllerRegistry().RegisterController("testdispatchcontroller")
Call router.AddRoute("GET", "/dispatch-smoke", "testdispatchcontroller", "Smoke")
+ Call ControllerFactory().Register("testdispatchcontroller", GetRef("TestDispatchController"))
+
+ Call ControllerRegistry().RegisterController("titletestcontroller")
+ Call router.AddRoute("GET", "/title-test", "TitleTestController", "Smoke")
+ Call ControllerFactory().Register("TitleTestController", GetRef("TitleTestController"))
End Sub
Sub TeardownMvcDispatch()
On Error Resume Next
- MVC_Dispatcher_Class__Singleton = Empty
+ Set MVC_Instance = Nothing
TestDispatchController_Class__Singleton = Empty
+ TitleTestController_Class__Singleton = Empty
Response.Status = "200 OK"
On Error GoTo 0
Call ResetTestRuntime()
@@ -77,4 +145,33 @@ Function KnownRouteDispatchCompletesWithoutLookupFailure()
Call MVC().DispatchRequest("GET", "/dispatch-smoke")
Call ASPUnit.Ok(dispatchActionRan, "Dispatch should reach a registered controller action without whitelist or lookup failures")
End Function
+
+' Regression test: MVC.ControllerName used to do "ControllerName = CurrentController",
+' assigning an object to a property return without Set. Reading MVC.ControllerName
+' after any controller had been dispatched raised "Object doesn't support this
+' property or method" instead of returning a value. This confirms it now returns
+' the dispatched controller's name as a plain string, with the "Controller" suffix
+' stripped (matching the convention Active() and *StylesheetTag already rely on).
+Function ControllerNameReturnsDispatchedControllerNameAsString()
+ Call MVC().DispatchRequest("GET", "/title-test")
+
+ Dim controllerNameValue
+ controllerNameValue = MVC().ControllerName
+
+ Call ASPUnit.Ok((VarType(controllerNameValue) = vbString), "MVC.ControllerName should return a String, not the controller object")
+ Call ASPUnit.Equal(controllerNameValue, "TitleTest", "MVC.ControllerName should return the dispatched controller's name with the Controller suffix stripped")
+End Function
+
+' ControllerStylesheetTag, RedirectToAction, and the CSRF-invalid-token redirect
+' helper all build a URL/asset path from MVC.ControllerName. This confirms that
+' downstream use actually works now that ControllerName returns a real string,
+' without needing to trigger a live Response.Redirect inside the test run.
+Function ControllerNameFeedsUrlGenerationForRedirects()
+ Call MVC().DispatchRequest("GET", "/title-test")
+
+ Dim generatedUrl
+ generatedUrl = Routes.UrlTo(MVC().ControllerName, "Smoke", Empty)
+
+ Call ASPUnit.Ok((InStr(LCase(generatedUrl), "/titletest/smoke") > 0), "URL built from MVC.ControllerName should contain the lowercase controller/action path, got: " & generatedUrl)
+End Function
%>
diff --git a/tests/integration/web.config b/tests/integration/web.config
index 3bf406c..e67b96c 100644
--- a/tests/integration/web.config
+++ b/tests/integration/web.config
@@ -6,7 +6,7 @@
-
+
diff --git a/tests/unit/web.config b/tests/unit/web.config
index 3bf406c..e67b96c 100644
--- a/tests/unit/web.config
+++ b/tests/unit/web.config
@@ -6,7 +6,7 @@
-
+
diff --git a/tests/web.config b/tests/web.config
index 3bf406c..e67b96c 100644
--- a/tests/web.config
+++ b/tests/web.config
@@ -6,7 +6,7 @@
-
+