Repository navigation
Aleph v26.8.27
Four days, 361 commits, 568 files, +61.5k/−5.8k (of which +40.5k/−5.7k is
source). Three threads and one correction. First, project rooms grow a full
face — a project_manage tool, Kanban / Workspace / Memory tabs in the
Panel, a roster-gated projects.changed push topic, and a prompt layer that
finally tells the model who else is in the room. Second, waiting becomes a
wire concept: a run sitting in its session's busy lane used to emit nothing
at all between chat.send returning an id and RunAccepted, so every client
painted "thinking" over a run the engine had never heard of. Third, the
streaming renderer stops rebuilding everything on every token, in the Panel
and the TUI both. Underneath all three runs a process-global handle
migration: 46 OnceLocks became CapabilitySlots, where writing a value and
stamping that it was written are one action instead of a discipline — because a
handle that was never installed and one installed with a permissive default
read identically from outside, and half of this release's diagnostics work is
the same conflation in a different subsystem.
Added
- Project rooms, end to end.
project_manageis the conversational face
(R8); the Panel gains Kanban, Workspace and Memory tabs over room-scoped
teams, goals and loops, plus read-only workspace browse;projects.changed
is a push topic whose delivery is gated on the roster. Authorization behind
everyprojects.*gate is one shared derivation rather than a predicate per
face, and a room-claimed session key always stamps the room's scope. - A room roster layer in the prompt. The model is told who else is in the
room, and a delegated child inherits the same<room_context>its parent
had — a fan-out where each member re-derives the roster is a fan-out with N
rosters. Covered by a real-machine fixture (qa/teamchat_rooms). - The waiting phase, as its own wire representation.
StreamEvent::RunQueued
and its protocol twin,TicketGuard::aheadfor lane position reported from
the waiter's existing wake point, and the lane carried onchat.history—
the attach-time authority, so a client that reconnects mid-wait rebuilds the
queued phase from a snapshot instead of guessing. The busy-input queue itself
is now crash-durable. CapabilitySlot, and a doctor check that can see through it.
install(v)writes the value and stamps the outcome as one action; a
conditional install'selsearm mustdecline("what was missing")rather
than silently skip. Each slot names its ownMissingSemanticsin its own
module — derived from what the fallback branch actually hands the reader, not
from the config default and not from the slot's name.core/capability-wiring
reports three states, not two: not booted (this process cannot answer), booted
and complete, booted with holes (named per slot, severity derived).- A streaming renderer that renders only what changed. In the Panel,
TypewriterRenderermounts once and splits into two zones — a stable zone
gated on the freeze boundary and a tail zone that re-renders at O(tail) — with
the character reveal mapped to byte offsets by an incremental cursor instead
of a per-framechars().take().collect()rescan. In the TUI: windowed chat
rendering with anRc-shared streaming prefix, a per-message rendered-line
cache, tail-only markdown conversion, queued gateway events coalesced into a
single draw, and noterminal.draw()at all on ticks that change nothing
visible. - Reconnect that reconciles instead of resetting. The TUI joins the run in
flight on an attached session, renders peer messages, backfills turn age, and
stops holding other sessions' runs; the Panel registers a conversation on
every chat surface and repairs reconnects at the root rather than inside a
component with a mount condition; the/btwoverlay settles across a
reconnect by askingagent.statusabout the one id it still holds. - The protocol owns the receipt codes both sides classify by. The Panel now
reads the server'serror_codeinstead of matching keywords in a message,
and a Cancelled send stops being painted as a danger banner. note_managecan read one note by address. Retrieval returned ranked
excerpts truncated to 4k each, and update replaced whole documents — so the
model could overwrite a note it had never been able to read in full. The
governance verdict archive also gets its first reader and a retention window.- Markdown streaming safety in shared UI logic. CommonMark-compliant fence
detection and reference-link-definition boundaries, so a partially-arrived
document is never split inside a construct. - Model-aware summarizer budget, with a cheap-summarizer fallback retry.
- Closed-set effect-evidence grades on mutating desktop actions, so a verb
reports what it actually observed rather than that it was dispatched. - The full desktop app keeps its remote target across restarts, and probes
the gateway's/readyendpoint rather than a bare TCP connect before
navigating — a socket that accepts is not a server that can answer.
Fixed
- Eight diagnostics checks answered "I could not look" with "there is nothing
there."Path::exists()returnsfalsefor two different worlds, and in a
subsystem whose entire job is telling an operator what is true, that
conflation is the job. Six sites read the error as "absent, and absent is
fine" — including a vault that cannot be opened reporting "no secrets stored
yet", the one sentence that stops an operator investigating, on a repo that
already records vault data loss as an observed failure. A ninth of the same
class was closed inbrowser_runtime, where a panicked probe task rendered
as[ok]. The fix is a chokepoint plus a source-level guard, not nine edits:
Presence/DirListingmake the third answer anErr(Finding), and
Unknownis deliberately not aPresencevariant, so spending it as
absence has to be written out. core/capability-wiringwas registered in the offline registry. Its cold
branch fired on every call, on every machine, forever — so the exit code it
documents as CI-gateable could never be 0. A check whose premise can never
hold is not a gate.- The harness line ratchet had been disarmed by grant, not by drift. A
prior entry setCEILINGto measured + 1024 and called the gap "headroom",
while asserting in the next sentence that the budget was still a hard cap.
Both cannot be true: R10's redline permitted ~20% silent growth for a day,
and "src/harness/ delta 0" — the claim five comparison rounds rest on — was
unprovable while it stood. Measured now: 5233, itemised. - rust-logic-audit and review batches applied across nine subsystems:
secrets/security (4 Critical), clipboard (4 Critical), config (3 Critical + 1
Warning), context (2 Critical), clarification (2 Critical + 2 Warning),
session (3 High), skill (3 High + 1 Medium), plus cli and canvas warnings.
SSRF and hostname handling, the DuckDuckGouddgURL decode, and
SECRET_SUFFIXESwere all widened. - Four streaming-render defects that only a real client shows. The message
bubble remounted on every token; the entrance animation played twice at
stream completion; the unfrozen tail was baked into the markdown cache; and a
cached prefix offset could outlive a content swap that shrank it. - A single command could take the daemon out through stdin.
command_text
is now bounded. Separately, a truncated stdin keeps its head and its tail,
cut on char boundaries — dropping the tail throws away the one part a reader
is usually looking for, and the hot paths for file writes, line counts and
note bodies are capped alongside it. - Windows reserved-name filenames were sanitized past the wrong boundary.
The stem rule now cuts at the first.or:; unsafe agent ids get named
reasons rather than a silent rewrite. - Panel and phone hardening. Attachment size and count are capped to bound
base64 inflation; URL credentials are stripped and protocol-relative links
blocked; a reconnect no longer clobbers in-progress edits; the phone memory
tab no longer loses the URL fragment or panics on a disposed read. - iOS: four independent trust and storage defects. Cert-pin saves are
serialized against concurrent challenges, the Keychain pairing URL is scoped
to the device that stored it, overlapping TLS trust prompts fail closed, and
isInspectableis gated behindDEBUG. The project also moves to Swift 6
language mode, and owns its IPv6 bracket rule instead of assuming
Foundation's. - A refused transcription endpoint took the daemon down with it.
- A failed run was reported as a cancelled one. Four error classes fanned
intoCancelledat the exit arm, so a provider auth failure rendered in the
trace as a neutral "stopped" — lighter than hitting the turn cap and byte-for
byte identical to the user pressing stop, while the session log beside it
saidErrored. Every guardrailBlocknow settles through one path, so the
second producer of that outcome stops leaving orphantool_usecalls behind. purge_allblocked the async runtime, and now falls back to inline
execution when no Tokio runtime is in scope rather than panicking.- A missing session-service handle dropped work without a word. Approval
records, replayed messages on the OpenAI-compat history path, both branches
of the slash-command fast path, both onSimpleExecutionEngine's, and a
skipped run-id/occupancy stamp now each name what they dropped. - CI told the truth about three of its own failures. The ubuntu exit-143
death names its resource (memory, not disk); the security audit's path
filter now includes the code it lints; andInfo.plistis staged into
OUT_DIR, because a link-arg cached intarget/naming a file in the source
tree outlives what it names — and the failing case is by definition the one
where the build script does not run. - This release's own gates were red before it started.
cargo fmt --check
failed on 26 files across all three platforms, which — underbash -e—
aborted the lint job before clippy and before the--all-targetscheck, so
the two steps that catch real regressions had not run in days. Apanic!in
build.rstrippedclippy::panic; the lint exists to keep panics out of code
that ships, and a build script is not that code, so the allow is scoped to
that one file.