v0.17.4 — a refusal stops arriving as a result #364
mmeyerlein
announced in
Announcements
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
0.17.4 is a sweep. One wave, one class of defect, found in eleven places: a
component asked another one whether something worked, did not read the answer,
and walked on as if it had. Nothing in it is a new capability — every entry
makes an existing promise true again.
A store refusal is no longer read as an empty result set (#343). The four
lanes with double-digit dispatch sites —
collector/assemble(21),memory-hive/dream-glue(28),memory-hive/extract-glue(23),memory-hive/recall(16) — are state machines over(context.<phase>, hop.operation), and not one of them readhop.error_code.operationsaysWHICH op answered; it does not say WHETHER it worked. So a refusal arrived
looking exactly like an answer — same phase, same op, an error sentence where
the rows should be. Measured: a refused window read made the collector write an
empty window leg and let the model answer the turn with no conversation at all;
a refused keyword leg made
recallreport that memory knows nothing; a refusedvocabulary read prompted the extractor with an empty known-predicate list; and a
refused scope read booked a nightly run as
status: "done", facts_in_window: 0,after which every later night derives its window from that row and skips the one
nothing ever looked at — the only one of the four a later run cannot repair by
itself. All 88 dispatch sites read both fields now, and a refusal is terminal:
no further store op leaves, the phase does not advance, and the lane says so.
The same guard on the nine small lanes (#343, now closed). Every one was
measured against a real refusal before it was touched, and every one was live.
session-keeper/stampread an unanswered lookup as "this channel has no opensession" and opened a second generation for one that had.
firewall/screenfailed open twice: an unanswered
rulesread left it with no blocklist andno allowlist, an unanswered
rateread counted zero arrivals and emittedpass.receptionist/greetread "no row" as "a channel nobody has met" and emitted amutation that grows a second agent.
memory-drain/drainprobed a ledger itsown insert never reached and dropped the day in silence.
Breaking: an unknown key in a
cellblock refuses the mutation too (#353).docs/config.mdhas always closed thecellkey list, and the boot enforced itthrough a hand-maintained allow-list inside
bootstrap.rsthat nothing elseconsulted. So the same template a boot refused went through a mutation without a
word, and a typo in a real key (
idle_timout_ms) took effect as the default forthe field it was meant to set — the cell ran with the default idle timeout, the
default restart limit, the default mailbox capacity, and nothing said the
operator's intent had been dropped. The list now lives on the
CellHeaderdeserializer itself (
#[serde(deny_unknown_fields)]), which every read path goesthrough: the bootstrap scan, the mutation and staging parse, and every node's own
cellblock inside a multi-cell template. Both refusals name the offending keyand the
config.jsonit stands in. The same rule's second half: acell.templateor
cell.type: "ref"in an instantiated tree refuses the boot — arefistemplate-time only. Migration: remove the unknown key from the
cellblock.It was doing nothing before — either a typo, in which case fix the spelling and
the value takes effect for the first time, or a slot used as a comment, which
belongs in the top-level
descriptionblock. The shippedtemplates/andexamples/trees were swept and carry no unknowncellkey.A
/colonyread refuses a filter it cannot parse instead of dropping it(#341, #359).
/colony/graphparsed the scope filter from an undocumentedtop-level
{"scope": …}while its own spec and its only shipped consumer send{"query": {"scope": …}}, so the documented form was silently ignored and thecaller got the unfiltered graph — a filter that fails open on a read that exists
to narrow. The other three
/colonyreads had the same shape. All four answer anunreadable filter with
error_code: "invalid_query"now. The old top-level formstays as an alias for one release and is deprecated with a named removal.
The builder gives back its lease and its in-flight marker together (#361).
Neither was ever released — not even on the successful run — so one completed
build left the scope locked until an operator cleared it by hand. The terminal
cell of every lane releases both now, never one without the other, and a
receipt.jsonrecords what the run actually gave back. Still open and writtendown: a run that never reaches a terminal cell at all.
Two more builder lies (#355, #360).
promoteforwarded a failed templaterescan verbatim and stamped
stage: promotedon it, so the deploy edge firedagainst a registry that never learned the class and the run died one cell later
on
template_missing— a duplicate template name reported as a missing one.deploystamped the literal stringdeployedon the colony's verdict whether itread
committedorrejected. Both branch on the answer now and fail closed,into
promote-failedanddeploy-rejected.A
bashcommand of 128 KiB or more says so (#351). Theargvlimit reportedas an I/O fault, which is the same shape a missing interpreter has.
The librarian's corpus carries whole sections (#344). The seed chunker cut
every section at
MAX_CHARSwithbody[:MAX_CHARS]— no continuation, nowarning, and no gate that could see it, because the corpus gate regenerates and
byte-compares and truncation is perfectly deterministic. Corpus-wide that
recovered content the tree had already lost: 311 rows became 411.
The release gate runs the published tree's gates before the publication
(#356).
check_claims.pyandcheck_adr_anchors.pybehave differently in thepublished tree by design, and CI was the first place either of them ever ran in
that shape — which is after the push. The previous release needed three follow-up
rounds for defects nothing local could see. Both run inside the materialised
export tree now, by default.
The cost report stops pricing embeddings at a twentieth of what they cost
(#357). The shipped price snapshot still mapped the embedding role to a model the
colony stopped calling on 2026-08-19.
Full detail, with the migration spelled out per entry:
https://github.com/mmeyerlein/meclaw/blob/main/CHANGELOG.md
All reactions