Skip to content

v0.5.0

Choose a tag to compare

@github-actions github-actions released this 16 Sep 22:04
· 101 commits to main since this release
c729935

v0.5.0 — 2026-09-17

The release where what this box measures drives something.

v0.4.0 moved the locating to the side that does it for nothing: bb pinpoint
in front of a search, the reference tables in front of the tree, and a gate
that says whether returned work is finished. Every subsystem underneath it
already had its data and its report, and nothing in between. bb lathe
proposed scripts nobody applied. bb bench printed the tasks where packing
loses and they stayed routable, so the next run packed them again and lost
again. bb uptake measured instruction lines nothing removed. Seventeen
detectors filed a finding and left it for a session, including the ones whose
fix is a delete.

So this release is the actuator layer, plus the two surfaces that were missing
from the loop it closes: a session that opens with no environment gets one, and
the ports this workspace is served by are a page rather than a guess.

Measured

Actuator coverage, bb findings --json over this repository today:

792  findings open
484  name an actuator that runs locally, for zero model tokens
 14  of the 22 actuators are named by at least one finding
before after
findings naming a local actuator 2 484 of 792
bb lathe proposals, and the habits under them 114 proposed 9 real
echo findings against this workspace 16, twelve of them the echo's fault 4
CI jobs that ran the arc/JS agreement test 0 of 9 every one

The lathe row is a subtraction, not a filter. commandShapes shaped heredoc
bodies as commands, so } catch, const n and Math.random() are on disk in
shapes.jsonl; it split pipelines on |, which made grep ; head the
seventh-strongest habit at 70 occurrences; and it counted wrappers, which made
timeout the most supported shape on the box. With those three closed, 114
proposals become 9 habits, and each one is a shape somebody ran by hand more
than twice.

544 node tests, 36 python tests, 0 failures, 260 files lint clean. 437 node
tests shipped in v0.4.0.

Added

bb fix runs 22 actuators. Each closes one mechanical case of one finding
and declines the rest by name, because a guessed edit is a wrong edit nobody
checks again. Six are the mechanical deletes: drop-dead-export,
remove-dead-dep, strip-debug-line, relock-npm,
resolve-identical-conflict, drop-dead-knob. Eleven more cover the detectors
that named none — four edit, seven write the plan behind a decision and leave
the finding open for the person who makes it.

Every actuator re-derives its detector's answer before it acts. A scan is a
photograph, and drop-dead-export acting on a stale one unexports a symbol
somebody started importing this morning; the test for that writes the caller
after the scan and asserts the decline. Each imports the detector's own pattern
rather than copying it — exportDeclRe, isWholeLineDebug and
conflictBlocks are exported now, so the rule that files a finding and the
rule that fixes it cannot drift apart.

drop-dead-knob is the first entry in DESTRUCTIVE, which was declared empty
and waiting for one. bb fix asks for --force before it runs. The edit is
reversible; what is not is that a knob somebody already set stops being ignored
and starts being unknown.

The rest of the actuator layer.

bb bench gate       a task the bench measured as packed-loses-bare is marked
                    `pack: false` by `bb route` and refused by
                    `bb monitor guard --for`
bb recom repeatable fact-records for the four surfaces that re-drove on a
                    schedule: the cron line, three pipeline stages, cookbook
                    boards, genesis coverage
bb wire trim        the instructions block is rows with ids, so a line
                    `bb uptake` measured nobody reaching for can be removed
bb lathe apply      writes the habits into scripts/ with a `bb recom` record
                    behind each; `reach` measures whether the habit recurred,
                    `sweep` tombstones what displaced nothing
bb proxy            pack, serve and sieve at the doorway, for an agent whose
                    hook system this box cannot write into
bb echos            spin, oscillate, drift, diminishing and converge over the
                    sessions already on disk
tokens calibrate    churn_factor, anchor_widen and reserve_by_kind fitted per
                    repo, on the `measure` gear's tick

bb wire trim refuses to trim all nine MCP tools: 0 of 19 across the whole set
is the server not being reached, and bb unwire is the verb for that. A
calibration factor with too few samples keeps its shipped value and says so.

A session that opens with no environment gets one. Found by running
bb env in a workspace somebody inited once and never wired: 2 of 9 artefacts,
bb wire status 0/11 files. Every surface this box installs assumes
.bundlebox is there, none of them says so when it is not, and the session
does what a session with no environment has always done — it searches the tree.
SessionStart runs bb init inline, because detection over the tree finishes
inside the 30s hook budget, and detaches the artefact build, which is minutes
of scanning. Measured on a fresh fixture: 2.5s inline, artefacts on disk within
6s.

The guard is asymmetric on purpose. Declining a real project costs one line of
advice a session can act on; initing a home directory, a mount point or /tmp
scatters a state directory nobody asked for and starts a background build over
a tree that is not a codebase. The obvious non-projects are refused by name and
the rest need positive evidence: a git worktree, or a manifest at the root or
one level down. wire.auto_init turns it off, and it does not switch off with
inject_context — a workspace that was just created still says so.

bb viewport serve — the ports this workspace is served by, as a page.
bb runbook status answers for the declared system. A workspace is served by
whatever is listening: a dev server somebody started by hand holds the port the
declared one wants, and runbook reports that service down without being able to
say why. The viewport reads the socket table and joins it to the declaration,
which produces three sets — declared and listening, declared and not, and a
listener nobody declared whose process works inside this tree. The third is the
one no other verb reports and it is usually the reason for the second.

Three socket readers, because the one that exists differs by box: ss, lsof,
netstat. how names the reader that answered, because a table with no rows
means "nothing is listening" under one reader and "no reader was available"
under none.

