Skip to content

Serve a request by the revision its _meta declares, not by the carrier - #7

Merged
HackTuah merged 1 commit into
mainfrom
slice/001b-ping-guard
Sep 7, 2026
Merged

Serve a request by the revision its _meta declares, not by the carrier#7
HackTuah merged 1 commit into
mainfrom
slice/001b-ping-guard

Conversation

@HackTuah

@HackTuah HackTuah commented Sep 7, 2026

Copy link
Copy Markdown
Member

Closes the ping defect live in published 0.1.0: the _meta clause refused ping with -32601 for every revision reaching it, including 2025-11-25, where ping exists. Its own comment named 2026-07-28; the code named no revision.

Red first

$ mix test test/beam_mcp/negotiation_test.exs
12 tests, 2 failures
exit=2

Nine pre-existing tests passed. Full output at slices/001b-ping-guard/logs/red.txt, written by the command.

Two halves of one defect

The same version-blind cond also stamped resultType and modern _meta serverInfo onto results answering 2025-11-25 requests — two fields 2026-07-28 introduced and 2025-11-25 does not define. Fixing only ping would have left the clause version-blind.

The clause now branches on the declared version. 2026-07-28 refuses ping and modernises; 2025-11-25 answers ping and does not; anything else is -32022.

Decided against the fetched specification rather than the issue text. Refusing a _meta naming 2025-11-25 would contradict this server's own advertisement — it returns that revision from server/discover and lists it in the -32022 supported payload, and the spec tells a client receiving -32022 to select from that list and retry the request, which produces exactly this message. The three specification pages quoted are archived under logs/, fetched by curl, and the sweep re-fetches and diffs them against upstream.

Verification

./tools/gate.sh    format / compile / test / credo  pass
                   reuse  pass (19 commentable files)
                   licence files  pass
                   Gate OK.  exit=0
mix test           39 tests, 0 failures

Three tests added for state threading through the new branches; two are mutation-killed with raw captures at logs/mutation-a.txt and logs/mutation-b.txt, the third guards a write that does not exist and is not scored — stated rather than claimed.

Review

Six rounds, two independent lanes, each on a git archive checkout of the index; both lanes printed the tree hash they read and both matched git write-tree. Twelve reports, each written by its own lane, are in logs/. Both approved tree d9b0c01e.

lib/ and test/ have not changed since round 2 — both lanes cleared the substance early, and every round after was evidence integrity. What that caught, three times running, was an archive whose bytes were not its command's, where each fix introduced the next instance. It stopped when the population was derived rather than the instance patched; tools/archive_sweep.sh now classifies every file and exits 1 if the population and the classifications disagree, scored by mutation rather than asserted.

Release note — needs a decision before publish

This removes resultType and _meta serverInfo from results for every method on the legacy-declared path, not just ping. A 0.1.0 client reading result.resultType there gets nil after what is numbered a patch. The removal is labelled under its own ### Changed heading; whether 0.2.0 is the honester number is left open deliberately. 0.1.1 reached main and was never published — its documentation fix ships inside 0.1.2.

Known, filed rather than folded in

tools/list omits ttlMs/cacheScope and server/discover carries no resultType, both required by 2026-07-28 (SCR-261). Two gaps in tools/gate.sh's own honesty (SCR-262). A conditional defect in the sweep's exit status, harmless today and blocking the moment it is wired into CI, is filed separately.

ping was refused with -32601 whenever a request carried per-request _meta
naming any revision, including 2025-11-25, where ping exists. The clause's
own comment named 2026-07-28; the code named no revision.

Red first, before any edit to lib/:

    $ mix test test/beam_mcp/negotiation_test.exs
    12 tests, 2 failures
    exit=2

Nine pre-existing tests passed. Full output in logs/red.txt, written by the
command. Measured against the pre-fix tree:

    ping + _meta 2026-07-28  -> {"error":{"code":-32601,...}}
    ping + _meta 2025-11-25  -> {"error":{"code":-32601,...}}   <- the defect
    ping + _meta, no version -> {"result":{}}                    <- the boundary
    ping bare                -> {"result":{}}

The same cond modernised every result on the same version-blind basis, so a
request declaring 2025-11-25 was answered with resultType and modern _meta
serverInfo -- two fields 2026-07-28 introduced and 2025-11-25 does not
define. Same root cause, so fixed together; fixing only ping would have left
the clause version-blind.

The clause now branches on the declared version: 2026-07-28 refuses ping and
modernises, 2025-11-25 answers ping and does not, anything else is -32022.

