Commit Graph
1183 Commits
Author SHA1 Message Date
Neil 3be5039eed fix: bump zod to 4.6.7 for recursive schema type compatibility (#31222) 2026-10-20 14:30:10 -07:00
Neil 12bf32836b feat(ci): ratchet raw repo connectionId host checks; migrate the cheap ones (#31096)
* 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.
2026-10-20 14:00:32 -07:00
Neil c8962bd529 perf(build): check CLI with Bun before TypeScript emission (#31043) 2026-10-20 01:51:32 -07:00
Neil e534527407 perf(checks): use Bun for end-to-end typechecking (#31046) 2026-10-20 01:00:32 -07:00
Neil 972d6dcb0a Speed up macOS updates by avoiding background installer throttling (#30944)
* fix(updater): avoid macOS background installer throttling

* fix(updater): harden installer scheduling build and diagnostics
2026-10-19 20:41:54 -07:00
Neil 92eb9790bc ci: fail the static job on unformatted files (#30863)
* 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).
2026-10-19 16:08:52 -07:00
Neil baf123eda8 fix(persistence): repo-row store writes take the row, not the id; ratchet the rest (R4-2a) (#30756)
* 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
2026-10-19 10:14:07 -07:00
Neil 90e99c48f4 refactor(pty): every PTY retirement names its exit proof; ratchet the unproven ones (#30722)
* 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
2026-10-19 05:48:19 -07:00
Neil 7ce58ec47e refactor(terminal): one PTY attribution for teardown, close and identity; delete the deprecated owner resolver (#30626)
* 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)
2026-10-19 01:50:57 -07:00
Neil f184c13bd7 Align root Node types with the supported Node 24 runtime (#30617)
* Align root Node types with the supported Node 24 runtime

* Preserve mobile rejection listeners with Node 24 types
2026-10-19 00:46:25 -07:00
Neil 4fc1855bda build: share one pnpm virtual store across worktrees (off in CI and on Windows) (#30254)
* 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.
2026-10-18 20:36:24 -07:00
Neil 5779a07a6a build: pin pnpm 12.14.0 (#30481)
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.
2026-10-18 19:27:33 -07:00
Neil db17b33ef9 ci: ratchet the legacy relay fence (importers of ssh/legacy-relay/ and COMPAT(legacy-relay-bridge) seams) (#30317)
Both lists may only shrink, so the 2027-01-15 retirement edits a known set of files.
2026-10-18 03:41:49 -07:00
Neil 10505696a2 ci: ratchet tests/ type errors so a broken spec import can't land unnoticed (#30286)
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.
2026-10-18 02:35:04 -07:00
Neil a2946f0a65 chore(worktree): composeWorktreeId plus a ratchet on hand-built worktree ids (#30238)
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.
2026-10-18 00:51:41 -07:00
Neil 7ede32ec12 ci: fail when CI config, workflows or e2e code name a src/ file that no longer exists (#30224)
* ci: ratchet src/ path literals in CI config, workflows and e2e code that name no file

* ci(path-literal): resolve relative imports exactly

* ci(path-literal): resolve against tracked files only; read ./src imports

* ci(path-literal): scan src/packages and src/types references too

* ci(path-literal): skip paths quoted in comments

* ci(path-literal): keep a recursive glob's boundary

* ci(path-literal): strip comments with a string-aware scan; any import occurrence is exact

* ci(path-literal): imports need a module or index; relative globs and folders stay prefixes
2026-10-18 00:02:58 -07:00
Neil d4aa9dcadc revert: restore pinned npx skills installer (#30207)
Reverts #30185 so the separate installer project can proceed. The restored shared command already pins skills@1.7.2 for CLI, desktop updates, and setup commands.
2026-10-17 23:24:02 -07:00
Neil ffb818bf61 Ship the locked skills installer instead of executing npx (#30185)
* security: ship a locked skills installer instead of running npx

* chore: remove retired skills process import exception

* test: include the locked installer in packaged layout fixtures

* test: reuse the real installer builder for package fixtures
2026-10-17 22:51:14 -07:00
Neil dcfe8c9e6c perf(typecheck): pin Bun as a devDependency and typecheck with it (#30149)
* perf(typecheck): pnpm tc uses the pinned Bun checker when available

bun check (Bun 1.4.3, TypeScript 7.0.2 parity, already used by CI) checks the
node project in ~29 CPU-s / ~2 GB peak vs tsc's ~98 CPU-s / ~16.6 GB. tsc's
incremental cache rarely helps agents: noEmit builds store no declaration
signatures, so the first edit to any file after a cold check re-checks every
transitive importer (a comment in src/shared/constants.ts re-checks all 18k files).

pnpm tc / tc:node / tc:web / tc:cli now pick the Bun from config/.bun-version when
it is on PATH and fall back to tsc with a one-line hint otherwise;
--compiler=native forces tsc. The pinned-Bun probe is shared with the
localization extraction check.

* build: pin Bun as a devDependency and make it the only default checker

The PATH probe left anyone without the exact pinned Bun (including a 1.4.2
global) on tsc's expensive path. bun@1.4.3 is now a devDependency, its
postinstall is allowed, and pnpm tc / typecheck:ci run that binary. A missing
binary is an error pointing at pnpm install rather than a silent fallback;
--compiler=native still selects tsc. A test keeps the devDependency equal to
config/.bun-version.

* ci: link the pinned Bun after the script-free install

--ignore-scripts leaves node_modules/.bin/bun as bun's postinstall stub, so the
typecheck runner found no binary and any pnpm-run script calling bun would hit
the stub before setup-bun's copy. pnpm rebuild bun runs only that allowed build.
2026-10-17 22:43:00 -07:00
Neil c359d09f22 chore(deps): remove unused desktop and mobile declarations (#30182)
* chore(deps): remove unused desktop and mobile declarations

* fix(deps): retain parser used by historical-release tests
2026-10-17 22:34:51 -07:00
Neil a42049f297 fix(tooling): lock routine dependency audit executables (#30183) 2026-10-17 22:12:03 -07:00
Neil 33e6785fe2 fix(deps): update vulnerable dependencies and prune stale overrides (#30143) 2026-10-17 19:01:40 -07:00
Neil ee56703d51 chore(ci): ratchet importers of the deprecated runtime-owner resolvers (#30002)
* chore(ci): ratchet renderer importers of the deprecated runtime-owner resolvers

getRuntimeEnvironmentIdForWorktree and resolveRuntimeOwnerForWorktree read a
worktree two hosts publish as not-a-runtime, which callers treat as local. The
62 renderer files importing them are grandfathered; the list may only shrink.

* ci: register the deprecated runtime-owner ratchet step in the preflight phase map
2026-10-17 00:38:41 -07:00
Brennan Benson 99788a91fa feat(acp): a managed runtime installs only when asked, with progress and cancel (#29023)
* feat(acp): a managed runtime installs only when asked, with progress and cancel

One installation per runtime owns every change to its releases: it reclaims old ones once, first,
then installs one at a time. An install checks free space, downloads with byte progress, extracts,
hashes each member (abortable), writes the receipt, then publishes by rename, the commit point: a
cancel after it reports success. Reinstall rehashes the pinned release without downloading. A
status read waits, bounded, for a newer revision and rereads the installed release from disk.

* fix(runtime): Install repairs a damaged copy, says when unzip is missing before downloading, and reads a failed download plainly

- Install over a damaged pinned copy downloads and verifies a fresh one, then swaps the damaged
  folder into staging, so there is no dead end; status reports the damaged fact.
- A host that cannot unpack the archive (no unzip) reports it in status and refuses Install
  without downloading.
- A network failure while downloading reads "Couldn't download …"; the raw error goes to the log.
- Reclamation skips the whole pass when a release folder could not be read.

* fix(runtime): a status read that can't read the installed files fails instead of saying not installed, and extraction uses the extractor the preflight found

* fix(runtime): a host that can't report free disk space still installs, and says so in the log

* fix(runtime): a download that can't be fixed by retrying says so instead of blaming the network

* fix(runtime): Install unpacks the release itself, checks it starts before publishing, and Cancel answers with the outcome

- The managed install unzips in-process (fflate, bundled into main), so a host without unzip can install; the unzip preflight and the missingProgram status field are gone. The Node runtime download keeps its own extractor.
- A runtime may provide validate(): Install starts the downloaded copy once before publishing it, and an existing copy before reusing it; a failure reads "<agent> couldn't start on this host".
- No 'cancelling' phase: Cancel resolves once the work has unwound and returns cancelled, or succeeded when it had already committed.
- Byte progress every 250 ms; status rereads disk and PATH only on a first read or a phase change.
- The declared download size is checked only for an uncompressed body.
- An existing copy whose files hash correctly but whose receipt is missing gets its receipt rewritten instead of reading as damaged forever.

* refactor(runtime): move the steps that put one release on disk out of the installer

The installer class now holds only its lifecycle: the shared operation state, the one-at-a-time
queue, install, cancel, status, reclaim and progress publishing. Reusing a whole copy, the free
space check, the download, unpacking, checking each file, the start check, the receipt, the
publishing rename and the failure sentences move unchanged into managed-runtime-release-install.ts,
which gets the pin, folder, label, signal and two callbacks (progress, and the commit point right
after the rename) instead of the class. No behavior change; this keeps the installer well under
the file-length limit once Remove lands in it.

* chore(deps): lock fflate for the in-process runtime unzip

* fix(runtime): a failed download blames the release only for a proven mismatch, so a refused proxy reads as a network problem

* fix(runtime): runtime downloads skip the HTTP cache, so Remove frees the space and nothing is stored twice
2026-10-16 17:38:02 -07:00
OrcaWinandOrcaWin 7e778d8129 perf(build): skip compiling tests into CLI output (#29400)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-15 23:25:26 -07:00
OrcaWinandOrcaWin 144aac00cc chore: update Vite, Tailwind, and build tooling (#29345)
* chore: update build and test toolchain

* fix(build): isolate shared code from CLI entry overwrites

* fix(build): preserve main startup asset paths

* test(build): override resolved Vite 8 fixture inputs

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-15 22:57:00 -07:00
OrcaWinandOrcaWin 01800a092a chore: update Oxlint and Oxfmt to latest releases (#29291)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-15 18:11:48 -07:00
Neil bd62269d95 ci: reuse pinned fast typecheck for Windows installer tests (#29149)
* ci: reuse pinned fast typecheck for Windows installer tests

* test: follow shared desktop assets and compiler dependency changes
2026-10-15 05:46:04 -07:00
Neil 02356732b0 ci: finish successful aggregate checks without a runner queue (#29136)
* ci: accept validated aggregate success without queuing a runner

* test(ci): exercise aggregate rejection of an unexpected preflight skip

* ci: restore normal preflight after hosted rejection proof
2026-10-15 04:39:30 -07:00
Jinjing 25908865b0 Bump Electron to 43.7.8 to fix find-in-page looping (#28909)
* Bump Electron to 43.7.8 to fix find-in-page looping

Find-in-page could loop when the host page contained iframes beside a
webview, so searches in the guest did not finish or wrap correctly.
Electron 43.7.8 includes the upstream fix (electron/electron#54199).

- Pin electron to 43.7.8 in package.json, pnpm-lock.yaml, and the
  Node runtime pin.
- Raise the runtime floor in electron-runtime-floor.test.ts to 43.7.8.
- Add an Electron regression test that runs a find traversal in a
  background window with host iframes before, after, and around a
  webview, checking forward and backward matches wrap correctly.

* rm junk file
2026-10-15 00:02:12 -07:00
Neil 7244bf3e6f Reclaim idle worktree space automatically on dev startup (#28864)
* Run safe worktree cleanup on dev and add interactive pnpm reclaim

* Rename storage cleanup command to reclaim-space

* Keep real stream constructors in transcript dependency tests

* Detach cleanup output and align historical cache previews
2026-10-14 15:47:02 -07:00
Neil 13a75d3939 [CI] Reduce redundant test work and import existing helpers directly (#28365)
* [CI] Reduce redundant test work and import existing helpers directly

* [CI] Preserve provider guards and pin the mobile browser test core

* [CI] Fix CI test setup and verify retained contracts

* [CI] Compare native media chunks without boxed byte arrays

* [CI] Detect shifted native media reads with position-sensitive bytes
2026-10-13 20:25:00 -07:00
OrcaWinandOrca Worker 4559c250b3 fix(windows): restrict secure files in-process instead of spawning icacls (#28135)
* fix(windows): restrict secure files in-process instead of spawning icacls

Every secure write on Windows spawned icacls.exe five times on the calling
thread (the Electron main thread for most stores): verify, /reset,
/inheritance:r /grant:r, verify, verify. Process creation under endpoint
security costs ~0.6 s each, so each write froze the UI for seconds (#26027).
The /reset + /inheritance:r pair is also what behavioural EDR scores as
ransomware tradecraft (#25583).

Add native/windows-security (@orca/windows-security): a small N-API addon
over advapi32 that reads and writes DACLs in-process. It exposes the token
user SID, a DACL read as SDDL, a DACL write (sync and on the thread pool),
a create-with-DACL file write, and an additive grant. Policy and
verification stay in TypeScript.

- writeSecureFile creates each temp file already carrying the protected
  user/SYSTEM/Administrators DACL (CreateFileW + SECURITY_ATTRIBUTES), reads
  it back once, and renames it. No ACL is modified on the write path and no
  process starts. Before: 5-7 spawns, 94-138 ms per write. After: 0 spawns,
  ~0.5 ms.
- A secure directory is verified once per process. It is rewritten only
  when verification fails, on the thread pool, because the write walks
  every descendant (~2 s on a 29k-entry userData).
- The verifier follows OpenSSH's rule: only the user, SYSTEM and
  Administrators may be granted anything, and the path must be protected
  (or, for a file, inherit from a secure parent). DACLs written by the
  icacls-era hardening pass unchanged, so there is no migration.
- If the addon cannot load, the write still happens and reports
  unrestricted, logged once. There is no icacls or PowerShell fallback.
- grantDirAcl, the startup userData grant, and the install-dir probe and
  package repair use the same addon, so whoami.exe and icacls.exe are no
  longer spawned anywhere at runtime.
- orcad's Windows slots ship the addon beside their bundles (built per
  arch, PE-machine checked). Its data-root privacy check fails closed, so
  without the addon a Windows orcad would not start.

Fixes #26027
Fixes #25583

* refactor(secure-file): harden existing Windows files through the sync lane

The read path called restrictWindowsPathSync directly and recorded success
itself; the existing sync lane already does both. One call site fewer.

* refactor(windows-security): one load, one rule, no fallthrough (review #28135)

- The descriptor parser is anchored, so a conditional ACE (its body holds
  parentheses) or anything else it cannot read fails closed instead of
  verifying the ACEs before it.
- findSecureDaclViolation is one flat rule: protected, allow rules only for
  the user, SYSTEM and Administrators (OpenSSH's ACE rule), and full
  control for the user. The inherits-from-a-secure-parent exception is gone:
  it accepted exactly what loosening the parent reaches.
- The addon and the user SID load once, behind one wrapper that adds the
  extended-length prefix to every path and runs async DACL writes to one
  path in order, so a startup grant cannot land after a re-lock and restore
  the broader DACL.
- With the addon loaded, a failed born-restricted create throws with the
  matching Node error code instead of falling back to a plain write.
- A grant leaves a NULL DACL untouched instead of turning 'everyone' into
  'only this SID'. WriteFile runs once and fails on a short write.
- The artifact create-intent store writes through writeSecureFile and drops
  its own temp writer, directory LRU and synchronous directory re-lock.
- Test seams the global fake makes redundant are gone.

* fix(packaging): fail a Windows build whose workspace addon binary is missing or foreign-arch

The @orca/windows-* workspace links exist on every host, so their presence
proved nothing: a missing or wrong-architecture .node only surfaced at
runtime, as unrestricted secure files or an orcad that refuses to start.
beforePack now reads each addon's PE machine against the target arch.

* refactor(windows-security): prune what the native ACL left dead (review #28135)

- Sync-spawn ratchet: hardenSecurePath and the sync-flag exemption it alone
  used are gone, and the advice and baseline header no longer say secure
  writers run icacls.
- The install-dir probe reads ACEs through parseSddlDacl, so an ACE it
  cannot parse fails the whole reading closed instead of being skipped; AC
  is a resolved alias there now.
- The packaging check reads the addon binary through node_modules, the copy
  that ships.
- No winCode-to-errno table: nothing reads .code off a failed secure write.
- The load memo is not mutated; secure-path keeps its own log-once flag.
- grantInstallDirPackageAcl is a plain function, and secure-file restricts
  a Windows directory and a POSIX path directly instead of through one
  two-platform helper.

* fix(windows-security): resolve LA in real-ACL tests; tear a file whose close fails

- The GitHub Windows runner is the built-in Administrator (RID 500), whose
  SID reads back as LA. Production already resolves it through
  localDomainSidOf; the win32 test helpers parsed without it. One shared
  readDaclOf resolves aliases for every real-ACL suite, and the verifier's
  unit test covers an LA-aliased directory and LA as a stranger.
- createFileWithDacl treats a failed CloseHandle like a failed write (lost
  data on a network volume) and deletes the torn file.
- Drop a duplicate test reset and a typo.

* fix(windows-security): load only from inside the app; read DACLs as structure (review #28135)

- Loader: orcad's staged binary first, then the workspace package only if
  its real path is inside the app (process.resourcesPath, or this
  checkout). A bare specifier otherwise walks every parent up to
  C:\node_modules, which any local user may create, and orcad holds
  credentials.
- The addon reads a DACL as { protected, aces: [{ type, flags, mask, sid }] }
  via GetAce, with a SID only for plain allow/deny ACEs and null for a NULL
  DACL. findSecureDaclViolation and the install-dir probe check that
  structure, so conditional, callback and object ACEs fail closed. The SDDL
  parser, its alias table, LA/localDomainSid resolution and getDaclSddl are
  gone; SDDL is only the write format.
- Install-dir tree pass: files flagless as before, but a directory only when
  its own DACL is protected (inheritably). Re-granting inheriting
  directories re-propagated each subtree (3.2 s for 1,093 directories). The
  root is no longer granted, or counted, twice. The two passes are plain
  functions behind a { root, tree } seam.
- One namespacing (path.win32.toNamespacedPath) at the addon boundary, also
  the per-path ordering key; the comment states the real sync-grant
  invariant.
- restrictWindowsDirectory(dir): Promise<boolean> replaces the callback
  lane; the reporter is guarded once, so the 'settle' stage is gone.
- Sync-spawn ratchet: the secure writers no longer spawn or block, so they
  leave the gate (109 -> 61 uses).
- Errors name the Win32 call and code only; a failed
  GetSecurityDescriptorControl fails the write instead of defaulting to
  unprotected.

* test(secure-file): assert the durable POSIX write's outcome, not the old chmod order

writeSecureFile now chmods the temp file to 0o600 before fsyncing it, so
the fsync also covers the mode change and the file is owner-writable when
opened. Assert what matters under umask 0o200: the write succeeds, the
temp file is fsynced read-only, and the published file is exactly 0o600.

* fix(windows-security): namespace paths with the host's toNamespacedPath

path.win32.toNamespacedPath rewrote POSIX temp paths (/tmp/x -> \tmp\x) in the
Linux runs of the Windows-logic unit tests, so the fake addon wrote to the wrong
place. The addon only runs on Windows, where the host function is the win32 one;
elsewhere it is the identity. The long-path assertion runs on Windows only.

* test(windows-security): assert the install-dir long-path prefix on Windows only

The prefix comes from the host's toNamespacedPath, which is the identity off
Windows; the Linux run of this suite expected the win32 escape.

* fix(windows-user-data-acl): grant the root even when its children cannot be listed (review #28135)

A readdir failure on userData aborted the whole grant, so the root grant
that could repair a DACL refusing the listing was never attempted. The
listing failure is now recorded, the root is still granted, and the
marker stays withheld so the next launch retries.

* test(windows-security): wait for relock propagation, accept Entra ID SIDs (review #28135)

- The large-relock test waited only for the parent to turn protected,
  which happens before propagation reaches the children, then read a child.
  It now waits on the parent and both ends of the child range.
- The token-user test rejected S-1-12-1 (Entra ID) accounts, failing the
  suite on Entra-joined dev machines.

---------

Co-authored-by: Orca Worker <orca-worker@localhost>
2026-10-13 01:56:55 -07:00
Neil c8996d76a5 Speed up daemon smoke checks and keep compiler slots busy (#28106)
* Remove repeated daemon smoke builds and idle compiler slots

* Test compiler scheduling through its process interface

* Keep preview component below the existing line limit with a type-only split
2026-10-12 17:20:19 -07:00
NeilandClaude Opus 5.5 125007b680 chore: upgrade terminal dependencies to matching payload betas (#27935)
* chore: upgrade terminal dependencies to the matching payload betas

* test: harden xterm payload contract, align mobile pins, fix serialize-at-ref across version bumps

- Cover foreign-payload replacement and the shared blank-cell reset in the
  installed image payload contract (both mutants previously survived).
- Bump mobile's xterm, webgl and unicode11 pins to the root's betas.
- Resolve the ref's own serialize version and patch so the differential
  fuzz baseline builds across a version bump.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 15:53:42 -07:00
Brennan Benson fa4ef2b961 ci: stop new main-process code from freezing Orca with synchronous launches (#27756)
* ci: ratchet synchronous child-process launches in main and shared code

The Electron main process freezes every window while a synchronous child
process runs, and on Windows each launch is slow because antivirus scans
it. Nothing stopped new synchronous launches from landing in main-process
code, and the spawn observer behind ORCA_MAIN_THREAD_DIAGNOSTICS only saw
async spawns, so the worst blocks were invisible to it.

Ratchet: check:main-sync-spawn-ratchet parses production src/main and
src/shared files (test-only paths excluded) and fails when a file not in
config/main-sync-spawn-baseline.txt reaches execFileSync, spawnSync or
execSync from child_process, runProcessSync from @orca/process-host, or
one of the synchronous secure-file writers (they run icacls on Windows).
Named, aliased, namespace, default, require() and import() forms count.
A baseline entry whose file no longer launches synchronously fails as
stale, so the list can only shrink. It runs in lint and in the PR
static-analysis job next to the process-host import check.

Observer: runProcessSync now reports to the spawn observer, timing the
whole call (that is how long the thread was stuck) and reporting from a
finally so failing, throwing and timed-out children are counted too.

Runtime behavior is unchanged except that the diagnostics observer now
sees synchronous launches; with no observer registered the sync path
pays one extra branch.

* ci: count sync launches per file and gate the repo's sync wrappers

The sync-spawn ratchet only tracked which files launched synchronously, so a
file already on the list (wsl.ts, win32-utils.ts, git-exec-file.ts) could add
more launches unnoticed, and new callers of the repo's own synchronous
wrappers passed CI.

The baseline now stores a per-file count of uses, in the same format as the
focus-read ratchet: a rise or a new file fails, a lower or zero count fails
until `pnpm check:main-sync-spawn-ratchet --prune` lowers it, and --prune
never raises an entry. Every value reference to a gated function counts
(call, hand-off, alias and its uses, re-export); imports alone count nothing.

Gated wrappers: gitExecFileSync (and the git/runner re-export),
restrictWindowsPathSync, hardenSecurePath when called with sync: true,
grantDirAcl, getWslHome, listWslDistros, wslUncDirectoryExists,
isWslAvailable (and the wsl.ts re-export), isPwshAvailable. Their 18
current caller files join the baseline.

Failure messages now give advice per family: the async process-host API for
child-process launches, the async twin for wrappers, and for the secure
writers a note that no async secure writer exists yet, so the sync one stays
and must never become a plain write (that drops the Windows ACL hardening).

Also refresh two stale blockMs descriptions and drop a no-op line from the
spawn-observer "stays silent" test.

* ci: count hardenSecurePath unless visibly async, and tighten ratchet tests

hardenSecurePath was counted only when `{ sync: true }` was written inline,
so moving the options into a variable or a spread dropped the count and the
check then asked for a prune. It now counts every use except a call with no
options or a spread-free object literal whose `sync` is absent or false.

Also: resolve backslash paths in the importing file as well as the
specifier; still visit heritage and instantiation expressions
(`extends mixin(fn)`, `fn<T>`); state the reference-only counting limit;
and point the secure-writer advice at raising this file's baseline count.

Tests cover the hardenSecurePath shapes, the alias chains that need the
fixpoint loop, and a prune that must not raise a grown row.

* test(ci): pin the main sync-spawn ratchet background step

* fix: count secure hardening calls with spread arguments
2026-10-12 11:28:25 -07:00
OrcaWinandm4air 7c13365f03 refactor(build): relay survivors named for what they are (#27962)
Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-12 04:09:18 -07:00
Brennan Benson 8cc779e576 Editor: Nim and Typst on the shared TextMate layer, so a long line can't freeze the window (#26987)
* feat(editor): run Nim and Typst on the shared TextMate layer with per-line limits

The editor's Nim and Typst highlighting moves from vscode-textmate onto the
Shiki core + Oniguruma layer chat uses, with the same per-line length cap and
time limit. A skipped line leaves the rest of the file plain instead of
freezing the window. Chat code block labels now name catalogue languages
(vue -> Vue, ts -> TypeScript).

* test(native-chat): expect the catalogue label for a ```ts fence
2026-10-11 16:54:08 -07:00
Jinwoo Hong 9ad45591af refactor(layout): keep compatibility code in named folders behind one entry (#27556)
* refactor(layout): keep compatibility code in named folders behind one entry

Moves the old-client translators into src/shared/workspace-layout/old-client/
and the remote layout snapshot recorder and persistence into
src/main/remote-layout-snapshot/, each behind an index.ts. A new
check-compat-boundary-imports script, run by pnpm lint and the PR workflow,
fails when code outside a compatibility folder imports past its index or
when a file other than the folder's named importer imports it. Moves only;
no behaviour change.

* test: drop whole-repo scan from boundary check unit tests; CI step runs it

* fix(lint): list only existing compatibility folders; resolve renderer aliases in renderer files
2026-10-11 16:06:05 -04:00
OrcaWinandm4air f9794d8efc refactor(remote): one paired E2EE handshake core for desktop, CLI, web and mobile clients (#27523)
* refactor(remote): one paired E2EE handshake core for desktop, CLI, web and mobile clients

Every paired client now builds its hello, checks the host's ready, seals its
auth frame and opens host frames through src/shared/paired-e2ee-client-handshake.
Platforms inject their own NaCl/base64 primitives. The v2 client session and its
pure-JS key schedule (@noble/hashes 1.8.0, the version mobile pins) move to
src/shared, and the per-host v2 latch policy is shared with an injected store.

No wire change: frames match the captured legacy and v2 fixtures byte for byte,
and a cross-version test drives the core against the released host and the
released client against this host. The auth step is now async so a device-key
signer can slot in later.

* fix(remote): keep @noble/hashes out of the CLI's unbundled runtime

The packaged CLI loads src/shared output from disk, so a bare @noble/hashes
import failed the packaged-runtime check. The v2 key schedule is now a
primitive: Node clients use the node:crypto schedule (moved to src/shared so
the CLI can reach it), while the browser and phone keep the pure-JS one.

* fix(mobile): re-check the v2 latch before sending a legacy auth frame

The auth step is async, so another socket can latch the host to v2 while a
legacy auth frame is being sealed. Refuse it at send time, as before.

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-11 12:14:51 -07:00
Jinwoo Hong e969881be5 chore(ratchet): layout-writer ratchets for main and renderer (#27431)
* chore(ratchet): layout-writer ratchets for main and renderer

Two per-file ratchets that may only go down: main-process callers of the
Store's layout setters plus direct edits of saved session partitions, and
renderer writes of the window's layout and per-view selection fields
(including computed-key and spread writes, counted where the key or object
is built). The frozen-mirror writers are exempt. Wired next to the
focus-read ratchet in lint and the PR workflow.

* chore(ratchet): count only membership writes of editor and browser entries

A .map over the existing openFiles or browser tab list keeps its entries
and order, so it is a content update (draft, git status, URL) and is no
longer counted by the renderer layout-writer ratchet.

* chore(ratchet): ignore variable keys and pass-through shorthands

A variable key such as exitCodes[ptyId] names no field, so only string
keys count. A shorthand key counts only in an object handed to set,
setState or a Store writer; elsewhere it passes a value on.

* chore(ratchet): prune renderer baseline after main moved
2026-10-11 14:55:11 -04:00
OrcaWinandm4air 34d7884a52 chore(ratchet): one focus-read ratchet (#27426)
Fold the empty owner-routing baseline into the focus-read ratchet. The three
deleted focus-routing helpers stay banned as a forbidden-name list.

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-11 02:06:40 -07:00
Neil c69f9d2f62 Add manual cleanup for worktree Electron storage (#27414)
* Add manual worktree storage cleanup command

* Validate cleanup exclusions and preserve failure diagnostics
2026-10-11 01:55:34 -07:00
Neil ac156c0302 perf(ci): overlap Bun typechecking and skip redundant alias scans (#27382)
* perf(ci): skip unnecessary alias scans and compare preflight compilers

* perf(ci): overlap measured Bun typechecking with static checks

* perf(ci): use measured Bun lint runtime and strengthen compiler parity checks
2026-10-10 22:48:25 -07:00
OrcaWinandm4air 2b4b60a87e refactor(relay): delete the SSH relay server, its build assets and tests (#27215)
* wip: delete the SSH relay server and its build, assets and tests

* wip: drop main/relay plugin conformance; WSL reader lookup test

* fix: drop the emptied relay bench; prune baselines

* test: keep the native halves of tests that also covered the relay

* chore(ci): drop shard timings for the deleted relay server tests

* fix: keep the directory listing budget that darwin terminal names now import

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-10 22:36:42 -07:00
ff27338299 refactor(ssh): connect fails for hosts orcad can't serve; remove the relay client path (#27214)
* wip: connect fails for hosts the managed server can't serve; remove relay session, deploy and relay UI

* wip: delete relay-only client modules left unreachable, prune their tests and gates

* wip: format

* wip: exhaustive unserved message

* wip: drop relay attestation test for the deleted ownership proof

* fix: prune stale baselines; type the test transport stub

* fix(ssh): census proves leftover relay leases; name the remedy for older terminals; translate every connect-failure surface

* test(ssh): leftover leases defer to the host census

* test(e2e): remove relay-path SSH e2e specs and their CI lanes

* test(e2e): drop the auto-convert case that built its relay era through a live relay

* test(e2e): mark relay-era browser and catalog journeys fixme until they seed a profile

* test(e2e): seed relay-era browser and catalog journeys from a saved profile

The three journeys built their old-version state through a connect without an orcad template, which now refuses the host. They now start from a seeded relay-era profile, so the first connect converts the host as an upgrading user's would.

* chore(ci): drop shard timings for the deleted relay tests

* test(e2e): carry a relay-era login cookie across the browser conversion; own every launch

* test(ci): drop the deleted SSH layout oracle from its route

* test(e2e): open the retained browser after conversion, retrying a pre-connect card

* test(e2e): bind the relay-era browser partition before seeding its cookie

A relay-era build always recorded the partition binding before a page stored
data in it. Seeding the cookie into an unbound partition made the binding store
refuse it (browser_route_partition_binding_store_invalid), so the retained tab
showed the generic routing card and Retry could never clear it.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-10 22:10:33 -07:00
OrcaWinandm4air e0376015a4 refactor(runtime): route adapter, split fences, ownership table, combined ratchet (#27218)
* refactor(runtime): route adapter, split fences, ownership table, combined ratchet

Adds callHostRoute, which calls a host-owned method on the host a HostRoute
names and reports a reply whose host identity moved mid-call as unverifiable
instead of applying it. Host fences are split: an identity fence (pairing,
runtime connection generation, the route's own SSH target) for mutations, and
the catalog data fence built on top of it for snapshot reads, so unrelated
catalog or SSH-state churn no longer moves a mutation's fence.

HOST_OPERATION_OWNERSHIP classifies every runtime RPC namespace. A new
ratchet counts getActiveRuntimeTarget( + legacyRouteFromSettings( +
settingsForRuntimeOwner( together per renderer file (227). One exemplar, the
folder workspace review-note write, moves onto the adapter.

* fix(runtime): fence the folder note write on its server; refuse aliased ratchet helpers

The folder catalog row lives on the paired server, so its write is fenced on
that server's identity, not on the SSH target the folder names. The ratchet
now counts spaced calls and value uses, and refuses an aliased import or
re-export that would hide calls from the count.

* test(ci): pin the owner-routing ratchet step to the static phase

---------

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-10 03:48:48 -07:00
Neil 1912c90141 Extract subprocess handling into @orca/process-host (#26963)
Extract subprocess execution and process contracts into @orca/process-host with explicit public entry points, independent checks, and one Node 18 compatible CommonJS runtime. Migrate application consumers and integrate package builds, dependency selection, import enforcement, and runtime resource staging.

Preserve daemon startup ownership and profile isolation, and qualify development rebuild recovery and concurrent output publication. Remove unnecessary aliases, test forwarding exports, and a duplicate output scan.

Validation: full typechecks, changed-code quality, package and artifact checks, native Windows builds/types plus 306 runtime tests, and independent native reviews.

Matched full E2E controls on original main and unchanged reviewed head both pass all 17 applicable tests (nine skips). Earlier intermittent E2E failures are documented in the PR; no assertions were weakened. The final combined-tree audit preserves both branches with no import-boundary violations.
2026-10-09 18:32:28 -07:00
Kelvin AmoabaandBrennan Benson 2cb2bef67e feat(native-chat): highlight code blocks and shell commands (#26343)
* feat(native-chat): highlight code blocks and shell commands

Uses Shiki rather than the highlight.js already in the preview: it can
resume mid-block while a reply streams.

* fix(native-chat): leave the tool row command unhighlighted

* refactor(native-chat): highlight code through a shared TextMate interface

Chat code now goes through one renderer highlighting interface built on Shiki
core and the Oniguruma WASM the editor already loads for Nim and Typst, with
VS Code Light+/Dark+. Lines that are too long or too slow leave the rest of
the block plain, failed grammar loads retry from a mounted block, every block
shares one time slice, finished-block caching counts retained text, each theme
keeps its own formatting, Vue template expressions are colored, and tool-row
commands take the grammar of the shell they ran in.

* chore(deps): drop pnpm self-install entries from the lockfile

Keeps the lockfile change to Shiki alone; the earlier commit picked up the
@pnpm/exe entries pnpm 12 writes when it runs in a fresh checkout.

* test(native-chat): give cold grammar loads time in highlighting tests

The first colored block in a test file loads its grammar and the regex
engine, which took over a second on a busy CI runner.

---------

Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
2026-10-09 13:08:42 -07:00
Kelvin AmoabaandBrennan Benson 3c075883ed feat(native-chat): make native chat replies flow in and the transcript glide when following (#26238)
* feat(native-chat): pace and fade streamed reply text

Snapshots land a clause at a time, 50-700 ms apart. Drawing behind them
at an eased speed is what makes a reply read as flowing.

* feat(native-chat): glide the transcript when following and jumping to latest

The offset is still pinned at once; only what the pin moved is eased, so
nothing races the virtualizer or a reply that keeps growing.

* chore: list remend with the other bundled renderer packages

dependencies is for packages the main process loads at runtime.

* Bound streamed reply reveal and preserve chat interactions

* Smooth overlapping chunks without postponing reveal deadlines

* Fix first-reply following and pin settled layout changes

* Pin concurrent media layout changes without a follow glide

* Keep native chat layout stable when replies complete

* Model delayed browser scroll events in navigation tests

---------

Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
2026-10-08 15:38:49 -07:00