4ac9296f08
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.
54 lines
1.9 KiB
Lua
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
|