Two mainboard views. bugbash is the bar a screen has to clear, checked
against the rendered screen rather than the source: six mechanizable rows from
spinwish's board, and the fourteen that need a person reading screenshots are
not here. It pairs with designlabs, which measures the declaration and says
so itself (affordance.signified unknown — a parse cannot see a pixel), so the
two produce the comparison neither can make alone: the declaration passed and
the screen does something else. turntables replays the boards already under
.bundlebox/var/boards/ and files the two facts no single run holds — a
scenario that changed its mind between runs nobody touched the corpus between,
and a scenario that went green to red and stayed. It runs nothing; the corpus
was already paid for.

bb dotty keeps what a run showed, the other half of what bb recom keeps.
The PNG is for the person; the accessibility summary is for the session,
because an image costs vision tokens and cannot be grepped, diffed or asserted
on.

Two detectors. swallowed-errors — a catch that discards the error with no
comment saying why (79 here, and quiet on the ones this tree already explains).
dead-config — a shipped key nothing reads (9).

sieve.enabled defaults to true. The allowlist reaches log, probe and test
output, Read/Edit/Write are absent from it by construction, and an elided middle
spills to disk before the marker is written.

Changed

Three echo rules were measuring the harness rather than the work. Found by
working through what the echos filed against this workspace: sixteen hits, and
twelve of them were the echo's fault.

drift reported five sessions as having changed nothing. Every one had changed
plenty, through the shell — one session ran 217 bash calls holding 59
redirects, 42 heredocs, 11 git writes and 4 sed -i. bb uptake settled the
same argument for reading and states it plainly: a file opened with sed -n
counts as much as one opened with Read, because which tool a session uses is a
harness setting. writesFiles says the equivalent about writing. A shell edit
carries no hash and no path, so oscillate ignores it, which is honest: this
box cannot tell whether a sed -i put a file back to a value it already had.
Five hits become one.

diminishing fired on 15 of 15 eligible sessions here, at ratios between 1.62
and 3.25 around a median of 2.24. The docstring at the top of the file already
said why that is not a finding — every long session costs more per change
because the window is re-sent on every turn, and that much is arithmetic — and
the threshold was then set at 1.6, below the arithmetic. A rule that fires on
the middle of a distribution is describing the distribution.
diminishing_ratio is a multiple of this workspace's median now, which is the
move bb oversight already makes when it calls a file "2.6x this tree's median
of 135". Under five eligible sessions there is no median worth having and the
answer is unknown, never ok. Fifteen hits become one.

spin claims the same question cannot return a different answer. That fails
for a command watching something this box does not control — gh pr in four
sessions, curl in a fifth, git show in a sixth — where running it again is
waiting, not spinning. It fails again for a shape whose argument selects the
command, which is the problem record.names already solves for cat and
grep. Events carry polls now, set by the JS feed that owns the vocabulary
and read by the Rust rule. The command still counts as run, because it was;
only the repeat rule ignores it. Seven hits become two.

What is left is four findings that hold up, including one session at 5.1x the
median cost per change.

The viewport serves under /<dirname>/ on 65432. It served every page from
the root of its port, so two checkouts could not both be open: whichever bound
the port owned every URL. / and /<dirname> redirect to /<dirname>/, which
is what makes the index's relative links resolve; a path outside the base
returns 404 naming the base it does serve; an out-of-range --port exits 2
instead of crashing in listen().

CI builds arc and proves the agreement ran. test/echos.test.js exists
because two implementations of one rule drift: arc/src/echos/ in Rust and
src/echos/fallback.js in JavaScript. CI built the kernel and never arc, so
both agreement tests skipped on all nine jobs since the day they were written,
and arc drifted two revisions from the JS without one red job. npm test is
green whether those tests compared the implementations or declined to, so the
workflow now reads its own output and fails on a skip:

node --test test/echos.test.js | tee "$RUNNER_TEMP/echos-ci.txt"
grep -q "SKIP arc" && exit 1

set -o pipefail above it, because a pipeline reports tee's status and a
failing test would otherwise leave the step green.

Fixed

A stale arc binary is a skip, not a verdict. npm test on main reported
that the Rust and JS echos disagree about diminishing. They do not. The
binary on that box was compiled before arc/src/echos/diminishing.rs last
changed, so it answered with a minimum-sessions rule that exists in no source
file in the tree — strings finds the sentence in the binary and grep finds
it nowhere in arc/src. The failure was the useful half: for as long as the
stale rule happened to agree with the JS, the same test reported a pass, and
that pass was a statement about two implementations only one of which was on
disk.

ARC now resolves to a binary newer than every file under arc/src and
arc/Cargo.toml, and when it is not, the two agreement tests skip with the
file that is newer and the command to run. mtime rather than a content hash,
because cargo's own freshness check is mtime-based and being wrong here costs
one skip and a rebuild instead of a false verdict. CONVENTIONS.md already
states the rule: degrade to unknown, never to a plausible answer.

A corpus runs against the base it declares. Found by being asked to set
mainboard.bugbash.base so the scenarios stage would close, which is what
the actuator release told anybody reading it to do. mainboard.bugbash.base is
the UI origin; a corpus runs against the API, and that setting's own comment
says what happens when the two are confused — joining a screen path to the API
base probes its 404 page and files findings about it. The key had been put
between --base and the persona in bb cookbook run, so setting it would have
pointed all 55 steps at a 404: green, against nothing.

The viewport is not a stray on itself. bb viewport serve is a listener
inside this tree that nothing declares, so the join put it in the undeclared
table and the page reported itself as the anomaly. Dropped by process rather
than by port number — pid, or a command line matching viewport serve read
from /proc or ps — so a --port override is covered and a different
process holding the same port is still reported.

looked is counted before the polls check in both echo implementations.
The count ran after that check, so a session of nothing but gh pr reported
"no command shape has been recorded" — unknown, for a stream full of them.