# The release flow (https://docs.vitrinka.ai/pm/release)



## A rolling next-release board [#rolling]

Each project has one rolling **next release** board that accumulates everything merged since the last cut — in every repository in the release (a new track takes every repository bound to the project), each on its own branch. The window is exact: in each repository it runs from the commit that repository's part of the last release ended at, never from a date guess — and the release page composes itself onto the board as the window grows.

The division of labour is deliberate: the AI writes the *prose* — the hero, the ✨ feature, 🐛 fix and 🔧 improvement entries — while the server computes the *facts*: the stats strip, the PR links, the linked boards from what it actually swept. A hallucinated PR count is not possible, because the count never travels through the model. PR links name their repository, so two repositories' #123 never mix.

## What the page shows [#page]

A hero, a stats strip and a merge timeline; then ✨ New, 🐛 Fixed and 🔧 Improved — one entry per feature with its PR links, each to its own repository, at most one screenshot, and up to three linked boards; then Made this sprint — portals to up to 24 boards the window touched. The screenshot comes from up to 24 candidates: the screens captured inside the window and the runner and upload shots on the boards it touched, ranked by visual quality, relevance to the window's PRs and freshness, with near-duplicates removed. A page shows each screenshot once: when two entries pick the same one, it stays with the entry whose PRs and linked boards match the board it was captured on — the entry higher on the page wins a tie — and the other entry runs full width without a picture. Publish current screens before refreshing so the page shows the latest UI.

## Three verbs [#verbs]

**Refresh release** and **Cut release*&#x2A; sit in the Release section of the project's brief once a track exists, and as **↻ refresh*&#x2A; and **✂ cut release** in the next-release board's header. The first sweep starts from a checkout, because it needs each repository's branch and window start:

1. **Start** — `vitrinka project release --refresh --repo <owner>/<name>:<branch>@<commit> …`, one `--repo` per repository. For work that lands on an integration branch, the start is that branch's first-parent fork point from the branch it was cut from, not `git merge-base` (see the release skill). Until the first cut, running it again with another start corrects the window.
2. **↻ refresh** — sweeps GitHub for everything merged since the cursor and hands the window to the release writer. The page appears on the board when the writer answers, and a re-run regenerates the AI's cards in place — it deletes exactly its own previous cards, never anything a human put on the board. Branches and starts are stored on the release, so the button needs no input.
3. **✂ cut** — freezes the board as the release and opens the next window; each repository's next window starts at the head its part of this one ended at. Cutting asks for confirmation and cannot be repeated on the same window.

A pipeline that cuts after a deploy pins the window end to the commit it deployed: `vitrinka project release --refresh --until <sha>` (REST: `"until": "<sha>"` in the refresh body, or per repository in `repos[]`). Only the merges that commit contains are swept, whatever merged after it waits for the next window, and the window's head is the commit itself; the refresh answers `headSha`, so the pipeline checks it before cutting. A commit the repository does not have (`release_until_unknown`), one its branch does not contain or one before the window start (`release_until_outside`) is refused before anything is swept. The pin is not stored: the next refresh without it sweeps to the branch head.

A window start is exclusive: a window that starts at a commit holds only the merges after it, so the PR that commit merged belongs to the release it shipped in, both for a corrected first start and for the window after a cut. Two more facts let a pipeline cut unattended. The release state reports `docHeadSha`, the window head the current page was written for: a refresh that moves the head leaves it behind and one that changes the window otherwise (a corrected start) clears it (`hasDoc` with `docHeadSha` different from `headSha` is a stale page), and the writer's answer to the new sweep sets it to that sweep's head. And a cut can name the head it expects: `vitrinka project release --cut --expect-head <sha>` (REST: `"expectHead": "<sha>"` in the cut body) refuses with `release_head_mismatch`, cutting nothing, when a refresh moved the window since.

Each cut release stays as a frozen board in the project's `releases` subgroup; `vitrinka project release` prints the cut history with PR counts.

<Callout type="info">
  Sweeping needs the GitHub App connected to the workspace with access to every repository in the release. A release track started before a repository was bound keeps its own repositories until a refresh lists them all (`--repo`); the refresh names bound repositories it did not sweep. Without the App, a start or a bound repository, refresh fails naming what is missing.
</Callout>

## The notes document [#notes]

The full release notes are a document of their own — pushed like any artifact — and the release links it: `vitrinka project release --notes <project>/<branch>/<key>` (its set address), `--notes <document link>` or `--notes <card id>`. The release board then shows a preview card of the document beside the stats and the Release section of the project's brief links it; linking another document replaces that one card, and `--notes-clear` removes it. The document must live in the release's own workspace — a link into another workspace is refused. A cut keeps the link on the frozen release; the next window starts without one.

The notes document has a register of its own (`direction: "release"`): chapter-style sections and KPI tiles, screens as a gallery rather than stacked figures, before-and-after numbers as a compare chart, and blocks tagged by audience so one document serves users, engineers and the team — readers pick their audience in the viewer, and the PDF, HTML, Email and Word exports keep it. The release skill writes it that way; the element vocabulary is in the [elements reference](/reference/elements).

## Readiness before and after shipping [#readiness]

The [Overview](/pm/brief) consolidates nested tasks, gates and conditional todos once per identity. Required **Before release** and **Ship** obligations block shipment; required **After release** obligations block closure. An explicit deferral needs both an owner and a trigger, and becomes due when that trigger is met. Optional work is visible without becoming a blocker.

Readiness counts cover the complete selected scope. Paginated API reads carry the first page's scope version; if the scope changes, restart the read instead of combining different versions.

## Exact release candidates [#candidates]

Select a [sprint](/pm/sprints) on the Overview and choose **Prepare candidate**. A candidate pins the scope version, each repository's full revision and every build's platform, kind, exact identity and source revision. An optional digest can identify the build content. You can link existing release notes; the candidate must then match every repository head in that release's sweep.

Changed work, a newer candidate or different linked release revisions makes an old candidate non-current. Prepare a fresh candidate and verify its exact builds rather than reusing old evidence for different work.

Record links to original verification and deployment evidence against that candidate. Only an authenticated human workspace admin can mark evidence verified or **Record shipment**. Select verified deployment evidence for the same candidate and environment; required Before/Ship obligations must be clear. The shipment preserves its candidate, readiness and evidence as an immutable [History](/pm/history) fact. Retrying the same shipment request ID does not record it twice.

Agents and Eve can read readiness, explain blockers, prepare candidates and record unverified evidence. They cannot self-verify or ship. Cutting release notes freezes the notes window; it does not record deployment.

## From the repo instead [#agents]

The same verbs are `vitrinka project release` (`--refresh`, `--cut`, `--notes`) and the release skill (`/vitrinka:release status|refresh|cut`) — see [skills](/dev/skills/overview). Whether a human presses ✂ or an agent runs the cut, it is the same pipeline and the same board.
