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, 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