* perf(chat): typeset each unchanged formula once instead of on every streamed update
A streaming paragraph or list re-parses on each revealed prefix, and every
complete formula in it went through KaTeX again, as did every formula on a
virtual-row remount. Keep a bounded cache of typeset results keyed by what
the typesetter reads (tag, math classes, formula text), hand each tree its
own copy, and remember failures so invalid formulas still show their source.
* test(chat): import the typeset module type at the top of the math test
* fix(chat): evict the oldest cached formula without a type assertion
* fix(chat): type the readable math fallback without an assertion
* Cache immutable math results with bounded retention
* fix: bound local iteration checks and show live progress
* fix: harden check lifecycles and cached test evidence
* fix: preserve process instrumentation and fixture lifecycles
* fix: give fixture unref spy a portable function type
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(daemon): read the PTY process table without blocking on terminal kill and resize
The terminal daemon force-killed a session by running ps and reading
/proc/<pid>/stat synchronously, and checked the resize foreground group
with execFileSync('ps'). A slow or wedged ps froze every terminal the
daemon serves for up to the ps timeout on each call.
Both paths now use the async process-table read (runProcess with a
timeout that kills the child). SubprocessHandle.forceKill may return a
promise; the termination controller awaits it so a refused signal still
releases the claim for a retry. A force-kill or SIGWINCH whose root
exits while ps runs signals nothing, since the captured groups could
already be recycled. ConPTY force-kill stays synchronous so dispose()
still sees the node-pty kill it must not repeat. The in-process
provider's app-quit sweep keeps the blocking variant. The ps dialect
and table readers move to posix-pty-process-table.ts.
Refs #34410
Refs #33870
* fix(daemon): address review on async PTY process-table reads
Guard only the root-pid fallback on exit so proven groups are still swept
after dispose, wait out at most one ps dialect probe, move the sync-launch
baseline entry with the table reader, and keep tests cross-platform.
Refs #34410
Refs #33870
* fix(daemon): keep the PTY open until an in-flight force-kill sweep finishes
A sweep whose root exits mid-read must not signal: the freed TTY may
belong to another terminal. Dispose therefore defers destroy() until the
sweep settles, so its hangup cannot end the root first.
Refs #34410
Refs #33870
* test(daemon): build the destroy-tracking PTY mock without a type assertion
Refs #34410
Refs #33870
* fix(pty): make the local graceful-deadline force-kill async and stub ps in tests
The in-process provider's graceful-deadline timer now uses the async
group sweep; the app-quit sweep and the root-signal path stay
synchronous. Tests that drove force-kill through fixture pids now stub
the async process-table read instead of querying the host, and a new
test pins that waiters do not chain behind a failed dialect probe.
Session ids are only read for rows on the root's terminal.
Refs #34410
Refs #33870
* fix(pty): hold PTY teardown until an in-flight force-kill sweep settles
Orphan cleanup could destroy a local PTY while the graceful-deadline
sweep was still reading the process table, neutralizing its fallback
kill and hanging up the root first. Both the daemon handle and the
local provider now defer teardown through PendingPtyGroupSweep.
Concurrent force-kill callers in the daemon now await the shared
in-flight request, so a joiner's physical-exit budget starts after the
signals are sent and it sees their rejection.
Refs #34410
Refs #33870
* fix(pty): let disposal and orphan cleanup join the in-flight force-kill sweep
Session disposal now joins the controller's shared force-kill request
instead of starting a second sweep. Local orphan cleanup decides its
escalation only after a pending graceful-deadline sweep settles, so a
refused sweep is escalated before the handle is released rather than
leaving the retry with a neutralized kill.
Refs #34410
Refs #33870
* fix(daemon): SIGKILL live PTY groups before a shutdown deadline exit
The old synchronous sweep sent its signals before the shutdown deadline
timer could run; the async sweep can still be reading the process table
when it fires. The deadline now SIGKILLs each live session's process
group synchronously before exiting (node-pty roots lead their own
group), falling back to the root pid when the group kill is refused.
POSIX only.
Refs #34410
Refs #33870
* chore(ratchet): gate event-loop-blocking calls in the daemon, PTY and orcad code
The terminal daemon and orcad serve every pane from one event loop, so a
synchronous child launch or file write there freezes all of them. A new
count ratchet tracks execFileSync/spawnSync/execSync, runProcessSync,
appendFileSync and writeSync per file under src/main/daemon,
src/main/pty, src/main/runtime and src/main/orcad; counts may only go
down. It reuses the sync-spawn ratchet's import-aware call collector,
now extracted to gated-call-use-collector.mjs, so aliases, namespace
imports and re-exports count and same-named methods on other objects
do not.
Refs #34410
Refs #33870
Bump agent-browser to 0.27.3. Its 0.27.2 release scrolls the target node into
view (nested scroll containers included) before computing the click point and
errors when another element covers that point, instead of reporting a click
that never reached the page.
top costs ~263 ms of CPU per sample, so the memory panel only refreshed
footprints every 10 s. The new orca-process-footprint helper reads
phys_footprint for every same-user process in ~3 ms, so footprints now
refresh on the panel's 2 s poll. top stays as the fallback when the helper
is missing or fails.
Co-authored-by: m4air <m4air@Mac.localdomain>
* perf(lint): keep JS plugins out of default oxlint and adopt ast-grep
Any jsPlugin makes Oxlint hand every file's AST to Node, so the seven custom
plugins cost the default `oxlint` run several times its native time.
- Move app-store-performance, quadratic-buffer-concat, renderer-scrollbar-style
and ime-enter-guard (scope- or dataflow-aware) to config/oxlint-js-plugins.json,
run by `pnpm lint:js-plugins`, CI, lint-staged and the changed-lines gate.
- Port sort-comparator-performance, mobile-pairing and native-chat-design-tokens
to ast-grep rules with rule tests, and delete the plugins.
- Add @ast-grep/cli, config/sgconfig.yml, `pnpm lint:ast-grep` / `test:ast-grep`,
a CI step, and an ast-grep pass in check-changed-code-quality.
- Add flaky-test rules (wall-clock reads, real-time waits, polling loops,
unquarantined skips, unexplained timeout bumps), baselined by a count ratchet.
* Enforce flaky-test ratchet on changed files; refresh baseline
* docs(agents): keep custom-rule details out of AGENTS.md
* Resolve ast-grep from the repo so gate scans of other roots work
* feat(devex): component fixtures with headless screenshots for fast UI checks
Colocated *.fixture.tsx files export named fixtures (render + optional store
seed, setup, interact, width). A dev-only Vite entry (vite.fixtures.config.ts,
extending vite.web.config.ts) renders one fixture in isolation with the real
I18nProvider, TooltipProvider, theme class, default-seeded store, and the web
client's preload bridge (installWebPreloadApi) as window.api.
`pnpm fixtures:shot <id|glob> [--theme dark|light|both]` starts or reuses a
detached server (Vite + one headless Chromium) and writes PNGs to
.cache/fixtures/. A warm page re-renders in place (~100ms/shot) and reloads
only after source changes. A stdio MCP server (registered in .mcp.json)
exposes list_fixtures and screenshot_fixture.
Fixtures load only through import.meta.glob in the fixture entry, which is
not a production build input; oxlint rejects static imports of *.fixture.
* Keep fixture config and agent config inside reviewed root entries
* Baseline the fixture page's default-settings seed in the focus-read ratchet
* docs(agents): shorten fixtures pointer
* Allowlist the dev-only fixture explorer's labels in localization coverage
* feat(devex): add pnpm check:changed as the single pre-handoff gate
Runs format, oxlint, the changed-code gate, affected typecheck projects,
affected unit tests, and the ratchets/verifiers whose inputs changed, in
parallel against the origin/main merge base plus local edits. Printing is
limited to failures, and a pass is cached by a hash of the diff, untracked
files, lockfile, and config so an unchanged rerun exits immediately.
* fix(devex): keep the check:changed pass valid across staging and commits
* docs(agents): slim Useful Commands to the commands agents need
- test:changed exits before starting Vitest when Git reports no changed files.
- Unrecorded tests beyond 100 in scope use Vitest's static import graph instead
of all running, so a fresh worktree's first changed run no longer runs the
whole suite (patched RecordedModulesResolver; CI does not record, unchanged).
- Bun's slow config load was the SQLite import scan statting every unit test;
filtered runs now classify only the tests their filters can select, and the
scan walks directories instead of fs.globSync.
- Silence the native config loader notice on every run.
- Zod's test bundle is content-addressed and reused, so a killed run leaks
nothing; leaked per-run directories older than 6h are swept.
- build:packages skips compiling when package inputs, compiler, and dist are
unchanged; test scripts no longer nest pnpm invocations.
Linked worktrees now get a per-worktree userData profile under
orca-dev-wt/<hash>, a deterministic collision-checked renderer port
(15200+) and CDP port (9333+), so parallel `pnpm dev` runs no longer hit
the single-instance lock. The main checkout keeps the shared orca-dev
profile; --shared-profile / --worktree-profile / ORCA_DEV_PROFILE override.
The runner prints `ORCA_DEV_INSTANCE {json}` on stdout and mirrors it to
out/dev-instance.json so agents can attach over CDP. orca-dev.mjs and the
terminal wrappers follow the profile the worktree's dev app used.
Adds profile seeding (pnpm dev:seed-profile, pnpm dev --seed-profile,
pnpm dev:fresh --seed-from): an allowlisted slim copy of projects,
workspaces, settings, SSH targets, and the account session, written as
orca-data.json for Orca's own import; the source is only read.
Storage reclaim removes per-worktree profiles once their worktree is gone
and no app, runtime, or daemon still uses them. dev-fresh-profile.sh is
replaced by a cross-platform dev-fresh-profile.mjs.
* chore(lint): remove disable directives that suppress nothing
Each was reported unused by the oxlint config that owns its rule (root,
casting, react-doctor, anti-slop, native-plugins), including 17 file-level
react-doctor blanket disables whose rule no longer fires. An unused
react-hooks disable was masking a react/purity finding in
useAgentBucketCounts (compiler rules skip functions with hook
suppressions); suppress that one explicitly where the code explains it.
* test(tab-bar): use typed tab/file fixtures instead of @ts-expect-error partials
* chore(lint): fix misc suppressed lint violations instead of disabling
- consistent-type-imports: the "vi.importActual requires inline import()"
rationale was wrong; use typed vi.mock(import(...), importOriginal) or a
type-only namespace import.
- no-control-regex: source-control commit/push failure summaries, shell PATH
hydration, and ipynb ANSI parsing reuse the shared ansi-escape-sequences
stripper and control-character pattern instead of private copies.
- no-explicit-any / no-useless-spread / no-this-alias: typed `this` in
bridge test mocks, drop blanket file-level disables, drop a this-alias.
* refactor(main): replace avoidable type assertions with checked narrowing
17 production cast suppressions in main/wsl-guest removed: untrusted IPC
and RPC payloads (Jira issue update, WSL guest plugin install, handshake
type) now narrow through isRecord/type guards and build typed results;
redundant casts dropped; hand-rolled object checks and errno casts use the
existing isRecord / hasErrorCode helpers.
* refactor(renderer,mobile): replace avoidable type assertions with checked types
13 production cast suppressions removed: RPC failures read through the
`ok` discriminant instead of casting to RpcFailure; react-native-web host
refs narrowed with instanceof HTMLElement; redundant reply/diff casts
become checked annotations; the TUI agent param uses z.custom(isTuiAgent)
like its sibling schemas; the agent-status slice cast that hid missing
actions is gone so the creator's return type checks the whole slice.
* chore(lint): drop dead tiptap wrapper; scope anti-slop mocking rule to test support
- safe-react-node-view-renderer patched handleSelectionUpdate, which
@tiptap/react 3.31.3 no longer has, so it always returned early. Use the
stock ReactNodeViewRenderer.
- The anti-slop test override only matched *.test.{ts,tsx}, forcing
file-level no-module-mocking disables in test harnesses and fixtures.
Match *-test-harness, *-test-fixture(s), .test-fixture and *.test.mjs,
then drop those disables and the now-redundant per-file exemptions.
* test: replace repeated cast suppressions with typed fixtures (main/shared/e2e)
Net -119 cast directives. Typed fixtures replace recurring casts:
unusedSshConnection/unusedOrcadHostTransport placeholders, a typed
readPersistedState harness read (exposed a test treating optional
httpProxyUrl as always set), real Response objects in fetch spies, an
updater main-window fixture, a CDP webContents adapter, one shared
releaseModuleExport for cross-version tests, and requireOverlayDir in
place of `as string` on possibly-undefined overlay paths.
* fix(runtime): validate loaded device rows and host scope instead of casting
Two cast suppressions claimed checks that did not happen:
- device-registry-file-read: "normalizeLoadedDeviceRow re-validates every
field" was false; deviceId, token, name and timestamps were never
checked. Rows without an identity are now dropped and the file is set
aside through the existing corrupt-file path (registry reads as
doubtful, so no revocation verdicts); a non-string token is stripped
so the row stays listed but cannot authenticate.
- client-ui: "only optionality differs" was false; any string reached
persisted workspaceHostScope. Narrow it with isWorkspaceHostScope and
ignore unknown scopes (wire schema unchanged for mixed versions). The
remaining cast now states what it actually covers.
* test(runtime,ipc): drop redundant cast suppressions; typed runtime store fixture
-98 cast directives across runtime/ipc tests: 61 casts that typecheck
without the assertion; a typed vi.fn for scoped invalidation; a
partialRuntimeStore fixture in place of `store as never` for
OrcaRuntimeService; typed worker terminal/authority builders in the
incarnation-fallback suite (the old stub lacked runtimeId, ptyId,
worktreeId and launchTokenHash) plus removal of stacked duplicate
directives there.
* chore(lint): typed vi.mock imports and explicit listener snapshot in shared-bucket tests
Remaining files from the misc-rule pass that overlapped other buckets:
codex-accounts mocks use the typed vi.mock(import(...)) form instead of
a consistent-type-imports disable; the daemon late-exit fixture copies
its listener set with Array.from instead of a no-useless-spread disable.
* refactor(github,mobile): narrow routing settings type; drop remaining avoidable casts
- work-item-routing cast a one-field object to AppState['settings'] under
a claim that readers only consume activeRuntimeEnvironmentId. Return the
existing RuntimeFocusSettings pick instead and narrow the cache-key
chain's parameters, so the compiler enforces that claim.
- mobile file preview reads refusals through the RpcResponse `ok`
discriminant rather than a cast.
- terminal document host-notify accepts `unknown` errors, removing the
message-bridge cast.
- native chat readability drops a redundant repo-list cast.
* test(renderer): replace repeated cast suppressions with typed fixtures
-140 cast directives (and ~150 unsuppressed casts of the same shapes):
- mockPanePtyArgs owns the connectPanePty fixture casts, so a wrong
argument order or fixture is now a type error; drop binding casts for
members PanePtyBinding already declares.
- Seed AppState tab rows with the typed makeTerminalTab; this exposed a
split direction 'row' that is not a TerminalPaneSplitDirection.
- Partial GlobalSettings use getDefaultSettings overrides; Jira-only
stores use createTestStore; runtime environment rows/statuses come from
a typed paired-runtime-environment fixture.
* chore(lint): drop no-module-mocking disable now covered by the test-fixture glob
* test(e2e): fix all 221 grandfathered e2e type errors; baseline to zero
The e2e typecheck baseline grandfathered 221 errors in 119 files. About a
third were tests drifted from the current API, several of which would
fail or mislead at runtime:
- terminal-shortcuts called a nonexistent xterm buffer method;
- the daemon-lifecycle fake lacked terminateOwnedTree, hidden by importing
its type from a module that does not export it;
- arguments in the wrong position (listProcesses(hostId, opts), a dropped
pty.write inputKind, an extra toWebTerminalSurfaceTabId argument);
- removed enum values ('drawer', 'idle', 'editor',
'session-insecure-origin') and a launch_source the telemetry schema
rejects;
- missing required fields (turnScope, childWorkOpen, providerIdentity,
reviewLookupOutcome, runtimeServiceAccount behind an `as RelayConfig`);
- dead reads (tab.customTitle, bucket.targets, changedPixels).
Also lifts variables that narrowed to `never` after poll-callback
assignment, and adds a typed xterm render-dimensions accessor. Includes
the cross-version suites' shared releaseModuleExport adoption.
* chore(typecheck): gate the now-clean e2e typecheck in local lint
The e2e baseline is empty, so the ratchet now rejects any tests/ type
error. CI already runs it via run-ratchets; add it to pnpm lint's
explicit ratchet list and replace the stale 'not yet clean' note.
* test(mobile): typecheck every mobile test; baseline 105 files -> 0
mobile/tests-typecheck-baseline.txt grandfathered 105 test files (531
errors) whose types CI never checked. Contract changes had landed without
these tests following, and type pins in them silently checked nothing.
- Root cause: salvagedOptional (src/shared/zod-salvage.ts) cast away
zod's optional marker, so every reply type built on it claimed
salvaged fields were always present. Return a real ZodOptional;
runtime is unchanged (the outer optional short-circuits undefined).
- Production now takes the port it uses: RpcOperationSender instead of
RpcClient for send-only callers, a narrow fetch signature, and only the
request fields push-receive reads.
- findAllByHostType/findByHostType replace `'Text' as never` lookups;
stubRpcClient gives tests a typed RpcClient.
- Real test bugs: settings-host-client-lifecycle "relay" hosts were
direct hosts (relayHostId no longer exists); two toEqualTypeOf pins
could never hold; a recovery hook ran with an undefined scope key;
a create-PR test cast to a never-imported type; "no URL" assertions
passed on null; plus stale required props, args and enum values.
* test(e2e): parse workerStart result instead of casting it
* test(runtime): typed partial runtime stores with production-shaped fixture data
Route every partial store passed to OrcaRuntimeService through
partialRuntimeStore with typed row builders, removing 60 cast directives
that hid fixture data production never produces:
- paired-client-navigation-test-harness keyed setWorktreeMeta writes by
`[object Object]` (it took an id where the store takes a target), so
reads never saw them;
- invalid worktree lineage origin/capture and incomplete workspace lineage;
- getWorkspaceSession returning null where the store returns the default
session; getGitHubCache returning undefined;
- orcad migration states missing migrationId/manifest/receipt, with a
toEqual pinned to that incomplete shape;
- partial repos, metadata, folders, project groups and notification rows.
* refactor(renderer): resolve suppressed React hook lint instead of disabling it
109 of 187 react-hooks/react-doctor suppressions fixed:
- ~95 exhaustive-deps disables only hid stable refs and setters passed
through controller objects; list them (identity never changes, so
nothing re-runs), plus a few real stale closures (tabId/worktreeId).
- Effects keyed on one value that read the latest props now use
useEffectEvent (WebConnect, skill freshness, Bitbucket dialog seeding,
Landing preflight, automation catalog, structured-session recovery,
file explorer).
- The conditional layout-owner hook is selected once at module load from
the build constant instead of a rules-of-hooks disable that also hid
the whole controller from compiler and react-doctor rules.
- A hook disable was masking a react/purity Date.now() in the palette's
recent-tabs memo; sample it when the snapshot is captured.
- Three identical debounced-search effects share
useTaskSearchDebouncedApply; layout snapshot merge helpers move out to
stay under max-lines.
Remaining suppressions are mount-once resources, invalidation epochs,
deliberately keyed fields, and test harnesses that mock the dispatcher.
* chore(ratchet): exempt test harnesses and fixtures from the focus-read ratchet
The ratchet skipped *.test-fixture.ts but scanned *-test-harness,
*-test-fixture(s) and *.test-support files. Those seed settings with
getDefaultSettings (which the ratchet discovers as a focus reader) and
never route host work, so 17 baseline entries were false positives, and
replacing a test's `settings: {...} as never` with typed defaults tripped
it. Match the repo's test-support names and prune (which also drops one
stale entry), and rename the frame-ordering harness to *-test-harness like
its siblings. A blanket "-harness" match would also exempt
e2e-terminal-pty-harness, which production imports, so the names stay
explicit.
* test: merge duplicate shared/constants imports
* revert(file-explorer): restore original tree load effects pending SSH e2e investigation
* test(runtime): drop RuntimeStore cast now that the fixture is typed
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* ci: fail when a checked-in test file has no runner
Two node:test files under .github/actions lost their runner when #31792
trimmed cloud pretest, and the two monaco .bench.ts budgets were never run
by any CI job. Wire both back in and add a static check that every
test-like file is collected by a declared runner or imported by a
collected entry (the src .spec.* suite-module convention).
* ci: ignore type-only imports and quote the lease glob for cmd.exe
* ci: parse type-only imports, cover UNIT_EXCLUDE, route cloud runner edits
Type-only imports are now dropped with the TypeScript parser, so a type
alias before a runtime re-export no longer hides it. UNIT_EXCLUDE files
must be named by a pr.yml or unit-tests.yml step. A cloud-only diff that
adds a test or edits a runner config now runs the static job. The monaco
benchmarks stay manual (pnpm test:perf:benchmarks) until a CI runner
proves their wall-clock budgets.
* ci: require UNIT_INCLUDE names in dedicated jobs and erase typeof import()
* ci: resolve each runtime import to the one file Vite loads
The unit dependency graph keeps every plausible candidate so it can
invalidate conservatively, which cannot prove a suite runs: an import of
./case.spec counted both case.spec.ts and case.spec.tsx. Resolve with
Vite's extension order instead.
* ci: take runtime imports from transpiled output
Imports used only as types are erased without a type modifier, so
parsing for type-only syntax still followed them. Derive edges from
ts.transpileModule output, which erases what Vitest's transform erases,
and only transpile files that can reach an uncollected candidate.
* ci: fix the agent-status benchmark config and route all cloud code
The agent-status benchmark config inherited the unit projects, whose
includes override its own, so it ran the whole unit suite and never the
benchmark. Clear projects as the performance config does. Route every
cloud code file to the static job, since content-detected tests can have
any name.
* Use incremental tsrs for local typechecks
* Update pinned Bun to 1.4.4
* fix(deps): bump dompurify to 3.4.17 and docs next to 16.4.1 for security advisories
dompurify 3.4.17 patches GHSA-cg2g-xh64-v4q7, GHSA-mrv2-3pqr-j5jr and
GHSA-c6gr-qvv4-5v3f in the root and mobile lockfiles. next 16.4.1 patches
GHSA-2464-fhr4-w9xw, GHSA-673q-mjm6-r5xr and GHSA-p3m5-59mp-jv5r in the
docs site.
* Prepare mobile bundle for smoke benchmarks
* Give native compiler benchmarks a larger ARM runner
---------
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* refactor(renderer): read workspace records through typed selectors
* refactor(renderer): keep only the selectors with readers; type the host owner lookup
* fix(ssh): draw every host list through one bounded, searchable host picker
A generated ~/.ssh/config can import 13k+ SSH hosts, and several surfaces drew a
row per host: Settings > General workspace directory 'Apply to', Notifications >
Machines, the sidebar and Agents Hosts submenu, the composer run-target picker,
the Add Project host selector and remote step, the skill-install machine pickers,
Settings > Servers SSH host picker, repository 'Add to another host' and the
automation host field and filter.
They now share components/host-picker: boundHostList (pinned hosts plus a ranked,
capped match list), HostCombobox for single choice (a Command in a Popover that
mounts nothing until opened, replacing Selects that mount every item), and
useBoundedHostRows with HostListSearchField and HostListOverflowNote for
checklists. A host-list-render ratchet stops new JSX from mapping straight over a
host collection.
* fix(ssh): host lists keep room for search results, menu search is keyboard-reachable
- boundHostList gives pinned hosts and other matches separate limits, and narrows
pinned hosts by the query, so a large selection never hides what the user types.
- In a dropdown, the host search is a menu item: arrow keys reach it, focus moves
into the input, and arrows leave it again.
- The Add Project host picker hides its setup actions while searching, so Enter
picks the matching host.
* fix(ssh): re-picking the current host in a host picker is not a change
The Selects these pickers replaced suppressed same-value changes; the skill
install form clears its workspace and target on a machine change.
* chore(ci): let the shared ratchet runner pick up the host-list render ratchet
main now discovers every check-*-ratchet.mjs through run-ratchets.mjs, so the
separate workflow step would run it twice.
* fix(ssh): host lists rank pinned matches and keep input order; label and scroll fixes
- boundHostList ranks pinned and other hosts by the same match order before
cutting each to its limit, then keeps input order among equal ranks, so a
muted or checked exact match is always reachable and clicking a row never
moves it.
- The Servers SSH host picker is named by its form label again.
- The sidebar Hosts submenu scrolls its host rows instead of running off screen.
- The composer's overflow note sits outside the listbox.
* ci: one list for Windows boundary tests and one step for ratchets
The Windows boundary step's 74 test paths were hand-listed in pr.yml and
again in WINDOWS_PACKAGE_TESTS, and the copies had drifted: six files ran
in the step but could not trigger the job. The step now prints
WINDOWS_BOUNDARY_TESTS, which WINDOWS_PACKAGE_TESTS spreads.
The ten per-ratchet preflight steps and the eight lint entries become one
run-ratchets.mjs that runs every check-*-ratchet(s).mjs it finds.
* fix(ci): preserve existing local lint ratchet scope
* fix(deps): root and mobile resolve one zod, and CI fails if they drift
Mobile type-checks src/shared zod schemas. With mobile on zod 4.6.5 and the
root on 4.6.7, TypeScript treated them as unrelated packages, so wrapped
schemas inferred {} (tests/e2e/helpers/phone-feed-list.ts:154) whenever a
job installed mobile deps.
* fix(deps): guard every package mobile's tsconfig remaps, not only zod
* test(ci): pin the shared-dependency version check to the static phase
* feat(ci): ratchet raw repo connectionId host reads; one drop-in resolver
Codex review 6 class fix (#4/#6/#7): host decisions read off a repo row's
legacy connectionId miss rows that name SSH only as executionHostId.
- getRepoRoutingConnectionId (shared/execution-host.ts): drop-in for a raw
repo.connectionId routing check that also finds executionHostId-only SSH.
- check-repo-host-read-ratchet: per-file count of raw repo connectionId
reads in src/main, src/shared, src/cli; counts may only shrink.
- Migrated 66 cheap reads: runtime GitHub/GitLab commands, PR/MR head ref
fetches, preserved-branch cleanup, ref queries, base-ref IPC, worktree
preparation, trash sweep, scan cache TTL, usage metadata.
* fix(ipc): PR base IPC routes executionHostId-only SSH rows; ratchet is a stated lower bound
Review follow-up (Opus on #31096):
- worktrees:resolvePrBase decided git/gh routing on the raw connectionId while
its head-ref fetches already used the resolver; one resolved connectionId
now drives all of it (test fails on main).
- getRepoRoutingConnectionId's contract is stated exactly: never local when the
raw field is set; on a contradictory ssh row executionHostId wins.
- The ratchet regex no longer matches report.connectionId; header and docs
say it is a lower bound.
- Regression tests for a runtime GitHub and GitLab command site.
* test(ci): pin the repo host-read ratchet step to the static phase
* fix(ci): repo host-read ratchet skips verbatim field copies into projected rows
A copy of connectionId into a projected row decides nothing; migrating it to
the resolver would change what the projection publishes. The sidebar feed
catalog's copy (added after the baseline) and two older projections no longer
count; baseline pruned to 91.
* ci: fail the static job on unformatted files
Runs oxfmt --check over the same tree pnpm format writes (~2s), and makes the
pre-commit hook format markdown and YAML so it covers what CI now checks.
* ci: route docs-only and workflow-only diffs through the formatting check
The format check reads the whole tree, so a diff that skipped the static job
could land drift that fails every later PR. Any changed file oxfmt formats now
selects static analysis (not typecheck or units), like the mobile-only route.
The path detector checks out .oxfmtrc.json for its ignores. The hook no longer
fails a commit whose only matches oxfmt ignores (lockfiles).
* fix(persistence): repo-row store writes take the row, not the id; ratchet the rest (R4-2a)
* fix(persistence): host-filter the runtime nested import; widen the id-only write ratchet (review)
* fix(runtime): nested import keeps matching runtime-stamped native rows on a paired host
* fix(persistence): class-scope the ratchet param exemptions; update a new main test to the target shape
* fix(ipc): fence a project-group move by the resolved twin host, so an SSH twin is not WSL-fenced
* test(ci): pin the id-only store write ratchet step to the static phase
* refactor(pty): every PTY retirement names its exit proof; ratchet the unproven ones
* refactor(pty): count raw ownership drops, route an abandoned relay stop to unverifiable, honest proof labels
* refactor(terminal): one PTY attribution for teardown, close and identity; delete the deprecated resolver pair
- strict ssh: route: a proven direct listing beats a same-id server proxy; other contradictions refuse
- terminal spawn's git branch takes the strict route when a host is named (rivals refuse)
- resolveTerminalPtyOwner + isUnattributedTwinWorktree replace resolveRuntimeOwnerForWorktree in
shutdown, pane and agent hibernation, and PTY identity; close logs unroutable runtime ids
- delete getRuntimeEnvironmentIdForWorktree/resolveRuntimeOwnerForWorktree (legacy branch moved
into the adapter), the alias golden test, the importer ratchet, its baseline and pr.yml step
- hasHostEvidence reads the owner indexes instead of scanning repos and groups
* fix(terminal): close and identity read spawn's attribution for unscoped remote ids (Opus P2)
- an unattributed twin's unscoped remote: id is skipped and logged on close, never closed locally
- setup terminals: identity and close follow spawn's runtime; raw-id teardown stays local
- drop the last stale getRuntimeEnvironmentIdForWorktree mock (tests/e2e unit suite)
* refactor(terminal): one ownership read in resolveTerminalPtyOwner
* test(terminal): pin the close-path twin skip and the two-proxy direct listing (Opus P3)
* test(terminal): keep the close-provider owner fixture's full union type (Codex P1)
* build: share one pnpm virtual store across worktrees, off under CI
Each worktree carried a full node_modules; the global virtual store links
one slot per package version instead. pnpm keeps an explicit setting on
under CI, so a pnpmfile hook turns it off there: CI packages from,
caches and verifies a project-local node_modules.
* fix(build): keep shared electron and windows-process-tree dirs untouched under GVS
Every worktree runs Electron from the one shared electron package, so the
per-repo dist cache must not swap that dist and a foreign-arch install must
not land in it. The relay addon builder now repairs and compiles a private
copy instead of the shared windows-process-tree dir.
* fix(build): accept CLI runtime imports reached through an in-artifact store link
Under the global virtual store node_modules/<pkg> realpaths into a store
outside the repo. The check still rejects anything not reached through a
node_modules link inside the artifact, such as a stray ~/node_modules.
* docs: explain the global virtual store layout and the ~/node_modules hazard
* test: compare CLI closure paths with POSIX separators so the suite passes on Windows
* build: keep Windows on the normal pnpm layout unless ORCA_PNPM_GLOBAL_VIRTUAL_STORE=1
Store links are true symlinks on Windows, and installs without admin or
Developer Mode are unverified; CI still forces the normal layout first.
* fix(build): close the remaining shared-slot gaps under the global virtual store
- verify-skills-cli-runtime resolves each import from the importer's realpath,
as Node does, so an artifact-local copy can't mask a ~/node_modules load.
- A concurrent sibling install of the shared Electron dist is kept, not swapped out.
- rebuild:node goes through ensure-native-runtime, which builds a shared
node-pty beside the slot instead of node-gyp cleaning its build/ dir.
* fix(build): keep pnpm rebuild:node a forced rebuild
It replaced pnpm rebuild node-pty, which always rebuilt; without --force it
only rebuilt a node-pty that failed to load.
* docs: note that the store switches must stay set, since pnpm run relinks
* build: move the pnpmfile under config/ to keep the repo root allowlisted
pnpm-workspace.yaml points pnpm at it; the lockfile checksum is unchanged
because the content is.
* fix(build): enable the global virtual store from the pnpmfile, not the workspace file
pnpm exec derives NODE_PATH from the file setting, so even with the store
forced off under CI every child process got the root node_modules on its
NODE_PATH; a Windows rebuild fixture then resolved the real
@orca/windows-registry it deliberately omits. The hook now sets the store
both ways, and ORCA_PNPM_GLOBAL_VIRTUAL_STORE=0/1 is the one switch.
* test(pty): keep F3's concurrent spawns on the node-pty mock
Two concurrent first import('node-pty') calls race vitest's mock registry
and the loser loads the real addon under the mocked linux platform. It
passed only where a platform-agnostic build/Release happened to exist,
so it failed on every shared-store worktree.
* ci: fail PRs whose packages need a dependency they never declared
A new dependency_isolation job installs with the global virtual store
forced on (ORCA_PNPM_FORCE_GLOBAL_VIRTUAL_STORE_IN_CI), HOME and the store
in an empty temp dir, then runs pnpm tc and the undeclared-dependency test.
Postinstall now warns when a node_modules above the shared store, such as
~/node_modules, would mask the same class of bug locally.
* ci: set the isolation job's HOME and store through GITHUB_ENV
Job-level env cannot read the runner context, which made pr.yml invalid.
12.10.1 repairs a global-virtual-store slot an interrupted install left
without dependency links (pnpm/pnpm#16642); 12.12 adds addMissingPeerTypes.
The CI store-mode and terminal-perf gates follow the pin they were measured
against, and mobile/ and config/audit-tools record the same pnpm.
tests/ is typechecked by nothing in CI, so the Windows CLI matrix spec imported a helper that never existed (#30231). Per-file error counts under tests/ (tsc -p config/tsconfig.e2e.json) may now only go down.
Worktree ids built as `${repo.id}::${path}` by hand are how one worktree got two
spellings (#30150). New code composes them through src/shared/worktree/id.ts; the
ratchet counts the remaining hand-built ids per file (18) and may only go down. Six
byte-identical `${repo.id}::${repo.path}` sites move to getRepoMainWorktreeId.