Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 9 additions & 11 deletions .github/workflows/gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,16 @@ jobs:
runs-on: ubuntu-latest
needs: [gate, pr-body]
if: always()
# The edited path may wait up to 20 minutes for running legs (tools/ci_legs_verdict.sh's
# bound); the job's own ceiling sits just above it so a hang is a FAIL, not a stall.
timeout-minutes: 25
# The edited path reads the legs' check runs for the head SHA: a read, granted here and
# nowhere wider.
permissions:
contents: read
checks: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: every leg of the matrix passed, and the pull-request body where there is one
env:
GH_TOKEN: ${{ github.token }}
Expand All @@ -126,18 +130,12 @@ jobs:
# the REQUIRED context, and a fresh green under that name on a commit whose legs
# this run never read would let a title edit stand a green beside a red (a review
# lane's finding). So the legs' verdicts for this head SHA are read from the checks
# API -- the latest run of each leg name -- and all three must be success.
# API -- the latest run of each leg name -- and all three must be success. The
# read WAITS for legs still running (G-079: an edit during the synchronize run read
# null conclusions, failed, and the ruleset blocked on that red beside the later
# green); tools/ci_legs_verdict.sh polls to a bound, and its probe runs offline.
[ "${{ needs.pr-body.result }}" = "success" ] || exit 1
# The expanded leg names only: the skipped matrix job of THIS run registers one
# unexpanded name ("Gate - leg (OTP ${{ '{{' }} matrix.otp ...", skipped), which is
# not a verdict (the first edited run measured exactly that).
legs=$(gh api "repos/${GITHUB_REPOSITORY}/commits/${HEAD_SHA}/check-runs?filter=latest&per_page=100" \
--jq '.check_runs[] | select(.name | test("^Gate - leg \\(OTP [0-9]+, Elixir [0-9.]+, [a-z]+\\)$")) | "\(.name): \(.conclusion)"')
printf '%s\n' "$legs"
n=$(printf '%s\n' "$legs" | grep -c . || true)
bad=$(printf '%s\n' "$legs" | grep -vc ': success$' || true)
[ "$n" -ge 3 ] || { echo "FAIL: only $n leg verdict(s) exist for ${HEAD_SHA}; the tree is unmeasured"; exit 1; }
[ "$bad" -eq 0 ] || { echo "FAIL: a leg's latest verdict for ${HEAD_SHA} is not success"; exit 1; }
bash tools/ci_legs_verdict.sh "${GITHUB_REPOSITORY}" "${HEAD_SHA}"
else
[ "${{ needs.gate.result }}" = "success" ]
case "${{ needs.pr-body.result }}" in success|skipped) ;; *) exit 1 ;; esac
Expand Down
36 changes: 36 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,42 @@ All notable changes to this project are documented here. The format follows

## [Unreleased]

## [0.8.0] — 2026-09-19

The quiet minor: **no public entry added, removed, renamed, hidden or changed in arity** —
`docs/public-api.txt` is line for line `0.7.0`'s, `release_markers!("0.8.0")` wrote nothing,
and the census holds an empty Unreleased section to that. No wire byte and no envelope byte
moves. Instruments and pages only, below. This is the "full minor release unchanged" the
README names as `1.0.0`'s condition for the public API; `1.0.0` follows once it has stood.

### Changed — instruments (no public entry moves)

- **The gate diffs `docs/public-api.txt` against `origin/main`** (a sixteenth step,
`baseline`; `tools/baseline_diff.sh`, probed by `tools/probe_baseline_diff.sh`): a public
entry's line deleted by hand, or a `since=`/`deprecated_since=`/`removed_in=` marker arriving
with a release number not above the CHANGELOG's highest heading — one it lists, or a phantom
between two releases — is a FAIL. The census reads the tree
alone and passed both edits green (a review lane measured it); this step reads git. Where
`origin/main` does not resolve the line says so and is not evidence.
- **The pull-request summary waits for running legs** on a body edit (`tools/ci_legs_verdict.sh`,
polling the head SHA's leg check runs until each is completed, bounded at 20 minutes, a
failed API read a poll spent rather than a verdict, probed offline by
`tools/probe_ci_legs_verdict.sh` with a scripted `gh`): an edit during a synchronize run no
longer fails on null conclusions and blocks the merge beside a later green.
- `tools/probe_gate_honesty.sh`'s "other steps not pass" count reads `pass (<detail>)` as pass
(it counted every detailed line: 7 of its 13 names on a green gate, 10 of the 16 the gate
prints now; 0 now).

