Files
lmcp/versions.lua
T
marfrit 4ac9296f08 lmcp: dual protocol 2025-06-18 + 2026-07-28, and envelope checking
The result of the bullpen campaign, carried out by @deus as operator with
@coder on versions.lua (job 1158). Both conformance suites green, attested with
0 open points; the record is on branch kampagne/2026-07-28 in this repo.

What it does:
  versions.lua  M.SUPPORTED gains 2026-07-28, old version first so an existing
                client keeps negotiating what it always did.
  lmcp.lua      requires envelope; routes on req._legacy, so initialize, ping
                and logging/setLevel keep answering the old shape while the new
                protocol gets the envelope path. resultType defaults to
                "complete" on every result.
  envelope.lua  new -- header and body checks, returns the -32020 family.

104 of the 180 added lines in lmcp.lua are comments: the campaign also carried
the German-to-English translation, so the diff looks larger than the behaviour
change is.

NOT yet on master. lmcp runs on around ten hosts in this fleet; a green suite
says the spec is satisfiable, not that it is complete or true. This branch
exists so a second pair of eyes reads it before it reaches any of them.

The copy fix in versions.lua (err.data.supported handed out as a COPY, so a
caller cannot hijack the module table) predates this work -- it was the
uncommitted German state, archived on wip/lmcp-deutsch-2026-08-09.
2026-08-11 00:08:32 +02:00

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