Skip to content

Release 0.81.0 — the door an author could not find - #24

Merged
JimmyWesley merged 3 commits into
mainfrom
develop
Sep 13, 2026
Merged

JimmyWesley merged 3 commits into
mainfrom
develop

Conversation

@JimmyWesley

Copy link
Copy Markdown
Owner

Release 0.81.0 — the door an author could not find

One spec version, v0.81, and the package moves 0.80.0 → 0.81.0. It comes
from the shape of the release before it: 0.80.0 shipped the extension
mechanism and left the person it was built for with no way in.
docs/extending.md still cited spec v0.20, so the one document naming
extensions contradicted what had just been published; nothing anywhere
taught how to write one; and the console's install box asked for a path.
A path is on the host — through a browser that is the container's
filesystem, so an operator holding an extension they had just written had
no route to a remote Station short of publishing it to git first. The
local-file door served only somebody with a shell there, who would have
used the CLI.

What changed

An uploaded archive is a source (L.2). A fifth door into the same
resolver, not a fifth resolver: bytes arrive as {name, b64}, the shape
ingest has carried since J.8, so a browser that can already send a document
sends an extension with no new mechanism. It is unverified by
construction
— there is no forge and no index identity behind an upload,
so nothing could have been verified, and the tier says that rather than
implying a check happened. It is refused before it is written when it
exceeds MONKEYLLM_STATION_EXT_UPLOAD_MAX_MB (25): this door is for an
extension's own code, dependencies are resolved at install, and a
vendored-wheel bundle belongs to the local-file route.

Every seam declares its handler's contract (L.3, L.9 rule 1). What a
curation handler receives, what an events handler receives and what each
returns were prose in the specification and nowhere in the code, so the
conformance kit could answer it works about an extension whose handler
took the wrong parameters — found on the first ingest, at whatever hour that
ran. The kit now checks the signature at install, and it does so by reading
the author's source without importing it: a heavy handler's module
imports the very dependency L.5 exists to keep out of this process, so a
check that imported would break the rule protecting the deployment in order
to catch a typo, and would run third-party code at kit time, which L.2 rule
5 is careful to say the kit does not do.

The rule hinges on defaults, not **kwargs. The first cut said kwargs
excused a misspelled parameter and was wrong in the exact case it was
written for: given on_event(event, forrest, **rest), **rest absorbs the
forest the host passes and forrest is still unfilled. The specification
paragraph was corrected to match the code, not the reverse.

The authoring surface (L.16). Prose is written and schemas are derived.
The teaching text lives in docs/extending.md, rewritten around Part L,
where it is read without running anything; the manifest schema, the seam
contracts and the role kinds come from the definitions themselves, because
a transcribed schema lies at the first new seam — silently, to exactly the
person who has no other source. F.203 adds a seam at runtime and asserts the
served document changes. GET /v1/extensions/authoring is deliberately
not under the admin gate: writing is not installing, an extension is
written on a laptop and installed by whoever governs the deployment, and
gating the documentation on that authority withholds it from the only
person who needs it — J.5.12's reasoning about skills, applied again.

A patch may break you, and the version now says so. Nothing in this
repository said what a version number means. The release workflow enforces
that the tag equals version in pyproject.toml, which is a consistency
check and not a policy: it never said which number to pick, and the rule
followed for thirty releases lived in one person's head. CONTRIBUTING.md
now carries it — the minor is the spec version a release implements, the
patch is every release that cuts no spec, the major stays 0 deliberately,
and while it is 0 a patch may change behaviour.

That last sentence had one consequence to chase through the tree: a
station_compat of >=0.79,<1.0 then protects nobody, and Part L had just
sold that range as a guarantee. So L.1 states what the host's version
promises and what it does not, docs/extending.md teaches the pin, the
L.16 reference prints the exact range at the one place an author copies it
from, and extensions/whisper pins to >=0.81,<0.82 — the extension
this project ships pins the way it tells everyone else to. Its suite reads
monkeyllm.__version__ rather than a literal, so the range is actually
exercised and a failure at the next minor is the pin working.

Also in this release

  • The Extensions console gains the author's half. A file picker beside
    the source field, and a Write an extension panel that lists the seams
    read from this Station and hands out a folder (SKILL.md plus the
    generated references) for a coding agent. api.js now exports one base64
    encoder, so ingest and the extension upload no longer carry two.
  • tests/test_version_agreement.py pins what the release guard cannot
    see: the number is written twice, in pyproject.toml and in
    monkeyllm.__version__, and only the first is compared to the tag. It
    also checks that the minor names a spec file that exists, because a
    release whose minor names no spec is a release nobody can check against a
    contract.
  • A v0.80 fixture of our own failed the new contract check — an events
    handler declared echo(value), a signature no real events call could
    fill, which passed a whole release because nothing checked.