### Changed — pages (copy)

- `docs/governance.md`'s Scorecard table carries the check's own figure beside each row
(read 2026-09-19; aggregate 7), with the rule behind each low one — Maintained is 0 for any
repository under 90 days old; Signed-Releases and Packaging read GitHub Releases and a
publishing workflow, neither of which a Hex release has — and two rows the first result
added (Packaging, CII-Best-Practices).
- `docs/provenance.md`'s example commands name `0.7.0`; `docs/connectome-canonical.md`'s
`schema_version` history no longer says `0.6.0` "carries this note".

## [0.7.0] — 2026-09-19

The signer seam, and nothing else: three public entries added — `BeamMCP.Signer` (the
Expand Down
54 changes: 32 additions & 22 deletions HANDOFF.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ SPDX-FileCopyrightText: 2026 Sudo Apt Holdings LLC
SPDX-License-Identifier: Apache-2.0
-->

# HANDOFF — beam_mcp, release 0.7.0 prepared; publish and tag are the owner's
# HANDOFF — beam_mcp, release 0.8.0 prepared; publish and tag are the owner's

Tag and publish are owner steps — never `mix hex.publish`, never push a tag, never bump the
version in `mix.exs`. For 0.7.0 the version bump is this release commit, reviewed like any
version in `mix.exs`. For 0.8.0 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
Expand All @@ -15,7 +15,16 @@ happened.

## State

- **`0.7.0` carries the signer seam and nothing else**: `BeamMCP.Signer` (one callback,
- **`0.8.0` is the quiet minor**: no public entry added, removed, renamed, hidden or changed
in arity — `docs/public-api.txt` is `0.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 against `origin/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.exs` says `0.8.0`; the README
recommends `~> 0.8.0` and the requirement test refuses `0.7.0` and `0.6.0`; the wire
recording's ten version lines are re-taken. `1.0.0` is next, after this minor has stood.
- **`0.7.0` carried the signer seam and nothing else**: `BeamMCP.Signer` (one callback,
`sign/2`, two arguments with pinned names), `BeamMCP.Signer.None` (the one no-op under
`lib/`) and `BeamMCP.Connectome.Canonical.signature/3` (the one call site, over `encode/2`'s
bytes, moving no envelope byte). Three public entries added, none removed, renamed or
Expand All @@ -24,10 +33,9 @@ happened.
callback list, the one `def 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
under `opts[:private_key]`), is the separate package `beam_mcp_signer`
(github.com/ScriptKittyOS/beam_mcp_signer, unpublished until this release is on hex.pm);
this package does not depend on it. `mix.exs` says `0.7.0`; the README recommends
`~> 0.7.0` and the requirement test refuses `0.6.0` and `0.5.0`; the wire recording's ten
version lines are re-taken.
(github.com/ScriptKittyOS/beam_mcp_signer, 0.1.0 on hex.pm, depending on `~> 0.7.0` — so a
host on it cannot take `0.8.0` until a signer release admits it; `UPGRADING.md` says so);
this package does not depend on it.
- **`0.6.0` carried everything since `0.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
Expand All @@ -42,18 +50,20 @@ happened.
`docs/public-api.txt` carried no `Unreleased` marker, so `release_markers!("0.6.0")` wrote
nothing; at `0.7.0` it 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.6.0..main` is the authority.
- Gate on the release commit: fifteen steps, every line `pass` (the 0.5.0 gate had thirteen;
the audit step made it fourteen in 024 and Dialyzer fifteen in 027a) — format (the tracked set, not a
glob), compile, instruments, test, credo, properties (11 at 1 000 generations), optional
deps, bench (the collector's overhead under the 1.5 µs ceiling; **the diff engine's run and
the commit that writes it. `git log v0.7.0..main` is 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, messages.
**11 properties, 705 tests, 0 failures** on the release tree (692 at 0.6.0, 604 at 0.5.0;
the differences are the slices' own pins — 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
on the fixture fails the step by name), docs, reuse, licence files, publication, baseline,
messages.
**11 properties, 705 tests, 0 failures** on the release tree (705 at 0.7.0 — 0.8.0 adds no
test, 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.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
Expand All @@ -63,10 +73,10 @@ happened.

## What is next

The road, as `UPGRADING.md` states it and the owner locked it: **`0.7.0`** this release, the
signer seam (`BeamMCP.Signer`, a behaviour added to the public surface and no authority — the
last intentional addition), **`0.8.0`** a quiet minor in which no public entry moves,
**`1.0.0`** after it has stood — the README's condition, that
The road, as `UPGRADING.md` states it and the owner locked it: **`0.7.0`** the signer seam
(`BeamMCP.Signer`, a behaviour added to the public surface and no authority — the last
intentional addition), **`0.8.0`** this release, the quiet minor in which no public entry
moves, **`1.0.0`** after it has stood — the README's condition, that
the public API and the stated threat model have each survived a full minor release unchanged.
The federation seam stays held on another board's answer and is not on that road. A compiler-tracer census
(module-body code run at compile time, which neither the text censuses nor `:xref` see) is
Expand Down Expand Up @@ -112,11 +122,11 @@ refused.
- The `2025-11-25` revision 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 steps — the runbook (followed for 0.6.0; the same for 0.7.0)
## The release steps — the runbook (followed for 0.6.0 and 0.7.0; the same for 0.8.0)

1. The release PR merged to `main` by rebase (the ruleset requires two green checks); `main`
is then the release commit.
2. The gate on that commit, fifteen `pass`, output recorded by command and exit code (the
2. The gate on that commit, sixteen `pass`, output recorded by command and exit code (the
release PR's own gate run is that record).
3. Tag the release commit **as it sits on `main` after the rebase-merge** (a new SHA; the
bytes are a function of the tree, measured) locally, signed (`git tag -s vX.Y.Z`); then
Expand Down
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ and refuses one that is not; what a tool *does* is the host's business.

```elixir
def deps do
[{:beam_mcp, "~> 0.7.0"}]
[{:beam_mcp, "~> 0.8.0"}]
end
```

**`~> 0.7.0`, not the more usual `~> 0.7`.** While this package is `0.x` it documents breaks
**`~> 0.8.0`, not the more usual `~> 0.8`.** While this package is `0.x` it documents breaks
at the **minor** position, and it has used that position five times: `0.2.0` removed two
fields from results for legacy-declared requests, `0.3.0` added the HTTP transport and the
`ttlMs`/`cacheScope` fields `2026-07-28` requires on `tools/list`, `0.4.0` replaced the
Expand All @@ -29,8 +29,9 @@ writes and moves `schema_version` to `2` (in the exported bytes), and requires a
`resources` and `prompts` lists to hold the package's structs (the host contract), and `0.6.0`
names the canonical envelope's algorithm in its bytes and moves `schema_version` to `3` (in the
exported bytes), each with a how-to-tell sentence in the changelog (`0.7.0` is an addition at
the minor, the signer seam, not a break). `~> 0.7` admits `0.8.0`, so it
would carry you across the next such break on a routine `mix deps.update`; `~> 0.7.0` does not. The tighter form is deliberate and is not an over-pin to be tidied away. What the pin
the minor, the signer seam, and `0.8.0` a quiet minor in which no public entry moved; neither
a break). `~> 0.8` would admit a `0.9.0`, so it
would carry you across the next such break on a routine `mix deps.update`; `~> 0.8.0` does not. The tighter form is deliberate and is not an over-pin to be tidied away. What the pin
buys is written down: [`docs/api-stability.md`](docs/api-stability.md) says what is public
(what ex_doc lists, `docs/public-api.txt` line by line), how a deprecation runs (three steps,
three minors), and what a `0.x` break must say; a census holds the surface to that record.
Expand Down Expand Up @@ -528,7 +529,7 @@ has no stdio server mode). `conformance/README.md` has the rest.
**Reproduce it:** `tools/conformance.sh` — one command, for anyone with Node ≥ 22 and
`python3`. **The trade, stated:** this package has two dependencies; producing this number
costs a second toolchain, so the step runs in a CI job of its own (`conformance.yml`, Node 22
pinned) and never in the local gate, which stays the fifteen steps a contributor with Elixir
pinned) and never in the local gate, which stays the sixteen steps a contributor with Elixir
and Erlang runs green with nothing else installed (Dialyzer among them, on the OTP binary — its
PLT is built once per machine, about a minute, and kept under `_build`). The gate needs hex.pm (two steps resolve
dependencies); of those, the dependency audit is the one that refuses an answer hex gives
Expand Down
3 changes: 2 additions & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ entry says so when it does.

| version | supported |
|---|---|
| `0.7.x` | yes |
| `0.8.x` | yes |
| `0.7.x` | no — superseded |
| `0.6.x` | no — superseded |
| `0.5.x` | no — superseded |
| `0.4.x` | no — superseded |
Expand Down
28 changes: 20 additions & 8 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ says **BREAKING**, which the census requires.

## The rule for `0.x`

Breaks land at the **minor** position and nowhere else. Pin `~> 0.7.0` (the current minor,
three numbers), not `~> 0.7`: the tighter pin stops at the next minor, which is where the
Breaks land at the **minor** position and nowhere else. Pin `~> 0.8.0` (the current minor,
three numbers), not `~> 0.8`: the tighter pin stops at the next minor, which is where the
next documented break can be, so a routine `mix deps.update` never carries you across one.
To move a minor: read the release's rows below and their CHANGELOG entries, apply each "how
to tell" sentence to your host, then raise the pin.
Expand All @@ -41,18 +41,30 @@ one that does adds the separate package `beam_mcp_signer` and passes its module
`signature/3`. Raise the pin to `~> 0.7.0` when you take it; `~> 0.6.0` stops before it by the
rule, not because anything moved.

**`0.8.0` has no row either: it is the quiet minor.** No public entry was added, removed,
renamed, hidden or changed in arity — `docs/public-api.txt` is line for line `0.7.0`'s, and the
release step wrote nothing into it — and no wire byte or envelope byte moved. What changed is
instruments (the gate diffs the baseline against `origin/main`; the pull-request summary waits
for running legs) and pages (the Scorecard's measured figures on the governance page). Raise
the pin to `~> 0.8.0`. **A host on `beam_mcp_signer` waits for that package's release that
admits `0.8.0`**: its `0.1.0` requires `beam_mcp ~> 0.7.0` (three numbers, by the same rule as
this page's pin), so `{:beam_mcp, "~> 0.8.0"}` beside `{:beam_mcp_signer, "~> 0.1.0"}` does not
resolve until it does. That is the cost of the three-number pin, paid once per minor, on the
signer's side.

## The road to `1.0.0`, in order

Stated here so nobody infers it from a plan's label or a folder's name:

1. **`0.6.0`** — everything since `0.5.0`, the assessability snapshot. One documented break at
the minor (the exported bytes).
2. **`0.7.0`** — this release: the signer seam, `BeamMCP.Signer` (a behaviour added to the
public surface; no authority passes through it), the last intentional addition before
`1.0.0`. An addition, not a break; `~> 0.6.0` stops at it all the same, by the rule.
3. **`0.8.0`** — a quiet minor: documentation, the Scorecard's rows, instrument leftovers. No
public entry added, removed, renamed or hidden — the "full minor release unchanged" the
README's `1.0.0` condition requires, measured by `docs/public-api.txt` not moving.
2. **`0.7.0`** — the signer seam, `BeamMCP.Signer` (a behaviour added to the public
surface; no authority passes through it), the last intentional addition before `1.0.0`.
An addition, not a break; `~> 0.6.0` stops at it all the same, by the rule.
3. **`0.8.0`** — this release: the quiet minor. Documentation, the Scorecard's rows,
instrument leftovers. No public entry added, removed, renamed or hidden — the "full minor
release unchanged" the README's `1.0.0` condition requires, measured by
`docs/public-api.txt` not moving (the gate now diffs it against `origin/main` on every run).
4. **`1.0.0`** — after `0.8.0` has stood: the surface frozen as `docs/api-stability.md` says.

## What `1.0` will ask
Expand Down
Loading
Loading