Tag and publish are owner steps: never mix hex.publish, never push a tag, never bump the
version in mix.exs. For 0.10.1 the version bump is this release commit, reviewed like any
other change; publishing and tagging remain the owner's, in the order the runbook below gives.
The slice records (plans, findings, lane reports, signoffs, archived gate runs) live in the project's internal tree, not in this repository. Nothing here summarises a review that has not happened.
0.10.1is a security patch on0.10.0: the project's own security review (three independent lanes, every finding reproduced first) found JSON booleans andnullreaching dispatch as strings, the advertised schema enforced at the top level only, stdio fault reports on standard output, host terms in client-facing-32603messages, raw stacktraces in the HTTP transport's logs, the cursor outside the JSON reader, and non-stringmethodor toolnameraising in the core. Each is fixed with a test seen failing on0.10.0first (test/beam_mcp/arguments_schema_and_faults_test.exs); the pages that claimed more than the code are corrected. No public entry moved; two behaviours change (booleans preserved, an unsupported schema keyword refused at startup), each in the CHANGELOG with how to tell whether a host is affected. Handled privately underSECURITY.md: the advisories are published after the release.beam_mcp_signer0.2.1ships beside it. A patch, not0.11.0, bySECURITY.md's own table (Critical: "a patch release of the supported minor carrying only the fix"; the package bytes since0.10.0are the fixes and comments), and because the README's~> 0.10.0pin admits0.10.1on a plainmix deps.updatewhere it would stop short of a0.11.0. Go's and Rust's compatibility promises keep the same exception for security fixes. After publishing, retire the affected releases so everymix deps.getnames the fix:mix hex.retire beam_mcp 0.10.0 security --message "Security fixes in 0.10.1; see its CHANGELOG entry"(and the same for earlier supported versions the advisory lists), andmix hex.retire beam_mcp_signer 0.2.0 security --message "Key could be printed; fixed in 0.2.1".0.10.0is the quiet minor again: no public entry added, removed, renamed, hidden or changed in arity (docs/public-api.txtdid not move;release_markers!("0.10.0")wrote nothing), no wire or envelope byte (the recording's ten version lines re-taken and nothing else; every canonical golden the same blob as at0.9.0). Instruments: the suite in FIPS mode in CI (.github/workflows/fips.yml: OTP 28.1.1--enable-fipsover OpenSSL 3.5.8 with the 3.1.2 FIPS provider, CMVP #4985; 11 properties, 729 tests, 0 failures in FIPS mode, measured locally on that toolchain and in CI), and an attested CycloneDX SBOM at every release (tools/sbom.sh, the EEF'smix_sbompinned by digest, outsidemix.exs; bound to the tarball's digest by a second attestation that the workflow downloads back and compares). Pages: the OpenSSF Best Practices badge's (architecture, assurance case, roadmap, code of conduct, code review, security-review procedure, continuity),docs/fips.mdmeasured and corrected (a missing provider fails at the first:cryptocall, not at boot), the supply-chain row's status, README's "Scheduled" after1.0.0, and no em dash left outsideslices/.mix.exssays0.10.0; the README recommends~> 0.10.0and the requirement test refuses0.9.0,0.8.0,0.7.0and0.6.0.1.0.0is next, after this minor has stood.0.9.0carries two additions and no break, and ends the stand at0.8.0: the:serverseam (a module option onBeamMCP.Transport.HTTP's Plug andBeamMCP.Transport.Stdio.run/1, defaultBeamMCP.Server, the transports reachingnew/1,handle_message/2and (stdio)shutdown?/1through it and never by name,BeamMCP.Servernow the behaviour a wrapper implements,shutdown?/1optional since HTTP never asks it), andscheme:andkey_id:beside the signatureBeamMCP.Connectome.Canonical.signature/3returns, copied from the host's options and verified by nothing here, the canonical bytes unmoved. Four public entries added, eachsince=0.9.0; none removed, renamed, hidden or changed in arity; the0.8.0wire recording re-taken with its ten version lines moving and nothing else; every canonical golden the same blob as at0.8.0. Censuses: a new one (the transports never name the core: three readings over the whole oflib/), two moved (the variable-module call list, the behaviour list), one vocabulary section (Scheme). Will-not-implement entry 12 is unchanged, its two tests untouched: the seam carries no state and decides no authority.mix.exssays0.9.0; the README recommends~> 0.9.0and the requirement test refuses0.8.0,0.7.0and0.6.0.0.10.0is next, the quiet minor (a FIPS leg, the SBOM at release: instruments and pages, no public entry), then1.0.0after it has stood.0.8.0was the quiet minor: no public entry added, removed, renamed, hidden or changed in arity (docs/public-api.txtis0.7.0's line for line,release_markers!("0.8.0")wrote nothing), and no wire or envelope byte moved. Instruments: the gate's sixteenth step diffs the baseline againstorigin/main(G-076); the pull-request summary waits for running legs on a body edit (G-079); the honesty probe's discriminator reads detailed pass lines (G-078); each with an offline probe. Pages: the governance table carries the Scorecard's measured figures with the rule behind each low one.mix.exssays0.8.0; the README recommends~> 0.8.0and the requirement test refuses0.7.0and0.6.0; the wire recording's ten version lines are re-taken.1.0.0is next, after this minor has stood.0.7.0carried the signer seam and nothing else:BeamMCP.Signer(one callback,sign/2, two arguments with pinned names),BeamMCP.Signer.None(the one no-op underlib/) andBeamMCP.Connectome.Canonical.signature/3(the one call site, overencode/2's bytes, moving no envelope byte). Three public entries added, none removed, renamed or hidden;docs/public-api.txtmarks themsince=0.7.0(the release step wrote three). The no-signature census pins the seam (the callback's whole spec, each behaviour's exact callback list, the onedef sign, the one call site), and sixteen mutants hold it. The signer that holds a key,BeamMCP.Signer.Ed25519(Ed25519 through OTP's:crypto, the key underopts[:private_key]), is the separate packagebeam_mcp_signer(github.com/ScriptKittyOS/beam_mcp_signer, 0.1.0 on hex.pm, depending on~> 0.7.0, so a host on it cannot take0.8.0until a signer release admits it;UPGRADING.mdsays so; and0.1.1, released 2026-09-19 with~> 0.7, is that release: it resolves beside0.9.0); this package does not depend on it.0.6.0carried everything since0.5.0: the wire hardening after the threat model, the threat-model page, the hash-agile canonical envelope (the release's one break), the install-floor slices (the OTP floor at compile time, the CI matrix on three pairs, the dependency audit, build provenance, the security policy, the instruments and Dialyzer, the tracer in its own trace session, the API-stability policy withdocs/public-api.txtpinned by a census) and, after them, governance and succession, the export-control statement and REUSE compliance by the specification's tool. One break at the minor, in the exported bytes (the canonical envelope's algorithm member andschema_version3), with its how-to-tell sentence. The road from here is written inUPGRADING.md(0.7.0the signer seam,0.8.0a quiet minor,1.0.0after it), so nobody reads it off a plan's label.- At
0.6.0the README recommended~> 0.6.0(a fifth use of the minor position) anddocs/public-api.txtcarried noUnreleasedmarker, sorelease_markers!("0.6.0")wrote nothing; at0.7.0it wrote three. - No head hash is written here: a hash written into the file it describes cannot include
the commit that writes it.
git log v0.9.0..mainis the authority. - Gate on the release commit: sixteen steps, every line
pass(the 0.5.0 gate had thirteen; the audit step made it fourteen in 024, Dialyzer fifteen in 027a, the baseline diff sixteen at 0.8.0): format (the tracked set, not a glob), compile, dialyzer, instruments, test, credo, properties (11 at 1 000 generations), optional deps, audit, bench (the collector's overhead under the 1.5 µs ceiling; the diff engine's run and encode each under its own ceiling now: 245 ms and 260 ms, medians of five in a fresh process after a warm-up, set at roughly double the stable worst of ten runs on the release head; the reachability queries' cost recorded and judged by no number, but a query refused on the fixture fails the step by name), docs, reuse, licence files, publication, baseline, messages. 11 properties, 729 tests, 0 failures on the release tree (729 at 0.9.0 as well: 0.10.0's instruments are workflows, measured by their CI runs, and add no test; 705 at 0.8.0 and 0.7.0, since 0.8.0 added no test and its instruments are probed by shell; 692 at 0.6.0, 604 at 0.5.0; the differences are the slices' own pins: at 0.9.0 the server seam's census and by-effect tests and the scheme's tests; at 0.7.0 the signer seam's census and behaviour tests; before it the floor, the provenance and security-policy pins, the tracer's session suite, the public-API census on the tree and on fixtures, the governance and export-control censuses). - The README's four-way split is held by census to the modules compiled from
lib/(the beams whose source is underlib/, not the test build's.app, which also liststest/support): every module named under shipping now is among them, every module named under the other three paragraphs is not.
1.0.0, once this minor has stood: the README's condition, that the public API and the
stated threat model have each survived a full minor release unchanged. 0.10.0 moved no public
entry, and its one threat-model edit corrects a status (provenance and the SBOM shipped), not
what the model defends. The freeze is docs/public-api.txt as it stands; docs/api-stability.md's
1.x rules take effect. After it, as additions at the minor: the federation seam (held on
another board's answer), effective connectivity, the Tasks extension. A compiler-tracer census
(module-body code run at compile time) is scheduled with its lift measured. Sign-aware
reachability, if asked for, is a later slice or a refusal decided in the open; all_paths
stays refused. Outside the package: mix_sbom's reading of tools: :optional (the named
workaround in tools/sbom.sh goes when an upstream release reads it).
- Subscriptions.
resources/subscribeis not in2026-07-28(subscriptions/listenreplaced it, a long-lived stream the stateless HTTP transport cannot hold); the capability is advertised withsubscribe: false. Whether to buildsubscriptions/listenon stdio, the legacy pair on the legacy era only, or neither and say so on the will-not-implement page. - A resource template in the declared connectome. The builder reads a
uriper entry; a template has auri_templateand is enumerated as unreadable: true and unflattering. Whether a template is a node, and of what kind. tools/listpagination. The cursor exists and both resource lists andprompts/listuse it; adopting it ontools/listchanges an existing result and waits for the word.- The diff encode's cost follows the caller's heap. Measured while rebuilding the benchmark: 115–126 ms in a fresh process, 133–151 ms after one encode in the same process, 240–252 ms in a process holding six earlier records. The gate's figure is the fresh one and says so; whether a long-lived caller should encode in a spawned process is unmeasured on a real host.
- The Livebook is not opened from hexdocs, as before: a copy fetched by URL has no
exports/beside it.
- An Elixir script under
tools/is parsed by the format step and run by nobody; a call into a module the package renamed is caught only when the script is next run by hand. - The censuses that read the built beams (
BeamMCP.Boundary.lib_modules/0: the split census, the package-reach census) read what the last compile left in the ebin; a beam outside Mix's manifest whose source sayslib/is counted until it is deleted. Not measured; stated on the function. - The collector is node-wide: every dispatch on the node lands in its table, whatever the server. The Livebook's observed export is restricted to its server by id for that reason.
readme_claims_test.exspins the claims listed in it and derives no claim set; the docs census closes a neighbouring gap (names that do not exist), and the split census another (a module named in the wrong paragraph), not that one.- The 1.5 µs ceiling's margin on this machine: the worst measured since it was set is
+0.878 µs/call, with the baseline alone swinging 1.4 µs between runs; the owner read the
numbers and kept the ceiling.
bench/overhead.exssays so beside the constant. - The
2025-11-25revision is served on stdio only; over HTTP the conformance row for it is 0 / 30 by design, and the README says so beside the number.
- The release PR merged to
mainby rebase (the ruleset requires two green checks);mainis then the release commit. - The gate on that commit, sixteen
pass, output recorded by command and exit code (the release PR's own gate run is that record). - Tag the release commit as it sits on
mainafter the rebase-merge (a new SHA; the bytes are a function of the tree, measured) locally, signed (git tag -s vX.Y.Z); thentools/release_tarball.sh vX.Y.Z beam_mcp-X.Y.Z.tar --publish(the script builds the canonical tarball fromgit archiveof that tag and publishes from that tree; a working-treemix hex.publishships that machine's file modes and is not what the provenance workflow attests); then push the tag. The tag's run downloads what hex.pm serves and verifies the attestation against it, and treats a version hex.pm does not serve yet as a failure, so the push comes last. (A tag and its commit build the same bytes; measured.) The GitHub ruleset targets branches, not tags, so a tag push is unprotected: what is tagged is what was read.