Skip to content

Sentryward 0.3.0

Choose a tag to compare

@rdlugs rdlugs released this 31 Oct 16:06
· 109 commits to main since this release
a563b64

Sentryward remains pre-1.0, and this release changes some default behaviour:
review context now follows callers two hops from the changed code, and
changing your password signs out your other sessions. AI findings still need
human verification.

Upgrade notes

  • Back up the database and ENCRYPTION_KEY before upgrading. The server
    applies this release's migrations on start.
  • review.impact_depth defaults to 2, so reviews search for callers of
    callers (within a cap of 8 code searches per review). Set
    review.impact_depth: 1 to keep the previous context.
  • REVIEW_RETENTION_DAYS is optional and unset keeps everything, as before.
    Once a retention period is set (here or under Instance settings), the worker
    deletes and clears older review data hourly, including the findings of
    changes with no activity in that period.
  • GET /api/usage no longer returns recent; API clients that read it should
    use GET /api/usage/recent.

Added

  • list_dir tool for agentic review passes (#144). With
    review.context: agentic, a pass can now list a directory at the change's
    head revision on GitHub and GitLab, e.g. to find a changed file's tests or
    siblings. Paths follow read_file's rules (relative, no ..), directories
    end in / and symlinks in @, a listing shows at most 50 entries, and its
    output is marked as untrusted and counted against the pass's tool budget.
    A directory larger than the forge returns in one request (100 entries on
    GitLab, 1000 on GitHub) is reported as incomplete rather than given a wrong
    count. Listings are cached by head sha like file reads.

  • Phone layout for the Reviews and Findings lists (#165). Below 640px wide
    (phones, or high browser zoom) each review and finding is a stacked card
    instead of a table row, so the lists reflow at 320px without scrolling
    sideways. Cards keep every column's value, the row menu and click-anywhere
    to open; a review's earlier runs expand inside its card, and a finding's
    change title is written out rather than left in a tooltip. Findings get a
    "Sort by" picker and a direction toggle in place of the sortable headers.
    Long active-filter chips truncate instead of widening the page, and the
    pagination's "Rows per page" label reads "Rows" on phones so its controls
    wrap.

  • Command palette and a global "Review a PR" action (#161). Ctrl+K (Cmd+K
    on macOS, where plain Ctrl+K stays the text fields' "delete to end of
    line") opens and closes a palette from any dashboard page, also from the top
    bar's Search button: it jumps to any page (admin pages only for admins, plus
    Settings), lists repositories and opens a repository's reviews, starts a
    manual review, and queues a review for a pasted pull or merge request link.
    It does not open over another dialog, sheet or menu, says when repositories
    are still loading or could not be loaded, and closing it while a link is
    being queued leaves you on the page with a "Review queued" toast to open it.
    A "Review a PR" button in the top bar opens the manual review sheet from
    every page; Home and Reviews open that same sheet.

  • Keyboard navigation and severity jump for findings on the review page
    (#157). Ctrl+J/Ctrl+U move between findings in display order, Ctrl+O
    or Enter expands or collapses the focused one, Ctrl+C copies its agent
    prompt (or the selected text, when there is a selection), and Ctrl+? opens
    a dialog listing the shortcuts. Shortcuts are ignored while typing in a
    field and inside menus and dialogs, and Cmd is left to the browser. A sticky
    "Jump to" bar under the header shows per-severity counts of the visible
    findings and focuses the first finding of a severity; it also opens the
    shortcuts dialog. Focus from the keyboard, a jump, Tab or a click shares one
    position, and the focus ring stays visible after a mouse-started jump.
    Ctrl+O and Ctrl+C only act while a finding has focus, a shortcut with
    nothing to act on is left to the browser (so its own Ctrl+J and Ctrl+O
    still work, while Ctrl+U never opens view source on the review page),
    Ctrl+? follows the active keyboard layout, and a jump clears the bar even
    when it wraps on narrow screens.

  • Key metrics on Home (#154). Once the workspace has a review, Home shows
    four tiles above the setup checklist: open critical/major findings, reviews
    and failed runs in the last 7 days, and estimated spend over the last 30
    days. Each links to the matching list (open findings sorted worst first,
    runs, failed runs, Usage on the 30-day window), loads with its own skeleton,
    and shows "-" if its stats fail to load. Stat takes a linkLabel so a
    linked tile can name the page it opens.

  • Progress for running reviews (#156). The worker records the current stage
    on the job (new progress column on review_jobs): fetching the change,
    preparing, reviewing (with n of m passes, and planning, context and
    walkthrough as details), checking findings and publishing, listing only the
    steps the run's config turns on. GET /api/reviews/:id returns it, and the
    review page shows a step list with elapsed time instead of a single spinner
    line (queued reviews show how long they have waited, and a job requeued
    after its worker stopped says it is waiting to retry). Until the run's
    config is known only the current step is listed, so the list only grows.
    Writes are best effort, coalesced, and only made by the attempt that holds
    the job; a new claim clears them, and every run waits for its last write
    before returning.

  • Privacy controls (#109). A new "Privacy and data" section of the README
    lists what Sentryward stores, what is sent to model providers and forges,
    and how long it is kept. Instance admins get an Instance settings page
    (GET/PUT /api/admin/settings, new instance_settings table) and
    workspace admins a Privacy page (GET/PUT /api/privacy, new
    workspace_privacy table) to restrict model requests to chosen providers
    and hosts (exact names, *.example.com wildcards, optional ports) or to
    local endpoints only (hosts that resolve to private or loopback addresses).
    A workspace can only narrow the instance policy. The policy is checked
    before a review posts anything, for every model it may call, including
    ones named in .sentryward.yaml: a refused review, walkthrough or verify
    model fails the review with a Blocked by privacy policy: reason, a
    refused fallback is skipped with a warning, thread answers post a short
    notice instead, and LLM connection save, test and model-list requests to
    a refused host are rejected before they are sent (the connection shows as
    "Blocked by policy"). Retention is now configurable from the dashboard
    (instance period, falling back to REVIEW_RETENTION_DAYS, which workspaces
    can shorten) and also purges the findings, posted-comment records and
    finished thread replies of changes with no activity for longer than the
    period. Every outbound model request (reviews, walkthroughs, verification,
    capability probes, thread answers and connection tests) is recorded in a
    new llm_request_logs audit table with its workspace, repository,
    purpose, provider, host, model, token counts, latency and outcome, never
    prompts, code or error text. Admins can browse, filter and export it as
    CSV or JSON on the Privacy page (/api/privacy/audit-log) and, for every
    workspace, in the Audit log tab of Instance settings
    (/api/admin/audit-log), and an instance audit retention period removes
    old entries.

  • Cross-file impact analysis (#108). Review context now follows callers of
    changed code more than one hop (review.impact_depth, 1-3, default 2, 1 in
    the fast profile; also in the dashboard under Findings > Review context):
    each caller's enclosing function becomes the next name searched, within a
    shared cap of 8 code searches per review (generic method names such as
    run or handle are not traced), and further hops are the first context
    dropped when a pass is out of room. Callers of declarations the diff
    changes or removes, including parameters on later lines of a multi-line
    signature, are searched first and labelled signature changed /
    removed, and the review prompt asks for broken contracts to be reported
    with evidence: contract, the affected callers in impact and their files
    in evidenceFiles, which the summary and inline comments show as
    Affected for contract findings. The walkthrough lists risky areas
    (authorization checks, database writes and schema changes, API routes, and
    route handlers or auth modules reached through callers; test files never
    count) in the summary comment and the dashboard's walkthrough card, both
    hidden by output.walkthrough_blast_radius, and they raise its blast
    radius: at least medium for any risky area, large only for a removed
    auth check. Reviews stored before review.impact_depth existed still allow
    an incremental review on the next push.

  • Model and review-cost benchmarking (#107). Workspace admins can set their
    own model prices under LLM Connections > Model prices (GET/PUT /api/settings/model-prices, DELETE /api/settings/model-prices/:id,
    new model_prices table); a review's calls are priced at them when it
    runs, ahead of the built-in table. A model nothing prices now records no
    cost and is flagged unpriced, so its cost reads as unknown instead of $0
    on the Usage page (totals, table and recent reviews), the Reviews stats
    strip and the review sidebar; the digest's usageTotals, review stats
    tokens and recent usage rows carry unpricedCalls for it. Ollama models
    stay free unless priced. A workspace price keyed by any prefix-stripped
    form of a router id (cx/gpt-5.6-sol for openai/cx/gpt-5.6-sol) applies,
    and the price list's models carry the price reviews get now, so the
    "no price" warning clears for them. A saved price's model id can't be
    changed when editing. The
    review digest now keeps per-model call time and unpriced call counts, and
    the number of findings dropped as duplicates. A Benchmarks page (GET /api/benchmarks) compares, over a period and optionally one repository,
    cost per review, tokens and mean call time per model, cost, median duration
    and duplicate rate per repository, and confirmed and false-positive
    findings per model by language and by category, naming the best model
    among those with enough decided findings. Rates are over decided findings
    (confirmed plus false positives), as in the calibration stats, and cost
    per review is over the reviews that called a model. Price overrides are
    loaded before a review's model calls. Reviews from before this change have no
    call time or duplicate count and are left out of those figures. Closes #107.

  • Extended thinking, agentic context and model capabilities (#139).
    llm.thinking (off, low, medium, high) and llm.verify_thinking
    let review and verification calls reason first, mapped to each provider's
    own option on native and proxied endpoints, with the budget added to the
    output limit and the prompt ceiling, and temperature left out. The strict
    profile thinks high. review.context: agentic lets a pass read files and
    search code through read-only tools at the head revision before reporting
    through a report_findings tool, within review.agent caps; what it read
    reaches the verifier, and usage records steps and tool calls. What each
    model supports on each connection (thinking, tools) is probed when a model
    is tested or first used (in parallel, with a timeout, and an inconclusive
    probe is retried after a day rather than before every review), corrected at
    runtime when an endpoint refuses an option or a custom endpoint ignores
    thinking options (one retry without it, a review warning), shown under a
    connection's Capabilities, and overridable in Configuration > LLM
    (dashboard only). New endpoint GET /api/settings/api-keys/:provider/capabilities.
    The eval runner gains --thinking, --context and --compare, and three
    cross-file cases.

  • Estimated model cost for reviews. Each model call now records its prompt
    cache reads and writes and reasoning tokens alongside input and output, and
    its cost, computed when the call is made from a built-in price table (exact
    model, then the id without its provider prefix or router effort suffix, then
    wildcard patterns; a model nothing matches costs $0, as do Ollama models,
    which run on the user's own hardware). A model attempt whose reply could
    not be used still counts its billed tokens, recorded as a failed call, so
    the estimate includes spend a fallback model was needed for. Costs are stored with
    the review, so a later price change leaves past reviews as they were, and
    reviews from before this record none. GET /api/reviews/stats sums them into cost, review
    summaries carry usageTotals.costUsd and per-model costs, the stats strip
    gains a Tokens tile and the review sidebar an Estimated cost row. Closes #87.

  • A Usage page (GET /api/usage) for code-review model spend: totals with
    input, cached and output tokens and the estimated cost, a tokens / calls /
    cost chart over 24 hours to all time, usage by provider and top models, the
    latest reviews that called a model, and a table by model or by repository
    showing costs (input, cached, output) or tokens. The Reviews page's Tokens
    tile links to it. Charts use recharts, and the chart colour tokens are now a
    colour-blind-checked palette in both themes.

  • REVIEW_RETENTION_DAYS to stop review data growing without bound. When set,
    the worker hourly deletes superseded, skipped and cancelled reviews older
    than the window and clears the stored result, resolved configuration and
    publication of older finished ones, keeping their row, verdict and summary so
    lists, stats and the findings list are unaffected. The newest completed or
    partial review of each change is never cleared, so its findings' details,
    inline-thread answers and incremental reviews keep working. Pruning runs in
    batches. The review page says when a result has expired and its markdown
    export returns 410.
    Unset keeps everything. Includes a migration (a result_expired_at column
    and an index on review jobs). Closes #86.

  • Notifications for finished reviews. Workspace admins add targets under
    Notifications: an outgoing webhook signed with an HMAC-SHA256
    X-Sentryward-Signature header (the secret is shown once), a Slack
    incoming webhook, or email to chosen members through the existing mailer.
    Each target picks its events (blocked, failed, completed), can send a test,
    and gets one delivery per review. The worker sends them best effort once a
    review finishes, fails or times out: a failed delivery is logged and never
    fails the job. URLs are stored encrypted and go through the same production
    host checks as self-hosted forges. Includes a migration (a
    notification_targets table and a notified_at column on review jobs).
    Closes #85.

  • Personal API tokens for scripts and CI. Create them under Settings > API
    tokens, with a name and an optional expiry. The value is shown once and only
    its hash is stored. Requests send it as Authorization: Bearer <token> and
    act as the token's user, with the workspace header choosing the workspace.
    A token can read anything its user can and queue or rerun reviews; other
    writes, and managing tokens, need a signed-in session. Revoked or expired
    tokens get 401, token requests share their user's rate limit, and deleting
    the account deletes its tokens. Includes a migration (an api_tokens
    table). Closes #84.

  • Password reset. "Forgot password?" on the login page emails a single-use
    link that expires after 1 hour, and the request answers the same way
    whether or not the email has an account. Without mail configured, the page
    asks the user to contact an admin, who can create a reset link (single use,
    24 hours) from the user's actions on the Admin page, with or without mail.
    Setting a new password signs the user in and ends every other session.
    Includes a migration (a password_resets table and a session_version
    column on users). Closes #83.

  • Findings are classified across runs as new, still open, reopened, fixed
    or invalidated. Each review after a change's first records this in its
    result (lifecycle), and the summary comment opens its findings with a
    "Since the last review" line and a list per group, at most 20 entries each
    (output.finding_lifecycle turns it off). A finding counts as invalidated
    rather than fixed when its file was renamed, deleted or dropped from the
    change, or the same problem now appears in a file the change adds or
    renames and is the only one of its kind (that finding is still open, not
    new). A finding resolved earlier and reported again is reopened. Findings
    carry a lifecycle column, GET /api/findings filters by lifecycle, the
    findings page gains a "Last review" filter, and invalidated findings no
    longer count as resolved in the stats. Includes migrations (a
    finding_lifecycle enum and column, backfilled from each finding's state,
    and an end_line column so gone findings keep their line range). Closes
    #104.

  • Structured evidence on findings. The model now returns a finding's
    impact (what breaks, and for whom) and fixRationale (why the suggested
    fix works) as separate fields instead of folding them into the body; inline
    comments, the summary comment, agent prompts and the dashboard show them
    under their own headings. Each finding also records what review.verify
    made of it (verified, uncertain or not_checked): forge comments mark
    verified and uncertain findings, and the dashboard shows a badge for all
    three. Results stored earlier have none of these and read as not checked.

  • GET /api/findings/calibration?repoId=&days=&groupBy= measures how the
    confidence models self-report has matched outcomes: findings bucketed by
    reported confidence in tenths, with confirmed (fixed) and false-positive
    rates over the findings that were decided, overall or by model or
    category. The False positives tab gains a card showing it. Includes a
    migration adding a confidence column to findings, backfilled from each
    finding's last run (null for tool and rule findings). Closes #105.

  • Team rules in .sentryward.yaml. A rules list states a convention once,
    with an id, description, severity and optional category, paths
    globs and good/bad examples, read from the base revision like the rest
    of the file. A rule with a
    pattern (an RE2 regular expression, matched in linear time) is checked
    against the change's added lines without a model, so the same diff always
    gives the same findings; the rest are given to the model, and a violation it
    reports is filed under the rule with the rule's severity. Both are reported
    with source rule and the rule's id, block at review.block_on, are
    reported whatever review.categories holds, and are marked with the rule in
    forge comments and the dashboard. The False positives tab gains a per-rule
    table, and a change to the rules makes the next push a full review. Includes
    a migration adding a rule_id column to findings. Closes #106.

  • Team rules in dashboard settings. The global config (every repository in
    the workspace) and repository settings can hold a rules list, edited on
    the Configuration page's Guidance tab. Unlike other lists, rules combine
    across layers in the order global config, repository settings,
    .sentryward.yaml: a later layer can add rules but never remove or override
    an earlier one, so a rule reusing an id is ignored with a review warning,
    and a layer whose rules would take the combined list past the limits is left
    out as a whole, with a warning. Repository settings whose rules clash with
    or do not fit beside the global ones are refused on save, and the global
    config lists repositories whose rules it leaves out. Dashboard rules keep the
    dashboard's severity_floor and min_confidence (a repository file can only
    lower them for those rules), and their patterns still run on files only the
    repository file ignores. The effective config names each rule's layer
    (sources["rules.<id>"]). The editor checks patterns with the server's RE2
    as they are typed (POST /api/config/rules/check), and inline comments for
    rule findings include the rule's description and paths, since dashboard
    rules are not visible in the repository. Closes #140.

  • Notification targets can be paused. Admins switch a target off from the
    Notifications list (PATCH /api/notifications/:id with active) to stop
    its deliveries while keeping its name, events, recipients, URL and signing
    secret, and switch it back on later. Reviews that finish while a target is
    paused are never sent to it, even after it is resumed. The list sorts and
    filters by status, paused targets still accept test deliveries, and members
    see the switch but cannot change it. Existing targets stay active (migration: an active column on
    notification_targets, default true). Closes #130.

  • Actionable states for reviews that did not succeed (#158). A failed,
    cancelled or skipped review offers Re-run in its alert (not while the
    repository's reviews are off, by hand or after lost access, since the server
    refuses it), plus a link to
    the page that fixes the cause: LLM Connections for a missing, removed or
    mismatched key, the review settings when no connection is selected, the
    Installation tab for refused forge credentials or lost access, Repositories
    for reviews turned off, Privacy for a blocked model, and the repository's
    Triggers tab for a skip (noting when .sentryward.yaml sets the trigger).
    GET /api/reviews/:id now returns errorKind, derived from the stored
    error when read (including errors recorded as a failed review pass), so
    older reviews get one too and no migration is needed, and
    repository.enabled, which decides whether Re-run is offered.
    "Review not found." links to the reviews list.

  • Unsaved-changes guard on the Review settings, Privacy and Profile forms
    (#163). Leaving a form with unsaved edits (a link, Back, switching the
    Review settings scope from the picker or any link, or a Settings or
    Instance settings tab that hides the form) asks to keep editing or discard,
    and reloading or closing the tab shows the browser's prompt. Switching the
    Review settings tabs never asks. While there are edits, the Review settings and Privacy save bars
    pin to the bottom of the page, and Review settings gains a Discard button.

  • Each Review settings field tags its effective value with where it comes
    from: default, profile: <name>, global, repo override or the
    repository file (#164). In a repository's scope, a field the repository
    file sets is locked, with a tooltip saying the file wins over the dashboard.

  • Sidebar attention indicators (#162). Reviews shows a count of runs that
    failed since you last opened the list (the last 24 hours before your first
    visit), counting only a change's latest run, as the list's default view
    does, and LLM Connections and Repositories show a warning dot when a
    connection is revoked, unreachable, blocked by policy or unreadable. The
    sidebar starts no credential checks of its own: it shows stored flags and
    the checks those pages and Home already ran. The
    state is part of each link's accessible name and tooltip, and a collapsed
    sidebar keeps a dot on the icon. New GET /api/reviews/attention and
    POST /api/reviews/viewed endpoints, backed by a new
    memberships.reviews_viewed_at column, track the visit per member and
    workspace, so it clears on every device. Leaving the list on a workspace
    switch records the visit in the workspace that was open.

  • Drag-to-reorder connections. Workspace admins can drag a connection card by
    its handle (or move it with the keyboard: Space, arrow keys, Space) to set
    the order everyone in the workspace sees. The order is saved by the new
    PUT /api/connections/order endpoint in a new forge_connections.position
    column; connections added later go after the ordered ones. Reordering is
    off while searching or filtering, since the order belongs to the whole list.
    Quick successive drags are saved one after the other, so the last order
    chosen is the one stored, and overlapping saves (two admins at once) wait
    for each other instead of failing. A save refused because the list changed
    says so rather than showing a generic error.

  • "Reconnect" on a connection whose credentials the forge rejected. It opens
    the connect sheet with that connection's provider, hosting option and host
    already filled in, so connecting the same account again resumes automatic
    reviews and keeps its repositories and history.

  • Drag-to-reorder repositories, the same way as connections. The order is
    saved by the new PUT /api/repos/order endpoint in a new
    repositories.position column. GET /api/repos still lists by path (the
    pickers elsewhere rely on that) and now returns each repository's
    position, which the Repositories tab sorts by, with unplaced ones last.

  • Each repository card shows its last review: the verdict (or status while it
    is queued, running or failed), the pull or merge request number and how long
    ago, linking to the review. The new GET /api/repos/last-reviews endpoint
    returns them, read in one query, so pages that only list repositories don't
    pay for it; skipped and superseded runs don't count. A new
    review_jobs (repository_id, created_at) index serves it.

Changed

  • The Installation tab lists connections as a grid of cards instead of a
    table. Each card shows the account, host, connection type, status,
    repository count and who added it and when, with "Manage repositories" and
    "Details" buttons; the menu keeps Details and Remove. Column sorting and
    pagination are gone from this tab, replaced by the saved drag order, and
    it shows skeleton cards while loading instead of a blank area. Its search,
    filter and "Add connection" controls sit on the left, in that order.

  • The Repositories tab lists repositories as a grid of cards instead of a
    table, with the same left-aligned search, filter and "Add repository"
    controls. Each card shows the path (linking to the forge), account, default
    branch, connection status, whether it uses global or custom settings, its
    last review and the review switch. Column sorting and pagination are gone
    from this tab, replaced by the saved drag order; it shows 24 cards at a
    time with a "Show more" button. Turning reviews on or off now only locks
    that repository's switch while it saves, not every switch.

  • LLM Connections shows every provider as a card, connected or not, instead
    of a table of stored keys with search, filters, sorting and pagination. Each
    card states what its reviews use (your key, the server's key, or nothing),
    explains a problem in visible text and offers the action that fixes it:
    "Replace key" for a rejected or unreadable key, "Check again" for one that
    could not be verified, and a link to the privacy policy (with "Connect"
    disabled) for a provider it blocks. "Connect", "Use my own key" and
    "Replace key" open a sheet for that card's provider (no provider picker),
    with a link to where the key is created and the custom host under
    "Advanced". Testing a model and the capability list move into one Details
    sheet per connection. Cards show when the status was last checked and can
    check it again, the page header sums up how many providers are connected
    and how many need attention, and the page shows placeholders while loading
    and a retry on error. The "Inactive" and "Unknown" statuses read "Key
    rejected" and "Couldn't verify". The cards are laid out like the
    Repositories page's, and workspace admins can drag them by their handle
    (or move them with the keyboard) into an order saved for the workspace.

  • Redesigned login, sign-up, password reset and invite pages. On wide screens
    they split into a product panel (what Sentryward does, light or dark with
    the theme) beside the form; on phones only the form shows, under the logo.
    Password fields get a show/hide toggle, sign-up and reset show a live
    strength meter and a "Passwords match" check (advisory, the 8-character
    minimum is still the only rule), inputs carry icons, and the first field
    is focused. Errors that aren't about a field, like wrong credentials,
    appear as an alert in the form instead of a toast, and the first user's
    "you will be admin" note is an info alert. The product panel plays an
    example review on a loop: a small diff gets an inline finding (severity,
    title typed in, explanation and a suggested change), then the
    sentryward/review status turns into a verdict, across three examples.
    Behind it, a slow brand-blue waveform drawn as dithered vertical bars
    (wider where it is brighter) drifts across the right of the panel, fading
    out under the text. Both only run while the panel is on screen and the tab
    is visible, and hold still with reduced motion.

  • The review page header groups "Copy prompt for all findings", "Copy
    markdown" and "Download markdown" into an Export menu (#155), leaving Re-run
    (and Cancel while the review is active) as the only buttons. Items keep
    their disabled states, and the Export button shows a spinner while a
    markdown copy or download is still running.

  • Rows in the Reviews and Findings tables open on a click anywhere outside
    their controls (#160), not just on the title. The title stays a real link
    (or button), so middle-click and Cmd-click still open a new tab, and the
    runs toggle, forge link and row menu do not open the row. Modified clicks
    and clicks that end a text selection are left alone.

  • The Reviews and Findings lists keep their filters, search, view and sort in
    the URL (#159), so Back from a review restores the list and a copied link
    reproduces it. Values the page does not know fall back to defaults. Each
    Findings tab keeps its own (?fpSeverity= and so on for false positives),
    switching tabs keeps both, and a review's "Back to reviews" link returns to
    the filtered list it was opened from, also after a re-run from the review.

  • The client uses a data router (createBrowserRouter) so forms can block
    navigation; routes still live in App.tsx.

  • Home replaces the status hero with a setup checklist built from live
    data: connect GitHub or GitLab, enable a repository, connect an LLM
    provider (with the same usability checks the hero had) and run a first
    review. Finished steps are ticked, the next one gets the primary button,
    and the checklist disappears once every step is done. Only a completed or
    partial review counts as the first one (skipped, failed and cancelled runs
    don't), and its "Review a change" action waits until the steps before it
    are done. A list that fails to load counts its step as done rather than
    asking for something that may exist. Suggested actions no longer list
    setup steps: "Can't reach your LLM provider" appears while a key fails its
    status check (instead of reopening setup), "Review a change" after the
    first review, and "Turn on automatic reviews" once setup is done but only
    requested reviews run. Closes #153.

  • GET /api/settings/api-keys reports blocked per provider: whether the
    privacy policy refuses that provider or its host, whichever key is used.

  • The dashboard sidebar is split into labelled groups: Activity (Reviews,
    Findings), Insights (Usage, Benchmarks), Workspace (Repositories, LLM
    Connections, Review settings, Notifications, Privacy, in setup order) and,
    for instance admins, Administration (Users, Instance settings). This
    replaces the "User Management" collapsible. The current team's page moved
    from the sidebar to a "Team settings" item in the workspace switcher (team
    workspaces only). The breadcrumb now starts with the page's group, and the
    Configuration page is renamed Review settings (its /configuration URL is
    unchanged).

  • The Privacy page's Policy tab shows the workspace policy form with an
    "In effect" summary beside it (the stricter of the instance and workspace
    policies; stacked on narrow screens). The policy form (also used by
    Instance settings) is split into "Where code can be sent" and "Data
    retention" sections. Allowed providers is a multi-select dropdown in
    which providers the instance blocks are disabled, retention has a "days"
    unit and presets, retention above the instance limit is caught before
    saving, and a Reset button undoes unsaved changes. Members see a note
    that only owners and admins can edit.

  • Model prices moved to their own tab on the LLM Connections page
    (/llm-connections?tab=prices), next to the Connections tab. The
    Benchmarks page's "Unknown" cost link opens that tab.

  • The Delete account card moved to the bottom of the Profile settings tab
    and the Account tab is gone. Old /settings?tab=account links open the
    Profile tab. Closes #146.

  • Changing your password in Settings now signs you out on other devices,
    keeping only the current browser signed in. Existing sessions stay valid
    through the upgrade.

  • The Reviews page's changes view counts its total with one SQL query
    instead of loading every pull/merge request key matching the filters, so
    page loads and polls no longer grow with the whole review history. Totals
    are unchanged for every filter. Closes #89.

  • Copying and downloading a review's markdown share one helper on the
    client, so the review page and the Reviews table row menu fetch it, report
    failures and track pending state the same way. The row menu's copy now
    goes through the same clipboard path as the review page. Closes #95.

  • Confidence is labelled as self-reported wherever it appears: the dashboard
    shows "Self-reported confidence" instead of a bare percentage, the minimum
    confidence setting says it is the model's own estimate, and forge comments
    still never show it.

  • The Usage page paginates its lists. Recent reviews now pages through every
    review that used a model in the period instead of only the latest 10. It is
    served a page at a time by the new GET /api/usage/recent (page, limit
    up to 100), and GET /api/usage no longer returns recent. It and the
    Usage by Model / Usage by Repository table page separately
    (?recentPage=, ?usagePage=). The table sorts across every row before
    paging. A new period sends both lists back to page 1, and so does a new
    sort, view or Costs/Tokens mode for the table. Each table's rows-per-page control now has
    its own id, so two on one page keep their labels. Closes #132.

Fixed

  • The "Sign up" link could disappear from the login page.
    GET /api/auth/registration shared the login rate limit (20 per 15
    minutes), and every auth page load reads it, so reloading those pages
    could exhaust the allowance; it now uses the general API rate limit.

  • The home status no longer says "Reviews are on" when the workspace has no
    usable LLM connection: it asks for one first. A server-provided key counts
    as usable unless the workspace stored its own key for that provider (reviews
    use the stored one, so a revoked stored key counts as missing) or the
    privacy policy blocks the provider. A provider that does not answer its
    status check gets its own "Can't reach your LLM provider" message instead.
    Closes #152.

  • An open finding in a file the change later deletes is now resolved (as
    invalidated) instead of staying open forever: a deleted file is never
    reviewed, so the run never got to say the finding was gone.

  • Webhook deliveries for one PR/MR that arrive at the same moment (two quick
    pushes, a push and a /sentryward review comment, or a redelivery) queue
    one review instead of reviewing the same head twice. Enqueues for a change
    are serialized, and the close cancel and stale-job recovery follow the same
    rule: a recovered job is superseded when a newer one is already queued.
    No migration needed. Closes #82.

  • A token or OAuth connection whose credentials the forge refuses (revoked
    or expired token, withdrawn OAuth grant, a token the server can no longer
    decrypt) no longer queues a failing review on every push. Sentryward
    records the refusal, either from the connection status check or from a
    review that hits a 401/403 confirmed by an account check. It then stops
    queueing reviews from that connection's repository hooks, skips webhook
    reviews already queued, and shows "Automatic reviews are paused" on the
    Repositories page. Connecting the same account again with a new token (or
    signing in again) resumes reviews and keeps the history, as does a status
    check that passes. Manual reviews still run. Includes a migration (one
    nullable column). Closes #103.

  • Searching reviews by title ignores case, like the repository path search,
    in both the runs and changes views: fix login now finds "Fix login
    redirect". Includes a migration that adds a title column to review jobs
    and backfills it from each review's summary. The worker stores the title as
    soon as it fetches the change, so skipped, cancelled and failed runs are
    found by title and show it in lists too. A second migration adds trigram
    indexes (pg_trgm) on review titles and repository paths so the search
    does not scan the whole table. Closes #88.

  • Client tests for the markdown download: the success test now waits for
    Download markdown to be enabled again, so a stuck spinner fails it, and the
    object URL stubs are restored after each test instead of leaking into later
    ones. Test-only, no behaviour change. Closes #90, closes #91.

Full changelog: v0.2.0...v0.3.0