Skip to content

v1.24.4

Choose a tag to compare

@github-actions github-actions released this 13 Sep 15:52
· 246 commits to main since this release

Downloads

Architecture MacOS Linux Windows
x86 Mac Intel Linux x86 Win x86
ARM Mac ARM Linux ARM Win ARM

This release includes the commit-level changes below.

Commits

  • Lint: fix the two violations this work introduced: The v1.24.0 release build succeeded and published all assets, but Lint & Test on main is red. It was red before this session — 158 prettier offenders, 10 black, 3 ruff, in files last touched as far back as v1.18.9 — so this does not fix the job. It fixes the part that is mine. black would reformat runtime/benny/core/adaptive_rag.py. Formatted. Verified afterwards that every behaviour-carrying string survived: the branch-expansion and lenient-source comments, the unresolved-fallback and no_retrieval-overridden trace tags, and self_check defaulting True. ruff was already clean on both files I touched; the 3 ruff errors in the tree are elsewhere. manual/OPERATING-MANUAL.md and manual/tour.html failed prettier. Both are GENERATED by scripts/manual_build.mjs, so they belong in .prettierignore rather than being formatted: the generator owns their shape, and reformatting them here would be undone by the next build — a permanent dirty-tree loop. The other generated outputs are listed too, so this cannot resurface when one of them happens to cross a line-length. manual/manual.json is hand-edited source and stays checked; verified it still is. Not touched: the ~168 pre-existing violations. That is a large unrelated diff across the app shell and architecture docs, and whether to take it on is a decision, not a cleanup to slip into a lint fix. Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • Lint: clear the backlog, and stop generated artifacts being linted as source: Lint & Test had been red on main since well before this session — every run in the visible history failed. Now green: prettier, eslint, ruff and black all pass the exact commands CI runs. Of 155 prettier offenders, 75 were not source at all: QLoRA training outputs and eval dumps under scripts/train/{qlora,eval}/out_/ — checkpoints, tokenizer.json, generated result files. Those are ignored rather than formatted. A tokenizer.json is a data file whose shape belongs to the library that wrote it, and reformatting a training artifact achieves nothing while risking a diff nobody can review. The remaining 80 were formatted, plus 9 files black-formatted and 3 ruff errors fixed: two unused imports (re in sdlc/bench_record.py, sys in tools/data.py) and one unsorted import block. Both imports were confirmed genuinely unused before removal — the re. matches in bench_record were prose and the filename "model_compare.py", and sys. appears nowhere in data.py. That check mattered: the sys import sits in the OCR path, which only runs when someone OCRs a PDF, so a wrong removal would have stayed silent until it didn't. Checked before touching anything, not after: no file among the offenders carried a signature, checksum or digest field, so JSON reformatting is whitespace-only here. The repo signs manifests, and formatting a signed file would invalidate it. The six app-shell files (bridge, mission) were fingerprinted before and after on identifier set, string count, hex-colour count and x- attribute list. All six came back identical — the reformat is whitespace only, not a behaviour change to two live panels. Verified: prettier clean, eslint exit 0 (52 warnings, 0 errors), ruff "All checks passed", black "213 files would be left unchanged", every benny module compiles, artifact_ledger_test 9/9, governance_signature_test 9/9, C0 gate green, manual --check passes, inventory sweep exits 0, evidence pack still reports Complete. The runtime bundle was re-synced from the reformatted source: zero content diff. Not fixed: eslint's 52 warnings. They are warnings, CI passes on them, and silencing them is a separate decision from making the job green. Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • Release feed: stop Windows x64 machines being offered the arm64 installer: metadata-latest-windows.yml — the file the in-app updater reads — has described only arm64 since at least v1.22.1. v1.23.0 and v1.24.0 shipped the same way. On this x64 machine "Update Now" would have fetched an arm64 build, which is very likely why it sat on 1.23.0 while releases came and went. electron-updater picks by architecture from files[] and falls back to the FIRST entry when nothing matches (Provider.js findFile: ?? filteredFiles.shift()). A feed with one arm64 entry therefore hands arm64 to everyone. The merge step was supposed to prevent exactly this and never ran. It collected inputs by exact basename "metadata-latest-windows.yml", but electron-builder names a feed <channel>.yml for the runner's native arch and <channel>-<arch>.yml otherwise — so depending on the runner, one of the two Windows jobs contributes a file the collector cannot see. It found one input, took the "Promoted" path, logged success, and published a single-arch feed. Three changes: Inputs are collected by the ARTIFACT DIRECTORY (windows-x64/, windows-arm64/), matching .yml or -.yml. The directory is the authority; the filename is not, because it depends on which runner built it. x64 sorts first, so the top-level path/sha512 — which serializeUpdateMetadata takes from files[0], and which is exactly what electron-updater falls back to — lands on the majority architecture rather than on whatever sorted first. A coverage guard fails the release when a platform built two architectures and the merged feed lists fewer installers than that. This bug survived three releases because a no-op looked identical to success in the log; nothing compared what came out of the merge against what went into it. The guard deliberately counts entries rather than looking for arch tokens in the urls. At merge time those urls are still electron-builder's raw names, and the x64 one carries no arch at all — canonical arch-bearing names are applied later by release-assets-stage.js. An earlier draft of this guard checked for tokens and would have failed every release; the test suite pins that case so it cannot come back. Six tests cover both filename schemes, the x64-primary ordering, the dropped-arch failure, single-arch promotion, and raw untokenised names. prettier clean, eslint exit 0, C0 green, artifact_ledger 9/9, governance_signature 9/9. Not fixed here: v1.24.0's published feed is still arm64-only. Existing x64 installs cannot self-update until a release is cut with this change. Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • Release: 1.24.1
  • Sitemap: derive the map from the code, and check it both ways: The first thing asked for on this work was a sitemap, and it was never built — implementation went ahead without the map that was supposed to precede it. This is that map, built the only way it can stay true: read out of the repository on every run rather than drawn. "Six sigma correct and complete" only means something if both halves are checked, so the build fails on either: CORRECT every node resolves to something real — a registered panel, an existing view.html, an endpoint that exists on disk. COMPLETE everything real appears on the map. A panel nobody placed in a zone fails the build by name. The second half is the one people skip, and it is the one that matters: a map which quietly omits a surface is worse than no map, because it is trusted. Endpoints per surface are read from the panel's own source — every "/api/x" it actually fetches — so the map records what a page CALLS, not what someone thought was related. Zones are the one hand-written thing here, because "what is this surface for" is a judgement, not a fact derivable from the file. There are nine: Decide, Prove, Watch, Recall, Replay, Ask, Hold, Think, Operate. An unzoned panel is an error, not an "other" bucket. The check earned its place on its first run by failing, and it was wrong: it reported the Bridge panel calling /api/runtime, "which does not exist". The panel was right — /api/runtime is a PREFIX forwarded to Benny by runtime_proxy.js, and there is no server/api/runtime.js to find. Proxy prefixes are now read out of the proxy modules themselves, so adding a proxy cannot turn into a false alarm. Both halves verified by breaking them deliberately: unzoning Gov and pointing a zone at a non-existent surface each fail with exit 1 and name the problem; the restored tree exits 0. Current estate: 16 surfaces in 9 zones, 51 API routes, 21 commands. Recorded rather than failed, because it is a fact worth seeing: 39 of 51 API routes are reached by no panel at all — including gov_raise, the endpoint that starts every causal chain in the governance ledger. Proposals can be raised from a command line or an agent, and from no screen. That is not a defect the sitemap should hide by omitting the route; it is the sort of thing a sitemap is for. Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • Use cases: 17 of them, every surface covered, and an honest count of what was actually run: The second half of the design set that was asked for first. Checked against the sitemap in both directions, so it cannot quietly stop being true: CORRECT a use case cannot name a surface, command or endpoint that does not exist. Every reference resolves against manual/sitemap.json and the repository. COMPLETE every surface on the sitemap is exercised by at least one use case. A screen nobody has a reason to open fails the build by name — it is either a missing use case or a surface that should not be there. Both halves proved by breaking them: dropping user from every use case fails with "no use case exercises it", and pointing UC-01 at an invented surface fails with "not on the sitemap". Restored, exit 0. Actors are the estate's own authorship enum (human, frontier, house) plus external. The ledger already records who caused every event in exactly those terms; a use-case map with a different cast would be describing a different system. One number is reported and deliberately NOT gated: 9 of 17 use cases are marked exercised — run end to end against the live estate this session — and 8 are declared, taken from the surface's own registered description and never run. Gating on that ratio would create pressure to mark things exercised, which is precisely how a coverage figure stops meaning anything. It is printed, and 53% is allowed to stand as the honest number. The map records the gap the sitemap found rather than smoothing it over: UC-02 "Raise work for a decision" has NO surface. gov_raise starts every causal chain in the governance ledger and is reachable only from a command line or an agent. The Gov arc lets you sign and decline; there is nowhere to raise. 17 use cases · 16 surfaces, all covered · 9 exercised, 8 declared. Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • Process flows: model the refusals, and check the graph as a graph: Third of the design set asked for at the outset. A flow written as prose cannot be wrong in a way a machine notices, which is why diagrams rot quietly. These are steps and transitions, so three specific rots are build failures: a dangling edge — a step is renamed and the arrow keeps pointing at the old name. Reads fine to a human, means nothing. an orphan branch — a path someone wrote and never connected. It reads as part of the process and can never happen. a flow that never ends — no reachable terminal. Each was proved by introducing it: a renamed target, an unconnected step, and a deleted flow all fail with exit 1 and name the problem; restored, exit 0. The design point is that REFUSALS ARE STEPS. A flow modelling only the happy path describes a system nobody has, and what distinguishes this estate is what it declines to do. 14 of 33 terminals are refusals or halts: signing onto a broken chain, raising without a rationale, citing an unsigned proposal, admitting bytes that fail their hash, judging a path the sweep never walked, reporting a gauge with nothing under it, answering from a manual that only half loaded. Completeness is measured against what was actually RUN. Every use case marked exercised must be modelled — 9 are, and all 9 have flows. The 8 declared use cases are deliberately left unmodelled: inventing the branches of something never run would produce a diagram indistinguishable from a real one, which is worse than an absent one. 8 flows · 70 steps · 33 terminals · 8 mermaid diagrams, all rendered from the same source the checker validates. Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • Robustness diagrams: enforce ICONIX's own rules, and let them catch my drawing: Last of the four design artefacts asked for at the outset. Robustness analysis earns its place only through four constraints most people draw straight past: an ACTOR may touch only a BOUNDARY a BOUNDARY may touch only actors and CONTROLS an ENTITY may be touched only by a CONTROL CONTROLS may touch boundaries, entities and each other Which forbids, concretely: a person reaching past the interface, a screen writing to a ledger, two screens wired together with no logic between them, two stores talking with nothing deciding. Each is a design defect, not an untidy drawing, and each now fails the build — so the wrong architecture cannot be committed. The checker earned its place immediately by rejecting MY OWN first draft with ten problems, two of them real ICONIX breaches I had drawn without noticing: govArc → proposalsApi a screen calling a route directly, with no control between. Fixed by modelling the request as the control it actually is. onboardCli → appRecord a command writing a record with no logic in between. The other eight were completeness failures of exactly the kind this artefact exists to expose: use cases declared surfaces — Files, Step-Through, Time Travel, Manifests, Bridge, Local LLM, manual_build — that my diagrams simply omitted. A process that touches a surface and a diagram that does not show it is the omission robustness analysis is for, so the boundaries were added rather than the declarations quietly dropped. Also enforced: an object connected to nothing is decoration and fails; every boundary must name a real surface, endpoint or command, or it is a picture. All three proved by introducing them — actor→entity, boundary→entity, and an orphan object each fail with exit 1 naming the rule broken; restored, exit 0. 8 diagrams · 25 boundaries · 36 controls · 25 entities · 105 edges, every one legal, rendered as mermaid from the source the checker validates. The chain now holds end to end: sitemap → use cases → process flows → robustness, each checked against the one before, and all five builds green. Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • HUD deck: the estate on a wall, and a microphone that cannot sign: One surface with three states rather than three products — ambient when nothing wants you, situation when you engage it, command while you hold the talk key. Served two ways: an arc inside the app, and /deck standalone for a projector, because the tier queries key on the container and inside a panel that container is about a thousand pixels wide, so the wall tier can never fire there. Tiers, not one responsive page. wall (>=1800px) gives 62px figures readable from across a room; column collapses to one; pocket shrinks for a hand. Verified live at 1920 / 820 / 380. VOICE CANNOT SIGN, and not as a policy — there is no branch in the intent router that could. The estate rests on a signature being a deliberate act by an authenticated person, and this runs in a room where anyone can speak and a television can too. Voice navigates, asks, and raises (frontier authorship, which asks for a decision and takes none). Asking to sign is recognised only so the deck can open the queue and explain why it will not. Two tests pin it: eight phrasings of "sign it" must all be refused, and no intent the router can return may name a signing path at all — the second is structural, so a future intent added without reading the header still cannot leak one. ROOM MODE IS ON BY DEFAULT. The payload names its own sensitive fields rather than leaving the renderer to guess: machine names, proposal titles, store paths. Turning redaction on after the first glance is one glance too late. MOTION IS THE ESTATE'S. C0 forbids ambient motion and on a wall that rule pays for itself — a surface that animates constantly becomes wallpaper. Each poll is diffed against the last and only a tile whose figure actually changed moves. Absence is a state, not a zero: no store, an unreadable ledger, a node that stopped reporting each render differently from "there is nothing". Two bugs found by building it. estateBoard keys on MACHINE and needs each node's state file; passing ledger names produced six anonymous nodes all reading stale — absence rendered as six dead machines, alarming and false, on a wall. And the standalone page loaded deck.js as a module, which runs after deferred Alpine, so every binding evaluated against nothing and the page showed only its title. The documentation chain then refused the work until it was described, which is the point of having built it: sitemap rejected an unzoned surface, use cases rejected a screen with no reason to open it, flows caught six steps orphaned because I modelled speaking as a second entry point instead of a branch from waiting, and robustness caught me wiring a screen straight to a route — the third time I have made that exact ICONIX error, and the third time a machine caught it rather than my care. All five builds pass. deck_intents 4/4, prettier clean, eslint 0, C0 green. NOT done: the Kindle tier. It needs a static no-JS board on its own route, not a breakpoint — e-ink cannot do any of this — and pretending a fourth media query covers it would be the drift these documents exist to prevent. Voicebox is starting but was not up when this landed, so the deck currently reports "Benny can listen, but Voicebox is not running — replies will be text only", which is the honest state rather than a broken button. Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • deck: give the deck a voice and a paper tier: The deck could hear but not speak, and its microphone was gated on the mouth. Voice out. /api/deck_speak drives Voicebox server-side so the browser never crosses an origin and the profile/engine rule lives in one place: a preset profile refuses any engine but its own, and the request schema defaults to qwen, so omitting the engine sent a kokoro profile a qwen job and got a 400. Cloned profiles run on qwen at roughly ten times real time on this CPU, so preset voices are preferred — a wall that answers a minute late is not slow, it is wrong. The endpoint spends a fixed budget and then says "still warming up" rather than hanging, because the reply is already on screen in text. Verified live: Audiobook-Narrator/kokoro, 205KB of RIFF/WAVE for a 4.3s utterance, 7.7s warm round trip; played in the browser from the deck itself. The microphone gate was wrong. It required Voicebox to be up while the note beside it promised "replies will be text only". Hearing and speaking are separate capabilities and the button belongs to hearing. The Kindle tier could not be a fourth breakpoint. Wall, column and pocket are one document reflowing; a Kindle is a different machine — no container queries, no Alpine, and a screen that costs a visible flash to repaint. So /api/deck_kindle renders finished HTML on the server: no script, no stylesheet request, nothing to hydrate, 2.6KB, and it fills a 758x1024 panel in one page with no scroll. It renders from deck_state's own fold, so the board in someone's hand cannot disagree with the wall. State is carried by words and by inversion rather than colour, because e-ink has none, and the reading time is printed — an old picture that says how old it is is still evidence. The manual had no deck entry at all. Adds the arc and three features (the wall, the voice, the paper), so the sitemap and the operating manual both account for what shipped. Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • deck: record what the Kindle tier cannot do about authentication: The route authenticates like every other API route, on purpose: these are the estate's governance figures and the server can be bound to the LAN. But a Kindle's browser cannot be expected to complete the app's challenge login, so on an installation with a password set the board is reachable only if the device already holds a session. Written down in the file rather than left to be discovered by someone standing in a kitchen with a Kindle, because the tempting fix — allowAnonymous — is the wrong one and should be refused with a reason attached. Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • release: publish through the estate, not through a browser: Publishing was the last estate action with no governed path. A tag went up, someone opened github.com and clicked Run workflow, and the only trace was a CI log nobody folds — an uncontracted execution, and the one with outward effect: installers on the internet that other machines trust. Now it is raise, sign, dispatch, record. The Release arc shows what is published and what the feeds actually cover; it can raise a proposal (frontier authorship, which asks and takes nothing) and act on one a person signed. It cannot sign, and there is no code in it that could. Two halves of the authority, neither sufficient. Without a signed proposal the endpoint returns 403 and writes nothing. Without a credential it returns 409. Governance is checked first on purpose: an operator told "not armed" when the real problem is "nobody signed it" goes and installs a token to fix a governance gap, which is the wrong lesson. The grading corrects a mistake this commit made first. The desktop host has carried a Windows arch fallback since v1.0.0 — when the feed omits the running architecture it downloads the canonical asset itself — so the known Windows x64 feed defect is degraded, not an outage. The first version of the fold credited that fallback to every platform. It begins if (process.platform !== "win32") return null. The live Linux feed has the same shape of gap and nothing rescues it: an arm64 Linux machine is handed an x64 AppImage by electron-updater's own ?? filteredFiles.shift(). Severity is now a property of the platform, and the estate reads stranded rather than degraded. Also fixed while here: the unpublished-tag gauge judged eighty historical tags against a ten-release window and called them all unpublished — a gauge wrong eighty times out of eighty-one teaches you to ignore it. It is now bounded by the window examined and reports that bound. And sitemap_build took the SECOND comment line as each command's summary, which in this codebase's house style is a bare //; every command in the map had an empty caption, and the two that did not showed a fragment from mid-sentence. The token is supplied, never generated, read from stdin rather than argv, and never printed or sent to the browser. Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • gov: never record the single-user placeholder as a signer: The desktop app runs in single-user mode, where every request resolves to an implicit principal whose name is the constant "user" (SINGLE_USER_APP_USERNAME). gov_sign took the session username first, so that slot name was written into the governance ledger as the person who authorised release-1-24-1. A reviewer asking "who approved this?" read "user" — a genuine human signature with no human's name on it. The earlier "nsdha" signature only carried a name because it came through a dev server with no session and fell back to the OS account. resolveSigner now recognises the placeholder by the auth service's own source: "single-user-app" marker and resolves the signer to the OS account the app runs under. Both facts are server-side, so nothing a request carries can influence the name — "the client cannot choose who signed" stays true. If the account cannot be read, signing is refused rather than falling back to "user": a placeholder recorded as a signer is an unattributed signature wearing a name tag. How the signer was identified is now written into the event as signer_source ("session", "single-user-app:os-account", "os-account"), not only echoed in the HTTP response — a name alone cannot say whether it came from a login or from the OS. Absent, not empty: builders called without a source omit the key. Forward-only by design. The release-1-24-1 signature stays as recorded; the ledger is append-only and hash-chained, and rewriting an attributed signature is the thing it exists to prevent. Verified by mutation: restoring the old behaviour (placeholder treated as a real session) fails exactly the three new placeholder tests. Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • updater: record why a self-update failed, and survive a dropped download: The ASUS failed to download 1.24 and left no trace of why. Every updater event and failure went to console — which a packaged Windows app sends nowhere — and to a title-bar status cleared after 15 seconds. The persistent desktop-updater.log only ever saw install-handoff lines. Now every updater event, electron-updater's own logger, and each failure with its undici cause chain (ECONNRESET, ENOTFOUND, cert errors, not just "fetch failed") lands in /logs/desktop-updater.log, rotated at 5 MB. Each check records version, arch, feed, pending dir and free disk on the cache volume, and the update state keeps lastFailure so it can be surfaced. The Windows arch-fallback download was a single unguarded fetch of ~130 MB: a laptop that sleeps or roams mid-download simply failed. It now retries 4x with backoff, aborts after 60 s without bytes instead of hanging, detects a truncated body, and does not retry a 4xx. tests/desktop_updater_download_test.mjs covers redirect, mid-stream drop + retry, stall, 404, refused connection cause, and log serialization/rotation. Disabling retry fails the drop test. Also run against the real v1.24.0 x64 asset (129632231 bytes via the release-assets redirect). Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • Release: 1.24.2
  • updater: a failed download offers a retry, and Windows builds stop colliding: Two reasons an x64 machine could not get an update, found while cutting 1.24.2. The update UI had no way back from a failed download. The failure set the update state to "error", which hid the button and showed "Could not check updates" (the check had succeeded; the download failed), and downloadDesktopUpdate() refuses anything but "update-available". So one dropped download meant no update until the app was relaunched, and nothing said why. The state now returns to "update-available" carrying the failure; the launcher shows "Retry update to vX", labels it "Update download failed", and points at desktop-updater.log. A later clean check drops the retry label. v1.24.2 built all six targets and then failed to publish at the arch coverage guard, correctly. With no nsis.artifactName, both Windows runners build their native arch and name the installer "Prime-Silo Setup .exe", so the merge deduped them into one entry: one installer for two architectures, the same defect that left the feed arm64-only for three releases. The installer name now carries ${arch}. A new test runs merge + stage on those names and checks the published feed lists both canonical installers with x64 on top. Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • Release: 1.24.3
  • release: each Linux feed reads only its own arch, so publishing works again: v1.24.3 built all six targets and failed at "Merge updater metadata": Expected exactly one metadata-latest-linux.yml file for linux, found 2. The directory-based collector from 75d4e0d matches "(-x64|-arm64)?.yml" in any linux-* directory, and the x64 spec's stem "metadata-latest-linux" also matches the arm64 job's "metadata-latest-linux-arm64.yml". Both Linux specs therefore saw both Linux feeds. Windows and macOS merge, so they were fine; Linux publishes one feed per arch and threw. No release since that commit could have published. Linux specs now carry an arch and a shared stem, so each reads only its own build directory and accepts either electron-builder name for it. Verified on the real v1.24.3 CI artifact layout (file names and feed files pulled from the run's artifacts): merge and release-assets-stage both exit 0, and the staged feeds are Windows [x64, arm64] with x64 on top, macOS [x64, arm64], and one Linux feed per arch. This is the first Windows feed that lists the x64 installer at all. The new test fails without the fix. Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • Release: 1.24.4