Leftovers, named

  • F.194 (signed-tag verification against the forge's published keys) is
    implemented and not measured — it needs a signed tag and gpg on the
    runner.
  • The curated index is still not published, so the verified tier has
    nothing to resolve against.
  • The Extensions console has had no real-browser pass, the authoring panel
    included.
  • F.169's curator measurement is still pending from 0.75.

Acceptance

F.199 – F.205. Full suite green locally on the release commit — 1842
collected, 0 failures, exit 0 on both matrix legs (Python 3.11.15 and
3.12.1) — and running in CI on this PR. python -m build + twine check
passed (wheel 246 kB, sdist 561 kB; the wheel carries
monkeyllm/extensions/contracts.py, authoring.py and cli_ext.py, and
nothing of the Station).

Part L shipped the mechanism and left the person it was built for with no
way in. docs/extending.md still cited spec v0.20, so the one document
naming extensions contradicted what had been released; nothing taught how
to write one; and the console's install box asked for "a path", which is
a path on the HOST. Through a browser that is the container's filesystem,
so an operator holding an extension they had just written had no route at
all to a remote Station short of publishing it to git first, and the
local-file door served only somebody with a shell there, who would have
used the CLI.

An uploaded archive is a fifth door into the same resolver: {name, b64},
the shape ingest has carried since J.8, so a browser that can already
send a document sends an extension with no new mechanism. It is
unverified by construction, because there is no forge and no index
identity behind an upload and nothing could have been verified; it is
refused before it is written when it is over
MONKEYLLM_STATION_EXT_UPLOAD_MAX_MB (25), since this door is for an
extension's own code and vendored wheels belong to the local-file route.

Every seam now declares its handler's contract. What a curation handler
receives was prose in the spec and nowhere in the code, so the kit could
answer "it works" about a handler with the wrong parameters, found on the
first ingest at whatever hour that ran. The kit checks the signature at
install by reading the author's source and never importing it: a heavy
handler's module imports the very dependency L.5 keeps out of this
process, so importing to catch a typo would break the rule that protects
the deployment, and would run third-party code at kit time, which L.2
rule 5 is careful to say the kit does not do.

The rule hinges on defaults, not on **kwargs. The first cut said kwargs
excused a misspelled parameter and was wrong in the exact case it was
written for: given on_event(event, forrest, **rest), **rest absorbs the
forest the host passes and forrest is still unfilled. The spec paragraph
was corrected to match the code rather than the reverse.

L.16 is the authoring surface, and its rule is that prose is written
while schemas are derived. The teaching text lives in docs/extending.md,
rewritten around Part L, where it is read without running anything; the
manifest schema, the seam contracts and the role kinds come from the
definitions themselves, because a transcribed schema lies at the first
new seam, silently, to exactly the person with no other source. F.203
adds a seam at runtime and asserts the served document changes. GET
/v1/extensions/authoring is deliberately not under the admin gate:
writing is not installing, an extension is written on a laptop and
installed by whoever governs the deployment, and gating the documentation
on that authority withholds it from the only person who needs it.

The Extensions console gains the file picker and an authoring panel that
lists the seams read from the Station and hands out a folder for a coding
agent. api.js exports one base64 encoder, so ingest and the extension
upload no longer carry two.

Worth keeping: the new contract check immediately failed a v0.80 fixture
of our own, an events handler declared echo(value), a signature no real
events call could fill, which passed a whole release because nothing
checked.

Spec v0.81 (L.2, L.3, L.9 rule 1, L.16), F.199-F.205 in
tests/test_v081_authoring.py. Suite green on 3.11 and 3.12.

Signed-off-by: Jimmy Wesley <[email protected]>
Nothing in this repository said what a version number means. The release
workflow enforces that the tag equals `version` in pyproject.toml, which
is a consistency check and not a policy: it never said which number to
pick, and the rule that had been followed for thirty releases lived in
one person's head.

Written down now in CONTRIBUTING, "Versions, and what a tag means". The
minor is the spec version a release implements, so 0.81.x implements
docs/monkeyllm-spec-v0.81.md and a contract change always moves it,
because a contract change always cuts a spec first. The patch is every
release that cuts no spec: a fix, a console, a document, a measurement.
The major stays 0, deliberately.

And while the major is 0, a patch may change behaviour. That is the
honest reading of a 0.x version and it is now stated rather than left to
be discovered, which has one consequence that had to be chased through
the tree: a station_compat of ">=0.79,<1.0" then protects nobody. Part L
had just sold that range as a guarantee. So L.1 now says what the host's
version promises and what it does not, docs/extending.md teaches the pin,
and the L.16 authoring reference prints the exact range to copy at the
one place an author copies it from.

tests/test_version_agreement.py pins what the release guard cannot see:
the number is written twice, in pyproject.toml and in
monkeyllm.__version__, and only the first is compared to the tag. It also
checks that the minor names a spec file that exists, because a release
whose minor names no spec is a release nobody can check against a
contract.

Signed-off-by: Jimmy Wesley <[email protected]>
Spec v0.81: the door an author could not find. An uploaded archive is a
fifth install source, so an extension written on a laptop reaches a
remote Station without being published to git first; every seam declares
its handler's contract and the conformance kit checks the signature at
install, reading the source and never importing it; and the L.16
authoring surface serves prose that is written beside schemas that are
derived.

extensions/whisper moves to ">=0.81,<0.82" with the bump rather than
after it. It shipped declaring ">=0.79,<1.0", which under the versioning
policy this release writes down is a range that promises what nobody
keeps: the extension this project ships should pin the way it tells
everyone else to. Its suite reads monkeyllm.__version__ instead of a
literal, so the range is actually exercised and a failure at the next
minor is the pin working.

Signed-off-by: Jimmy Wesley <[email protected]>
@JimmyWesley
JimmyWesley merged commit 4ded409 into main Sep 13, 2026
4 checks passed

This branch was successfully deployed

1 active deployment
pypi — a550ef1a Deployed Sep 14, 2026 by JimmyWesley via publish #19
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