Commit Graph
8578 Commits
Author SHA1 Message Date
Merge Sim e980959aef test(codex): type the dispatch fixtures instead of asserting over them
main's new casting gate (#20367 base) flags type assertions on changed
lines. Replace them with checked types: the recording sink already
satisfies its interface, both CodexSession fixtures are now annotated and
carry real collaborators, the settlement assertion compares whole
identities, and the integration helper reads submissions through the
host's public journalSnapshot instead of its private session map.
2026-09-13 13:39:20 -07:00
Merge Sim 69f14aed95 Merge origin/main into brennanb2025/codex-admission-settlement-2 2026-09-13 12:00:08 -07:00
Jinwoo Hong a1d135e233 fix(ai-vault): re-read a transcript rewritten to its previous size (#20261) 2026-09-13 11:08:12 -07:00
fe4237cd41 fix(agent-hooks): let the provider, not a keystroke, end these turns (#20149)
Escape is ambiguous at the source for Claude, OMP, Pi and Prime Agent: the same
key closes an overlay and cancels a turn, and which one it meant is focus state
only the TUI holds. Nothing downstream can recover it, so for these agents a
plain Escape is never evidence a turn ended — the provider's own hook decides.
Ctrl+C is untouched, and no other agent type changes.

The renderer skips the round-trip and main re-checks the same rule, so a stale
or direct inference request cannot route around it. A navigation Escape does not
clear a Ctrl+C already waiting to settle: Escape is not a retraction.

Fixes #13547
Fixes #9208

Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-09-13 00:01:33 -07:00
e9065ddd16 fix(runtime): rank tui-idle evidence instead of inferring idle from silence (#20155)
`terminal wait --for tui-idle` returned satisfied in ~0s while an agent was
mid-turn. The shared title detector defaults a name-only agent title to `idle`
so the sidebar can clear a stale spinner, and the wait accepted that stored
value as completion.

Rank the evidence instead. An explicit idle marker in the agent's own title or
a known ready prompt settles the wait; a fresh first-party OSC 9999 status
saying working/blocked/waiting vetoes it; a name-only title is a last resort
that settles only once the stream has also gone quiet. The rank is derived at
read time from `lastOscTitle` rather than stamped onto the record, because
`syncWindowGraph` rebuilds leaves from an explicit field list and would drop a
bespoke provenance field on any renderer publish.

Two things the ranking alone gets wrong are handled here too. A quiet non-shell
foreground process no longer proves idle on a pane where Orca launched a known
agent — that is an agent still booting, and resolving on it is what let
`dispatch --inject` lose the prompt (#9976). And the idle poll re-reads the live
leaf each tick, because a record captured at registration stops advancing and
its frozen `lastOutputAt` makes the quiescence gate pass while the pane streams.

The demotion is scoped to agents that go on to announce rest explicitly. Grok,
Copilot, Aider, Mimo, agy and OpenCode emit their name and nothing more at rest:
a real idle Grok pane repaints its banner about four times a second forever, so
demanding quiescence from it left no settle signal at all and the wait ran to
timeout.

The design is Brennan Benson's, from #14642, which won a cross-review against
#6012, #6555 and this branch's earlier approach; it is ported here only because
that branch shares no git history with main and cannot be merged. Neil's #6555
first drew the explicit-vs-ambiguous line the ranking rests on, and Revofusion's
#6012 first identified that a single title sample cannot prove completion.

Fixes #6011

Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Revofusion <syed@moonai.org>
2026-09-12 23:16:59 -07:00
Neil 8759b25e07 fix(automations): isolate the scheduler tick and refuse oversized cron steps (#20152)
Two defects that change nothing about when an existing schedule fires.

#16303: evaluateDueRuns awaited each row with no catch, so one unreadable schedule
skipped every later due automation in that tick. Each row is isolated now; a poison
record writes one folded skipped_unavailable run explaining itself and the tick
continues. A renderer send that throws is closed out as dispatch_failed rather than
mislabelled as an unreadable schedule.

#15895: step validation only checked integer >= 1, so a step wider than its field
degraded silently to a single value and still passed validation. Oversized steps are
refused at input time only, bounded by the count of distinct values a field holds, so
day of week rejects */8 while */7 stays legal.

Runtime parsing stays lenient so rows saved before the gate keep running the cadence
they have. isValidAutomationSchedule now answers only 'acceptable as new input'; a new
isRunnableAutomationSchedule answers 'can Orca still run this', and the editor uses it
so a legacy row opens intact and can be renamed without re-authoring a schedule that is
still firing.

Verified: 34/34 corpus expressions fire identically to main.

Fixes #16303
Fixes #15895
2026-09-12 22:56:23 -07:00
599e669375 fix(skills): evict removed runtime discovery cache (#11489)
* fix(skills): evict removed runtime discovery cache

* fix(skills): retire removed runtime cache entries using pending scan identity

* fix: rescan mounted skill consumers when a runtime re-pairs under the same id

- Fold the pairing revision into useActiveSkillDiscoveryRuntimeTarget's
  selector so a same-id re-pair yields a new runtime target and every
  mounted useInstalledAgentSkillNames effect re-runs instead of holding
  the retired peer's installed list after the module cache is evicted.
- Reset hook-local result/loading state on runtime target identity change,
  which also bumps the refresh generation so an in-flight scan issued to
  the retired peer can no longer commit its result into React state.
- Add mounted-hook regression tests covering the re-pair rescan and the
  in-flight stale-scan fence.

* fix(skills): reset discovery state via render-adjusted state, not a ref write

React Doctor flagged the render-phase write to stateResetInputRef. React can
discard a render after the write, in which case the next render sees "already
reset" and keeps painting the previous target's skill list until a rescan.
Track the reset inputs in useState and adjust it during render instead, which
React replays safely.

Also drop the `as never` / `as GlobalSettings` casts from the tests this PR
added, since main now enforces consistent-type-assertions on changed lines.

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Neil <neil@stably.ai>
2026-09-12 22:38:29 -07:00
6c1d95b0da perf(tooling): reuse directory entry types in source scans (#20212)
* perf(tooling): reuse directory entry types in source scans

* fix(source-scan): stat DT_UNKNOWN dirents so untyped directories are still walked

`readdirSync(..., { withFileTypes: true })` can hand back a Dirent whose
type the filesystem did not report. For that entry every predicate is
false, so the readdir-type fast path treated a real directory as a file
and silently dropped its subtree from every ratchet guard. Fall back to
`statSync` whenever the entry is neither conclusively a file nor a
directory, keeping the no-stat fast path for ordinary entries.

Also make the two readdir-order assertions in the walk test
order-independent; `scanSourceTree` returns raw readdir order, which
differs on tmpfs.

* test(source-scan): unit-test the stat fallback via an extracted helper

The fabricated-Dirent readdir mock could not satisfy both gates at once:
vi.mocked(readdirSync) resolves to Node's Dirent<NonSharedBuffer> overload, so
the mock needed a type assertion, and #19462's casting gate rejects new ones on
changed lines. Removing the cast then failed tsc.

Extract directoryEntryNeedsStat and test it directly with a structural probe.
No mock, no cast, no top-level await, and the DT_UNKNOWN case is pinned:
removing the fallback fails 'stats an entry whose type readdir could not report'.

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Neil <neil@stably.ai>
2026-09-12 22:38:25 -07:00
Neil f2b6434fe6 perf(ai-vault): bound per-row bookkeeping in unlimited session scans (#20291)
* perf: deduplicate unlimited vault scans once

* perf: release discarded vault aliases during unlimited scans

* fix: bound per-session bookkeeping in unlimited vault scans

- Drop the per-session alias-key string, wrapper object and positions array
  the accumulator retained for every parsed row; index winning positions by
  the row's own sessionId instead (~430 B -> ~45 B per session at 50k rows).
- Add a --expose-gc retention test asserting a 50k mostly-unique load-all
  corpus stays under 128 B of bookkeeping per session while matching
  dedupeCodexSessionsBySessionId exactly.

* perf(ai-vault): bound per-row bookkeeping in CodexSessionCollection

Key winners by the row's own sessionId string so an unlimited scan retains no
alias-key string per live row (301 -> ~115 B/row measured over 50k rows), and
split into a per-alias-key map only for the rare id that spans several hosts,
namespaces, or rollout names, so admission stays O(1). Fold the PR's
CodexSessionAccumulator into the collection main already routes every scan
through, and rerun its scanner-level tests against that single class.
2026-09-12 22:10:30 -07:00
392583caba perf: skip WSL discovery when filtering native-only paths (#20266)
* perf: skip WSL discovery when filtering native-only paths

* fix: skip the AI Vault running-distro probe on WSL-less hosts

- getAiVaultWslHomeDirs, the sibling in the same Promise.all as the
  native-path filter, still spawned wsl.exe unconditionally on win32;
  gate it on the cached installed-distro list so a host with no distro
  performs no probe when only native Codex homes are configured.
- Hosts with a distro installed keep probing from that sibling, so the
  running-distro last-known-good cache is still warmed by the listing
  and a later probe outage falls back to the observed list, not [].
- Add a test against the real wsl module asserting zero wsl.exe spawns
  across the whole listing Promise.all, plus the warmed-cache fallback.

* fix(ai-vault): gate WSL home discovery on the cached distro list, not a probe

`listWslDistrosAsync()` resolves `[]` when the `wsl.exe` probe is rejected, so a
transient failure made `getAiVaultWslHomeDirs()` conclude "no WSL distros" and
skip discovery. That narrowed the allowed-roots set `ai-vault-delete` and
`ai-vault-subagent-list` validate against, wrongly rejecting WSL-hosted paths.

Gate on `hasCachedWslDistros()` / `getCachedWslDistros()` instead: a pure cache
read that only skips discovery once a successful probe has reported zero user
distros. It also never probes, so the AI Vault listing cannot be the first to
cache `[]` and flip a configured distro to "missing" in runtime resolution.

* test(ai-vault): drop the type assertion tripping the casting gate

check-changed-code-quality runs config/oxlint-code-quality-casting.json with
assertionStyle:'never' over changed lines, and `args as string[]` in the new
wsl-probe spy failed it. Narrow through Array.isArray instead, which is also
honest about execFile's argv being optional.

cached-session-list-wsl-probe + cached-session-list: 9/9 pass; tc:node clean;
changed-code quality gate passes.

---------

Co-authored-by: Orca Worker <orca-worker@localhost>
Co-authored-by: Neil <neil@stably.ai>
2026-09-12 21:18:50 -07:00
fc81355fe1 perf: accelerate cancellable remote transcript line scanning (#20351)
* perf: search remote transcript newlines directly

* fix: bound newline search by the yield window so cancellation stays observable

A newline-free segment jumped straight to the next line break, skipping the
character-count yield and its abort checks. Cap each jump at the yield window
and yield there so a large single-line transcript still stops promptly.

---------

Co-authored-by: Orca Worker <orca-worker@localhost>
Co-authored-by: Neil <neil@stably.ai>
2026-09-12 21:05:50 -07:00
Neil 411843f633 fix(ci): cache the vendored addon where node-gyp actually writes it (#20445)
The workspace link means pnpm never creates a .pnpm/@orca+windows-registry@*
entry, so all four native-cache blocks globbed a path that cannot exist and
the addon was recompiled on every Windows job.

Also hardens the addon itself: RegEnumValueW reports a byte count and the
registry does not enforce whole WCHARs for string types, so an odd count let
Napi's auto-length scan run past the value; and a value named __proto__ would
reassign the result object's prototype instead of becoming an entry.
2026-09-12 20:45:42 -07:00
e0e79f1ccd fix(resource-manager): show saved folder workspace names and groups (#20324)
* fix(resource-manager): resolve folder workspace names and groups

* fix: recover local folder PTY attribution after restart

* fix(resource-manager): keep ambiguous-id rows and open folder rows

Ambiguity filtering removed both rows of a workspace-id collision from
worktreeById, so step 3 of the merge dropped browser-only rows for any
id present on two execution hosts. Carry ambiguity as a separate
MergeContext signal that gates only folder host/name attribution; the
existence check and the old repo-level host default are unchanged.

Folder-workspace rows rendered as enabled buttons but navigateToWorktree
resolved only worktrees, so clicks were a silent no-op. Route folder
keys through activateAndRevealWorkspace, which owns host selection and
path-status gating.

* test(resource-manager): repair the merge-call ratchet anchor

The ambiguous-id fix added `ambiguousWorktreeIds` after `worktreeById` in the
mergeSnapshotAndSessions call, so the parity test's end anchor no longer matched:
indexOf returned -1 and slice(start, -1) silently widened the scan to the rest of
the file. The test still passed but stopped pinning the merge call site.

Verified: removing `...resourceSessionBindings` now fails the test again.

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Neil <neil@stably.ai>
2026-09-12 20:35:48 -07:00
Neil 5127d1eb3b refactor(windows): vendor the registry addon as @orca/windows-registry (#20438)
* refactor(windows): vendor the registry addon as @orca/windows-registry

windows-native-registry@3.2.2 was last published in 2023 by a single
maintainer. Orca called two of its exports, both read-only, so the whole
dependency is replaced by a local N-API addon under native/.

The vendored addon is read-only by construction: setValue, createKey and
deleteKey are gone, so RegDeleteTreeW no longer ships in the app. Two
upstream defects are also fixed rather than carried over — the name/data
scratch buffers were file-scope statics that concurrent reads would
scribble over, and createKey/deleteKey called .c_str() on a temporary.

Build wiring keeps the existing shape: still an optionalDependency gated
to win32, still excluded from pnpm's allowBuilds so only Orca's own
Windows rebuild runs node-gyp for it, still copied into the packaged
resources. The CI native caches now key on the vendored sources so an
addon.cc edit cannot restore a stale .node.

* test(windows): check the vendored registry addon against reg.exe

The addon is vendored source, so no upstream release proves it still
decodes values the way Orca's PATH readers expect. reg.exe is the only
independent oracle on the box.

* ci(windows): register the registry addon test on the Windows runner

A Windows-gated file self-skips on ubuntu, so without both registrations
it reports success while running on no machine at all.

* fix(build): link the registry addon as a workspace package, not file:

As a `file:` dependency pnpm re-resolved and re-linked the package on
every install, including `--frozen-lockfile` (measured: "added 1" on a
repeat no-op install). That virtual-store churn ran concurrently with
node-gyp reading the same tree and cost @vscode/windows-process-tree its
binding.gyp mid-rebuild, failing package (windows) whenever the native
cache hit and only that module needed building. The linux packaging job
hit the same race from the other side, as a pnpm staging move failure.

A workspace link resolves once and leaves the store alone; repeat
installs are now 55ms no-ops. native/windows-registry is listed
explicitly so `packages:` still does not auto-discover mobile/.

* fix(build): stop tracking node-gyp output for the vendored addon

The build/ tree is generated per host and ABI; the committed copy was
macOS-specific gyp scaffolding from a local build and would have shipped
stale Makefiles to every checkout.

* chore: ignore the vendored addon's node-gyp bin output too

node-gyp also emits bin/<platform>-<abi>/ beside build/; both are per-host
generated output that must never be committed.
2026-09-12 20:10:49 -07:00
Neil 471463f4ce perf(git): normalize tracked discard paths once per operation (#20299)
* perf(git): normalize tracked discard paths once per operation

* chore(git): drop the now-dead tracked-pathspec re-export
2026-09-12 20:05:54 -07:00
Neil 91143cf7af perf(git): reject non-HEAD refs before sorting remotes (#20429) 2026-09-12 20:04:22 -07:00
Neil 480fc20a7c perf(skills): test agent ownership without building a deduped list (#20428) 2026-09-12 20:04:12 -07:00
OrcaWinandm4air 90b02cba60 fix(updater): open background check errors from the status bar (#20270)
* fix(updater): open background check errors from the status bar

* docs(updater): describe error disclosure initialization

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-09-12 19:56:46 -07:00
OrcaWinandm4air 0a44b29741 fix(tabs): end drag gestures when the window loses focus (#20323)
Co-authored-by: m4air <m4air@Mac.localdomain>
2026-09-12 19:56:29 -07:00
Neil 2d1bd1eb48 perf: bound whitespace normalization for tool previews (#20332) 2026-09-12 19:40:31 -07:00
Neil fccc887037 perf: skip impossible inline HTML comment matches during encoding (#20293) 2026-09-12 19:40:10 -07:00
35a5259ccd perf(android): queue fragmented scrcpy video packets (#20230)
* perf(android): queue fragmented scrcpy video packets

* fix(android): release consumed scrcpy chunk storage

* fix(android): bound queued scrcpy fragment count

- Coalesce pending video fragments once more than MAX_PENDING_CHUNKS
  (1024) are queued, so a large frame delivered in tiny socket chunks
  cannot retain millions of Buffer objects below the 16 MiB byte guard.
- Add a regression test feeding a 256 KiB frame one byte at a time and
  asserting the retained fragment count stays bounded.

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Neil <neil@stably.ai>
2026-09-12 19:39:40 -07:00
Neil fee47fdb09 fix(chat): bound journal replay memory by live history (#20247)
* fix(chat): stream journal replay without retaining obsolete revisions

* fix: page journal replay reads so no SQLite snapshot outlives its statement

- iterateJournalEpochRows fetches one completed LIMIT statement per page
  instead of a lazily consumed .iterate() cursor, so reduction never runs
  inside an open read snapshot and a WAL checkpoint can pass mid-replay.
  Regression test: a checkpoint issued from inside the reducer is not busy.
- The retention test now asserts the applyJournalRow spy observed every
  row, so the 8 MiB bound cannot pass vacuously if the spy stops
  intercepting.
- Reliability gate manifest records the new assertion and the paged
  read design.
2026-09-12 19:39:30 -07:00
659f204fec perf: avoid rescanning partial Windows desktop responses (#20235)
* perf: avoid rescanning partial Windows desktop responses

* fix: own the retained serve-channel tail and pin the no-newline invariant

- Copy the retained partial line via ownRetainedString after each drain so
  a 13+ char tail no longer pins the whole drained response as a V8
  SlicedString (measured 23 MB -> 22 KB for 32 pending tails behind 1 Mi
  lines); regression test with --expose-gc.
- Add a test asserting the retained buffer never contains a newline after
  a drain, which the chunk-only fast path depends on.
- Locate the first delimiter with decoded.indexOf offset by the retained
  length instead of rescanning the whole accumulated buffer; test pins
  that no indexOf runs over more than the new chunk.

---------

Co-authored-by: Orca Worker <orca-worker@localhost>
Co-authored-by: Neil <neil@stably.ai>
2026-09-12 19:39:20 -07:00
Neil de7558f095 fix(editor): eliminate multi-second Markdown blank-run scans (#20231)
* fix(editor): keep Markdown blank-run scans linear

* test(editor): guard rich Markdown blank-run performance
2026-09-12 19:39:10 -07:00
b9263eaaf6 perf: reuse pending filesystem watcher debounce timers (#20264)
* perf: reuse pending filesystem watcher debounce timers

* fix: cancel watcher batches after terminal errors

* fix: null the cleared batch timer on last-listener unsubscribe

`Timeout.refresh()` is a no-op on a handle already passed to
`clearTimeout`. `unsubscribeLocalWatcher` cleared `root.batch.timer`
without nulling it, so a re-subscribe inside the teardown grace window
reused the root with a dead handle and `scheduleLocalBatchFlush` never
re-armed — fs change events for that root stopped reaching the renderer.

- Null `root.batch.timer` after clearing it in the unsubscribe path.
- Add a real-timer regression test covering unsubscribe + re-subscribe
  within the grace window; it fails on the previous PR head.

---------

Co-authored-by: Orca Worker <orca-worker@localhost>
Co-authored-by: Neil <neil@stably.ai>
2026-09-12 19:39:01 -07:00
Neil 8641b3af09 perf: remove repeated sibling scans from cyclic agent lineage cleanup (#20302) 2026-09-12 18:49:34 -07:00
OrcaWinandm4air 25a1259d28 perf(ai-vault): count recent sessions for scan cutoffs (#20301)
Co-authored-by: m4air <m4air@Mac.localdomain>
2026-09-12 18:36:57 -07:00
Neil 25d6cd75eb perf(cmd-j): scan phrase word boundaries monotonically (#20282) 2026-09-12 18:36:47 -07:00
OrcaWinandm4air 824dc5353a perf(chat): join transcript line fragments at record boundaries (#20278)
Co-authored-by: m4air <m4air@Mac.localdomain>
2026-09-12 18:36:37 -07:00
Neil a766df7a7a perf(plugins): index contributed keybindings by command (#20241) 2026-09-12 18:36:18 -07:00
Neil b1c6d53e90 perf: bound palette phrase placement to word starts (#20306) 2026-09-12 18:16:51 -07:00
Neil 490937a043 perf(git): classify bulk discard paths once (#20274)
* perf(git): classify bulk discard paths once

* style: format bulk discard partition
2026-09-12 18:16:41 -07:00
Neil d66de72db1 perf: stop review acknowledgement summaries at the first readable line (#20263) 2026-09-12 18:16:31 -07:00
OrcaWinandm4air 59d643c62b perf(ai-vault): deduplicate session scan batches incrementally (#20255)
* perf(ai-vault): deduplicate session scan batches incrementally

* perf(ai-vault): reduce scan-local occurrence metadata

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-09-12 18:16:21 -07:00
Neil b8f1849ba8 perf: scan markdown review lines with native newline search (#20245) 2026-09-12 18:16:12 -07:00
OrcaWinandOrca Worker dbac4e6ed2 perf: avoid rescanning partial transcript records (#20239)
Co-authored-by: Orca Worker <orca-worker@localhost>
2026-09-12 18:16:02 -07:00
Neil 2185389f2e perf(plugins): reconcile shortcut conflicts once per owner (#20237) 2026-09-12 18:15:52 -07:00
OrcaWinandm4air cb4321df8b perf(agents): avoid cloning lineage ancestor sets (#20236)
Co-authored-by: m4air <m4air@Mac.localdomain>
2026-09-12 18:15:42 -07:00
OrcaWinandOrca Worker 2285971186 perf(checks): bound earlier error context collection (#20211)
* perf(checks): bound earlier error context collection

* docs: record bounded log excerpt ordering invariant

---------

Co-authored-by: Orca Worker <orca-worker@localhost>
2026-09-12 18:15:12 -07:00
Neil 38966784de perf: scan chat activity lines from the end (#20356) 2026-09-12 18:13:47 -07:00
OrcaWinandOrca Worker 37fca54473 perf: avoid copying single-chunk process output (#20355)
Co-authored-by: Orca Worker <orca-worker@localhost>
2026-09-12 18:13:37 -07:00
OrcaWinandOrca Worker 8830b508b3 perf: reuse normalized executable during agent recognition (#20353)
Co-authored-by: Orca Worker <orca-worker@localhost>
2026-09-12 18:13:18 -07:00
Neil 6440fccf08 perf: skip quick command fields that cannot improve search scores (#20352) 2026-09-12 18:13:08 -07:00
Neil e3b72e17ec perf: skip nonmatching spans in quick open fuzzy scoring (#20349) 2026-09-12 18:12:58 -07:00
OrcaWinandm4air a0d26cadd8 perf(computer): avoid rescanning fragmented native provider replies (#20348)
Co-authored-by: m4air <m4air@Mac.localdomain>
2026-09-12 18:12:49 -07:00
OrcaWinandOrca Worker c07e33bf42 perf: reuse shared SHA-256 for renderer fallback (#20347)
* perf: reuse shared SHA-256 for renderer fallback

* test: wait for Windows child marker to become removable

---------

Co-authored-by: Orca Worker <orca-worker@localhost>
2026-09-12 18:12:39 -07:00
OrcaWinandOrca Worker 11fcb03dfa perf: avoid typed-array iteration in shared SHA-256 blocks (#20346)
Co-authored-by: Orca Worker <orca-worker@localhost>
2026-09-12 18:12:29 -07:00
OrcaWinandOrca Worker dcc1f90125 perf: count bot comments without a filtered array (#20343)
Co-authored-by: Orca Worker <orca-worker@localhost>
2026-09-12 18:12:10 -07:00
OrcaWinandOrca Worker 3c7c846a1d perf: compute check severity once per sort (#20342)
Co-authored-by: Orca Worker <orca-worker@localhost>
2026-09-12 18:12:00 -07:00