Files
lmcp/phase2_integration/versions.lua
T

54 lines
1.9 KiB
Lua

local M = {}
-- versions.lua
-- Module for version checking according to LMCP contract
-- Both versions are supported. The old version (2025-06-18) is retained
-- for backward compatibility, while the new version (2026-07-28) is added.
-- Dual protocol (old + new) is a later phase.
M.SUPPORTED = {'2025-06-18', '2026-07-28'}
--- Checks whether a version is supported.
-- @param version The version to check (string or nil)
-- @return boolean true if supported, otherwise false
-- @return table|nil error details if not supported
function M.check(version)
-- (a) version == nil or '' -> true, nil
if version == nil or version == '' then
return true, nil
end
-- (b) exact match in M.SUPPORTED -> true, nil
for _, supported_version in ipairs(M.SUPPORTED) do
if version == supported_version then
return true, nil
end
end
-- (c) everything else -> false, { code = -32022, data = { supported = <copy> } }
--
-- COPY, not M.SUPPORTED itself. Previously the module table leaked out:
-- a caller that keeps the error object and err.data.supported[1]
-- overwrites, changes the module's list for EVERY subsequent
-- call -- demonstrated on 2026-08-09: after
-- e.data.supported[1] = 'CAPTURED' check('2025-06-18') is false and
-- check('CAPTURED') is true. Contract rule 5 ('A call must not shift the list of
-- the next') was thereby broken, and test 5 did not see it
-- because it never writes over the returned reference.
-- As long as no one called the module, this was theoretical. Since it is attached to
-- initialize, it is reachable.
local copy = {}
for i = 1, #M.SUPPORTED do
copy[i] = M.SUPPORTED[i]
end
return false, {
code = -32022,
data = {
supported = copy
}
}
end
-- No new globals, no side effects, M.SUPPORTED is not modified
return M