Vocion 2.88
Six minor versions and four patches in one day, on top of the morning's 2.82. A structural gate on outbound copy, the briefing rebuilt as a typed document around the decisions only a person can take, four surfaces on one list row and a Search you can read, record pages at full width with a rail that points instead of repeating, @artifact as a typed contract on the turn — and the release train's leftovers finally cleaned up, including an accessibility bug that had quietly renamed every decision button in the product.
This is the fourth release notes post in two days and the second dated 2026-09-16. The others are Release notes — 2026-09-15, Release notes — 2026-09-15 (afternoon) and, from this morning, Vocion 2.82. Nothing here replaces anything in any of them.
Vocion 2.82 ended by warning that its tag carried a broken schema and that the one-line fix was on main but not in the release. That fix is v2.82.1, and it is the first thing in this post. Ten more tags followed it the same day.
The tags
Vocion core is at v2.88.0, cut from main at 6829a9bb. semantic-release cuts a version on every push, so this is ten tags rather than one:
| Tag | What it carries |
|---|---|
v2.82.1 | the schema fix Vocion 2.82 promised — build from this, not from v2.82.0 |
v2.82.2 | the accessibility fix below, a render-time ref write, the Storybook session fixture |
v2.82.3 | the canvas removal finished, and two browser expectations the rail had moved |
v2.82.4 | #367 — green CI on main, and the Snooze verb that vanished |
v2.83.0 | #368 @artifact, #232 the Notion connector, #257 the docs pass |
v2.84.0 | #339 — the pattern library and one list |
v2.85.0 | #341 — who approved an action run |
v2.86.0 | #370 — the voice gate |
v2.87.0 | #371 — the briefing rebuilt |
v2.88.0 | #372 — full-width record pages |
And one more thing landed on main after the tag: #378, 29b90f44, the rail is the conversation, never a second copy of the page. It is written up here because it finishes the story #372 starts, and it is not in v2.88.0. It will be in the next tag.
Three lines of work, from three people. The product line — the deliverable contract, the connector, the pattern library, the voice gate, the briefing and the record-page work — is one. Recording whether an agent or a person approved a run is another engineer's. The accessibility fix, the render-time ref, the Storybook fixture and the browser-test repairs are a third's. As on every post here since this feed started, the write-ups are published under the team rather than under individual bylines.
What you can do now that you could not this morning
- Have a draft refused before it reaches your review queue because it opens with "Just checking in" — and have the rule that refused it be one your workspace wrote, with the reason attached.
- Cut a phrase out of a draft and see it come back as a proposed rule, with a count of how many times you have cut it.
- Open a morning brief whose length is information: a quiet day is short, because a section with nothing in it is absent rather than rendered as an empty state.
- Read a delta instead of a level — and never see
↑ $0on a first brief. - Type
@artifactand know the turn owes you a document, with a stub in the pane naming what failed if it could not be produced. - Open a search result and read the indexed text of the document, which is exactly what retrieval reads.
- Work a record page at full width, with the conversation lying over it rather than taking a column out of it.
- See a delegation that failed say so — in the answer, in the timeline, and still after a reload.
Feature releases
| Change | Post | Source |
|---|---|---|
| Outbound copy linted against workspace-authored voice rules at three seams; reviewer deletions propose new rules | Outbound copy has to pass your voice rules | #370 |
| The briefing as a typed document: nine sections, an attention budget in code, deltas joined against yesterday, no green status without evidence | The briefing is a list of your decisions now | #371 |
| Search, Needs you, Artifacts and Learnings on one row; a Search page with a measured filter row and a document you can open | One list, and a Search you can read | #339 |
| Full-width record pages, an overlaying rail, an end to nested cards, and a rail that points at the page instead of repeating it | Record pages go full width | #372, #378 |
@artifact as a typed contract on the turn, with a backstop that guarantees a document or an honest stub | Tag @artifact, and the turn owes you a document | #368 |
Developer updates
| Subject | Post | Source |
|---|---|---|
| Why a voice guide produces the tells it bans, and what a twelve-line schema refinement does that a better prompt cannot | The prompt that produced the tells it forbade | #370 |
A decorative <kbd> that joined every decision button's accessible name, and the exact-name test that was right all along | The keyboard hint that renamed every button | 04796795, #367 |
| Two failure shapes from one framework, a trace node stuck at "start", and a hydrate that rewrote every failure as a success | A failed hand-off that never reached you | #368 |
Also in this release
A Notion source connector (#232). Pages and databases, discovered through Notion's search endpoint and paginated to a cap, with page bodies read from the block tree to a bounded depth and a bounded request budget — a page that hits either bound is still emitted, marked as truncated, and reported as skipped content rather than silently shortened. A database contributes its title and description only; its rows are pages, and they arrive if they are shared too. An incremental sync sorts newest-first and stops at the watermark; a full sync sorts oldest-first so the tombstone pass sees everything.
Two details worth naming. The connector deliberately sends no object-type filter to the search endpoint and branches on the result instead, because that filter's accepted values changed meaning between API versions — the version header is configuration, and a connector that hard-codes a filter value is a connector that breaks on a date somebody else chose. And the client is built per call, inside the sync, from the credentials on the context: there is no module-level client and no cached headers, which is what makes two organisations syncing in sequence use two different keys. There is a test that asserts exactly that.
Credentials come from the encrypted per-organisation store or the sync refuses. There is no environment-variable fallback on this path — the fall-through-to-the-server's-key pattern exists for model and paid-tool providers, not for connectors.
No migration, no new environment variables, seventeen unit tests — and every one of them mocks the network. This has not been run against a live Notion workspace, and nothing here should be read as saying it has.
Who approved an action run (#341). A nullable boolean on the action run, written true when the trust ladder released it, false when a person approved, executed or rejected it, and null while it is undecided — a rejection is a decision and it is never an agent's. A re-proposed run clears all three of the decision fields rather than inheriting a stale one, and a person who retries or rejects something an agent auto-approved stamps their own answer outright.
It is queryable — the reviews API takes approvedByAgent=true|false|null and rejects anything else with a 400 — and the adoption overview counts auto-approvals off the decision timestamp rather than off the activity stream. The migration carries a one-shot backfill for runs decided before the column existed, and a partial index on the true rows.
It has no screen yet. Nothing in the dashboard displays it; this shipped as a column, a query and a REST parameter. A post claiming you can see it today would be wrong, so: you cannot.
Three documented traps (#257, documentation only — three files, no code). The migration conventions gained a rule for the collision described below. Contributing gained a section on git worktrees: a worktree does not share node_modules or the environment file, and the environment schema validates on import, so a test run dies during module load with an error that never mentions the worktree; a stale incremental TypeScript cache survives a branch switch and invents type errors in files the branch never touched; a development database shared with another worktree can be ahead of the branch; and macOS has no timeout, so a command copied from a Linux runner fails in a way that reads as the wrapped command failing. And a stale comment in CI claiming the linter always exits non-zero was replaced, since it no longer does.
The migration numbers, which is why that docs pass exists
Three branches were in flight at once, and all three needed a migration on the same day. 0108 went to the approval column. 0109 went to the voice rules. The briefing document had claimed 0108 first, and had to move twice — to 0109, then to 0110 — because an applied migration is immutable, and two files sharing a number means one of them never runs somewhere.
The conventions file now says the rule plainly: renumber the later migration to the tail of the sequence; do not edit the conflict into something that merges. Restore the journal to the base branch's file verbatim, append exactly one fresh entry, and rename the concurrent sibling too, because the applier matches on the filename's first four characters. It also says what does not catch this: the migration check verifies lock safety, not number allocation, and a collision is perfectly legal on each branch in isolation.
The renumber was checked by assertion rather than by eye — every journal tag has a file, every file has an entry, no duplicate index or tag, no two files sharing a numeric prefix, each tag's prefix equal to its index, the entries before the new one identical to the base branch's. Then applied to a fresh database: 111 migrations, with all three of the day's new columns present.
Two of the three PR bodies in that group still quote the wrong number, because they were written before the renumber and were not corrected. The files on disk are the authority, and the posts here cite the files.
How to read these
Each post opens with a status line saying what state the change is in and which commits it is. Every falsifiable claim cites a file, a pull request or a commit, and developer posts end with a "Where this lives in the repo" section.
Where something was not verified end to end, the post says which part — and in this release that list is long enough to collect in one place:
- the voice gate: no live model call is exercised by any of its tests, and the explanation produced when a rewrite fails the gate twice is not read by any component, so the reviewer currently sees an unchanged draft and nothing else;
- the briefing: no live agent has produced a typed briefing end to end. The contract, budget, deltas, redaction and pages are exercised against fixtures and a scripted model. The archive's search filters in the browser over the newest 500 rows and is not a scalable query;
- the Notion connector: mocked throughout, never run against a live workspace;
- who approved a run: data and API only, no screen;
@artifact: the model-backed branch of the backstop is never exercised by a test — every test injects a stand-in — and the contract is not forwarded by one of the managed execution providers;- delegation failures: not reported at all on the out-of-process execution targets, where nesting is not visible across the transport;
- the nested-surface guard: it sees only what renders through the new primitive, which is two component trees. Around thirty other files still hand-roll the same border and the guard is blind to every one of them;
- the browser suite passes serially; under parallel workers two specs still race over organisation-wide approval counts.
One claim from a commit message is quoted in these posts as the author's own observation rather than as a verified figure: the count of lint errors coming out of the Playwright artifact directories cannot be reproduced from the repository, because those directories are generated and were never committed.
Where this lives in the repo
- Core:
vocion/vocion-core.v2.88.0is6829a9bb;mainis29b90f44, one commit ahead. - Pull requests in this release, in merge order: #367, #257, #368, #232, #339, #341, #370, #371, #372 — plus #378 after the tag, and the six direct commits that made up
v2.82.1throughv2.82.3. - Migrations in this release:
0108_action_run_approved_by_agent,0109_project_voice_rules,0110_briefing_document. - The feeds are feature releases and developer updates, summarised on /releases and available as RSS.
/blog/tag/releasestill redirects.