Decided against the specification rather than the symptom. Two alternatives
were rejected and the argument is in the slice PLAN. Refusing a _meta that
names 2025-11-25 would contradict this server's own advertisement -- it
returns that revision from server/discover and lists it in the -32022
supported payload, and the spec tells a client receiving -32022 to select
from that list and retry, which produces exactly this message. Fixing only
the ping guard would have kept an envelope announcing a revision the client
did not ask for. The three specification pages quoted are archived under
logs/, fetched by curl rather than recalled.

Coverage: every test in the file discarded the returned state, so the new
era branches were untested for state threading. Three tests added; two are
mutation-killed (logs/mutation-a.txt, logs/mutation-b.txt, raw captures),
the third guards a write that does not exist and is not scored.

    $ ./tools/gate.sh
      format / compile / test / credo   pass
      reuse                             pass (19 commentable files)
      licence files                     pass
    Gate OK.   exit=0
    $ mix test
    39 tests, 0 failures

Version 0.1.2. NOTE FOR RELEASE: this removes resultType and _meta
serverInfo from results for every method on the legacy-declared path, not
just ping. A 0.1.0 client reading result.resultType there gets nil after
what is numbered a patch. The removal is labelled under its own Changed
heading in the changelog, and whether 0.2.0 is the honester number is left
to the release decision. 0.1.1 reached main and was never published; its
documentation fix ships inside 0.1.2.

Reviewed over six rounds by two independent lanes on checkouts of the index.
Both approved tree d9b0c01e. This commit adds their two round-6 reports and
one correction they named: a tally sentence in FINDINGS.md that asserted a
count contradicted by the tree's own reports, deleted rather than retyped,
inside the paragraph arguing a typed count is indistinguishable from a
derived one.

Signed-off-by: Ayla Croft <aylacroft@proton.me>
@HackTuah
HackTuah merged commit 0332c41 into main Sep 7, 2026
4 checks passed
HackTuah added a commit that referenced this pull request Sep 7, 2026
Owner decision. The fix on main is numbered 0.1.2; this makes it 0.2.0.

For a client declaring 2025-11-25 a method that was refused now answers
(ping), and results on that path have lost two fields -- resultType and
_meta serverInfo. A 0.1.0 consumer could have depended on either. For a
published package the wire JSON is the API, so that is a minor bump.

The field removal is the breaking-shaped half and carries the decision on
its own. An earlier statement of the rationale had the ping direction
backwards -- it said a method that answered now refuses, which is true of
no method in this release. A reviewer caught it by enumerating every
method on this tree and on 0.1.0's rather than by reading the rationale:

    ping         0.1.0: REFUSED -32601      0.2.0: answered
    every other method: unchanged status on both

That sentence was one of the two grounds given for the number and reached
the changelog unchecked. The changelog now names the error rather than
quietly rewording it.

Also in this release, because a lane found the README recommending a
requirement that spans the break the version exists to signal:

CONVENTIONS.md gains a standing rule -- the README states what the package
does today, and every behavioural claim in it is pinned by a test that
runs. test/beam_mcp/readme_claims_test.exs discharges it. Each test quotes
the README sentence it pins and asserts that sentence is still present, so
a claim that moves without its test fails, and a test guarding a deleted
claim fails too rather than reading as coverage. Three claims previously
had nothing holding them, including that every method is served bare with
tools/call reaching dispatch -- a security property that rested on prose.

Scored by mutation. Reverting the README dependency fails the claim quote;
inserting a session guard fails the two behavioural tests:

    MUTANT A  8 tests, 1 failure   REAL_EXIT=2
    MUTANT B  8 tests, 2 failures  REAL_EXIT=2

Mutant B's source check reported old-remaining 1 because the replacement
clause embeds the original line, so that count carried no information;
what proved it applied was the effect. Assert the effect, not only the
match count.

    ./tools/gate.sh    format / compile / test / credo  pass
                       reuse  pass (20 commentable files)
                       licence files  pass
    Gate OK.  exit=0
    mix test           47 tests, 0 failures

Base re-derived rather than assumed: PR #7 merged by rebase, so main
carries a rewritten commit and the local one was not its ancestor. Trees
compared identical (a866ad8 both, empty diff) before resetting onto main.

Reviewed on a checkout of the index by two independent lanes over rounds 7
and 8; both approved tree 072cf981. The committed tree is that tree plus
the two round-8 lane reports and nothing else.

Six non-blocking items both lanes raised are filed rather than folded in,
including whether the README should recommend ~> 0.2.0 rather than ~> 0.2
-- ~> 0.2 admits 0.3.0 and so spans the next break the way ~> 0.1 spanned
this one. That is a maintainer's call and is not made here.

Signed-off-by: Ayla Croft <aylacroft@proton.me>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant