Merge origin/main into structured-session-eviction

Three mobile conflicts, all from this stack's own extractions meeting main:

- rpc-client.ts: this branch had already moved RpcClient into rpc-client-contract
  and re-exports it; main still declares it inline. Keeping both produced a
  duplicate declaration. Dropped the inline copy and ported the one member main
  had that the contract lacked (getLastInboundAt).
- worktree-create-retry(.test).ts: the cutover-error split moved
  isLogicalClientCutoverError out of stable-logical-rpc-client, and main added two
  new imports alongside the old path. Both kept, pointed at the new module.
- StreamRequest keeps paramsForReconnect from this branch.

Root typecheck, mobile tsc, full lint gate, and the mobile suite (3755 tests) green;
pnpm-lock.yaml untouched.
This commit is contained in:
Brennan Benson
2026-08-19 18:28:16 -07:00
67 changed files with 4495 additions and 194 deletions
@@ -0,0 +1,176 @@
import { readFileSync } from 'node:fs'
import { join, resolve } from 'node:path'
import { describe, expect, it } from 'vitest'
import { parse } from 'yaml'
const projectDir = resolve(import.meta.dirname, '../..')
const readWorkflow = (relativePath) => parse(readFileSync(join(projectDir, relativePath), 'utf8'))
const MAC_WORKFLOWS = [
['hourly', '.github/workflows/hourly-mac-build.yml', 'build-hourly-mac'],
['daily', '.github/workflows/daily-mac-build.yml', 'build-daily-mac'],
['adhoc', '.github/workflows/adhoc-mac-build.yml', 'build-adhoc-mac']
]
const winWorkflow = () => readWorkflow('.github/workflows/dev-channel-win-build.yml')
const winSteps = () => winWorkflow().jobs['build-win'].steps
const stepNamed = (steps, name) => steps.find((step) => step.name === name)
describe('dev-channel Windows build wiring', () => {
it.each(MAC_WORKFLOWS)(
'calls the shared Windows workflow from %s with its own channel',
(channel, path, jobName) => {
const jobs = readWorkflow(path).jobs
const winJob = jobs[`build-${channel}-win`]
expect(winJob).toBeDefined()
expect(winJob.uses).toBe('./.github/workflows/dev-channel-win-build.yml')
expect(winJob.with.channel).toBe(channel)
// Why `./` and not a pinned @main: `uses:` resolves against the ref this
// workflow file came from, which for an ordinary dispatch is main. A branch
// deliberately selected in the Actions picker already supplies its own copy
// of the whole file, so this follows that rule rather than adding a second.
expect(winJob.uses.startsWith('./')).toBe(true)
expect(winJob.needs).toBe(jobName)
expect(winJob.secrets).toBe('inherit')
}
)
// Why gated on published: the Windows leg uploads into a release the mac leg
// created, and refuses to create one itself. Without this it would run for a
// tag that was never published — or, for hourly, for a run that skipped.
it.each(MAC_WORKFLOWS)(
'only runs the %s Windows leg once the release is live',
(channel, path, jobName) => {
expect(readWorkflow(path).jobs[`build-${channel}-win`].if).toBe(
`needs.${jobName}.outputs.published == 'true'`
)
}
)
// The Windows job names the release by consuming these; a renamed step id
// would silently resolve them to empty strings and upload nowhere.
it.each(MAC_WORKFLOWS)(
'exposes the %s tag, version and commit as job outputs',
(_c, path, jobName) => {
const job = readWorkflow(path).jobs[jobName]
const stepIds = job.steps.map((step) => step.id).filter(Boolean)
for (const key of ['tag', 'version', 'head_sha', 'published']) {
expect(job.outputs[key]).toBeTruthy()
}
// Every referenced step id must actually exist in the job.
for (const expression of Object.values(job.outputs)) {
const referenced = [...expression.matchAll(/steps\.([A-Za-z0-9_-]+)\./g)].map((m) => m[1])
for (const id of referenced) {
expect(stepIds).toContain(id)
}
}
}
)
// Why this is asserted: the previous design dispatched a sibling run, which
// needed actions:write on a job holding macOS signing credentials. Calling the
// workflow directly removes that, and it must not creep back.
it.each(MAC_WORKFLOWS)(
'does not widen the %s signing job to actions:write',
(_c, path, jobName) => {
expect(readWorkflow(path).jobs[jobName].permissions?.actions).toBeUndefined()
}
)
})
describe('dev-channel Windows build workflow', () => {
it('is both callable by the mac workflows and dispatchable on its own', () => {
const triggers = winWorkflow().on ?? winWorkflow()[true]
expect(Object.keys(triggers)).toEqual(
expect.arrayContaining(['workflow_call', 'workflow_dispatch'])
)
// workflow_call cannot express `type: choice`, so the channel set has to be
// enforced in the job itself.
expect(stepNamed(winSteps(), 'Vet the requested inputs').run).toContain('hourly|daily|adhoc')
})
// Why windows-2022: windows-latest moved to the Windows 2025 / VS 2026 image
// before node-gyp could detect VS 18, breaking native dependency install.
it('pins the same Windows image release-cut builds on', () => {
expect(winWorkflow().jobs['build-win']['runs-on']).toBe('windows-2022')
})
// The guard against a branch whose electron-builder config predates Windows
// dev builds: without it, publish.repo resolves to the main repo.
it('verifies the dev-channel packaging identity before building', () => {
const steps = winSteps()
const names = steps.map((step) => step.name)
const verify = names.indexOf('Verify dev-channel packaging identity')
const build = names.indexOf('Build app')
expect(verify).toBeGreaterThanOrEqual(0)
expect(build).toBeGreaterThan(verify)
expect(stepNamed(steps, 'Verify dev-channel packaging identity').run).toContain(
'verify-dev-channel-packaging.mjs'
)
})
// Why: electron-publish creates a release when it cannot find the tag under
// `--publish always`. If the mac leg discarded its draft while this was
// building, that would mint an untitled Windows-only release.
it('confirms the target release exists before publishing into it', () => {
const names = winSteps().map((step) => step.name)
const confirm = names.indexOf('Confirm the target release still exists')
const publish = names.indexOf('Publish Windows artifacts')
expect(confirm).toBeGreaterThanOrEqual(0)
expect(publish).toBeGreaterThan(confirm)
})
// electron-publish refuses to upload into a release published more than two
// hours ago. The mac leg publishes as soon as it finishes, so a slow notary
// queue plus a slow Windows build crosses that line and drops every asset.
it('opts out of the publisher two-hour upload window', () => {
expect(stepNamed(winSteps(), 'Publish Windows artifacts').env.EP_GH_IGNORE_TIME).toBe('true')
})
it('packages Windows unsigned through the shared electron-builder config', () => {
const publish = stepNamed(winSteps(), 'Publish Windows artifacts')
expect(publish.with.command).toContain(
'electron-builder --config config/electron-builder.config.cjs --win --publish always'
)
// No signing env: SignPath's approval waits cannot fit a dev cadence, which
// is the entire reason these builds are unsigned.
expect(Object.keys(publish.env)).not.toContain('SIGNPATH_API_TOKEN')
})
// Why both: a release carrying the installer but not the manifest is a row the
// picker offers and the in-app update 404s on.
it('requires the manifest and the installer before calling the build good', () => {
const verify = stepNamed(winSteps(), 'Verify Windows update manifest published')
expect(verify.run).toContain('latest.yml')
expect(verify.run).toContain('orca-windows-setup.exe')
})
// Why: this is callable and separately dispatchable, and workflow_call takes
// its inputs as free text, so it re-derives the ref guarantee rather than
// trusting whoever called it.
it('vets the requested commit before checking it out', () => {
const names = winSteps().map((step) => step.name)
const vet = names.indexOf('Vet the requested inputs')
const checkout = names.indexOf('Checkout the built commit')
expect(vet).toBe(0)
expect(checkout).toBeGreaterThan(vet)
expect(stepNamed(winSteps(), 'Vet the requested inputs').run).toContain('--contains')
})
// Telemetry's transport gate accepts only 'stable' or 'rc'; leaving the build
// identity unset is what keeps unvetted artifacts silent.
it('never stamps an official telemetry build identity', () => {
const build = stepNamed(winSteps(), 'Build app')
expect(Object.keys(build.env ?? {})).not.toContain('ORCA_BUILD_IDENTITY')
})
})
@@ -0,0 +1,125 @@
#!/usr/bin/env node
// Why this exists: the dev-channel workflows run from main, but they build (and
// therefore read `config/electron-builder.config.cjs` from) whatever ref was
// asked for. A branch cut before Windows dev builds landed has a config that
// ignores ORCA_WIN_*, which would resolve `publish.repo` to the *main* repo and
// leave the release identity signed-looking. Publishing would then fail deep
// inside electron-builder with a 404 from a token scoped to the dev repo — or,
// worse, succeed against a repo it was never meant to touch.
//
// So: load the config exactly as electron-builder will, and assert the identity
// it produced matches the channel and platform the workflow believes it is
// building. Runs before packaging, fails with a sentence someone can act on.
import { createRequire } from 'node:module'
import { resolve } from 'node:path'
import { pathToFileURL } from 'node:url'
const CHANNEL_REPOS = {
hourly: 'orca-hourly',
daily: 'orca-daily',
adhoc: 'orca-adhoc'
}
const CHANNEL_VERSION_ENV = {
hourly: 'ORCA_HOURLY_BUILD_VERSION',
daily: 'ORCA_DAILY_BUILD_VERSION',
adhoc: 'ORCA_ADHOC_BUILD_VERSION'
}
export function collectDevChannelPackagingProblems({ channel, platform, config, env }) {
const problems = []
const expectedRepo = CHANNEL_REPOS[channel]
if (!expectedRepo) {
return [
`Unknown dev channel "${channel}"; expected one of ${Object.keys(CHANNEL_REPOS).join(', ')}.`
]
}
if (config.publish?.repo !== expectedRepo) {
problems.push(
`publish.repo is "${config.publish?.repo}" but this ${channel} build must publish to "${expectedRepo}". ` +
`The checked-out ref's electron-builder config does not understand this channel on ${platform} — rebase it onto a main that does.`
)
}
if (config.publish?.releaseType !== 'prerelease') {
problems.push(
`publish.releaseType is "${config.publish?.releaseType}" but dev-channel builds must publish as "prerelease".`
)
}
// Why version too: `extraMetadata.version` is what stamps the tag the workflow
// already created. A config that dropped it would package package.json's
// version and upload into the wrong release entirely.
const expectedVersion = env[CHANNEL_VERSION_ENV[channel]]
if (expectedVersion && config.extraMetadata?.version !== expectedVersion) {
problems.push(
`extraMetadata.version is "${config.extraMetadata?.version}" but the workflow computed "${expectedVersion}".`
)
}
if (platform === 'win32') {
// The one that silently breaks updates rather than failing the build: a dev
// build that advertises a publisherName can never install its own channel's
// next build, because electron-updater verifies against the name baked into
// the installed app.
if (config.win?.verifyUpdateCodeSignature !== false) {
problems.push(
'win.verifyUpdateCodeSignature must be false for unsigned dev builds, or electron-updater will Authenticode-verify every installer this build downloads and reject all of them.'
)
}
if (config.win?.signtoolOptions?.publisherName != null) {
problems.push(
`win.signtoolOptions.publisherName is set to "${config.win.signtoolOptions.publisherName}" on an unsigned dev build; it must be absent.`
)
}
}
return problems
}
function parseArgs(argv) {
const args = {}
for (const entry of argv) {
const match = /^--([^=]+)=(.*)$/.exec(entry)
if (match) {
args[match[1]] = match[2]
}
}
return args
}
function main() {
const { channel, platform = process.platform } = parseArgs(process.argv.slice(2))
if (!channel) {
console.error(
'Usage: verify-dev-channel-packaging.mjs --channel=<hourly|daily|adhoc> [--platform=win32]'
)
process.exit(1)
}
const require = createRequire(import.meta.url)
const config = require(resolve(import.meta.dirname, '../electron-builder.config.cjs'))
const problems = collectDevChannelPackagingProblems({
channel,
platform,
config,
env: process.env
})
if (problems.length > 0) {
for (const problem of problems) {
console.error(`::error::${problem}`)
}
process.exit(1)
}
console.log(
`Dev-channel packaging verified: ${channel} on ${platform} → stablyai/${CHANNEL_REPOS[channel]} @ ${config.extraMetadata?.version}`
)
}
// Why the guard: the test imports the pure collector without running the CLI.
// pathToFileURL, not a `file://` template: this also runs on Windows, where a
// drive-letter path does not concatenate into a valid URL.
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
main()
}
@@ -0,0 +1,174 @@
import { createRequire } from 'node:module'
import { resolve } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import { collectDevChannelPackagingProblems } from './verify-dev-channel-packaging.mjs'
const require = createRequire(import.meta.url)
const CONFIG_PATH = resolve(import.meta.dirname, '../electron-builder.config.cjs')
/** The config reads process.env at require time, so each identity needs a fresh load. */
function loadConfigWithEnv(env) {
const saved = { ...process.env }
for (const key of Object.keys(process.env)) {
if (key.startsWith('ORCA_')) {
delete process.env[key]
}
}
Object.assign(process.env, env)
try {
delete require.cache[require.resolve(CONFIG_PATH)]
return require(CONFIG_PATH)
} finally {
process.env = saved
delete require.cache[require.resolve(CONFIG_PATH)]
}
}
const WIN_ADHOC_ENV = {
ORCA_WIN_ADHOC: '1',
ORCA_ADHOC_BUILD_VERSION: '1.4.178-adhoc.20260819010203'
}
afterEach(() => {
delete require.cache[require.resolve(CONFIG_PATH)]
})
describe('electron-builder dev-channel identity', () => {
it('keeps the SignPath publisherName on stable Windows builds', () => {
const config = loadConfigWithEnv({})
expect(config.win.signtoolOptions.publisherName).toBe('SignPath Foundation')
expect(config.win.verifyUpdateCodeSignature).toBeUndefined()
expect(config.publish.repo).toBe('orca')
expect(config.publish.releaseType).toBe('release')
})
// The whole point of the change: an unsigned build that advertised a
// publisherName would Authenticode-verify — and reject — every installer it
// ever downloaded, including its own way back to stable.
it('drops the publisherName and disables update signature checks on Windows dev builds', () => {
const config = loadConfigWithEnv(WIN_ADHOC_ENV)
expect(config.win.signtoolOptions?.publisherName).toBeUndefined()
expect(config.win.verifyUpdateCodeSignature).toBe(false)
})
it.each([
['hourly', { ORCA_WIN_HOURLY: '1' }, 'orca-hourly'],
['daily', { ORCA_WIN_DAILY: '1' }, 'orca-daily'],
['adhoc', { ORCA_WIN_ADHOC: '1' }, 'orca-adhoc']
])('publishes %s Windows builds to its own repo as a prerelease', (_channel, env, repo) => {
const config = loadConfigWithEnv(env)
expect(config.publish.repo).toBe(repo)
expect(config.publish.releaseType).toBe('prerelease')
})
// Why: ORCA_MAC_* gates hardened runtime, notarization, and root-level
// forceCodeSigning. If the Windows variables leaked into that, the Windows job
// would fail packaging for want of a cert it deliberately does not use.
it('leaves mac release signing off for Windows dev builds', () => {
const config = loadConfigWithEnv(WIN_ADHOC_ENV)
expect(config.forceCodeSigning).toBe(false)
expect(config.mac.notarize).toBe(false)
expect(config.mac.hardenedRuntime).toBe(false)
})
it('still notarizes mac dev builds', () => {
const config = loadConfigWithEnv({
ORCA_MAC_ADHOC: '1',
ORCA_ADHOC_BUILD_VERSION: '1.4.178-adhoc.20260819010203'
})
expect(config.mac.notarize).toBe(true)
expect(config.publish.repo).toBe('orca-adhoc')
})
})
describe('collectDevChannelPackagingProblems', () => {
const goodWinConfig = {
publish: { repo: 'orca-adhoc', releaseType: 'prerelease' },
extraMetadata: { version: '1.4.178-adhoc.20260819010203' },
win: { verifyUpdateCodeSignature: false }
}
const env = { ORCA_ADHOC_BUILD_VERSION: '1.4.178-adhoc.20260819010203' }
it('accepts a correctly configured Windows dev build', () => {
expect(
collectDevChannelPackagingProblems({
channel: 'adhoc',
platform: 'win32',
config: goodWinConfig,
env
})
).toEqual([])
})
// The failure this script exists for: a branch predating Windows dev builds
// resolves publish.repo to the main repo.
it('rejects a config that resolved the main repo', () => {
const problems = collectDevChannelPackagingProblems({
channel: 'adhoc',
platform: 'win32',
config: { ...goodWinConfig, publish: { repo: 'orca', releaseType: 'release' } },
env
})
expect(problems.join('\n')).toContain('must publish to "orca-adhoc"')
expect(problems.join('\n')).toContain('rebase it onto a main that does')
})
it('rejects a Windows dev build that still advertises a publisherName', () => {
const problems = collectDevChannelPackagingProblems({
channel: 'adhoc',
platform: 'win32',
config: {
...goodWinConfig,
win: { signtoolOptions: { publisherName: 'SignPath Foundation' } }
},
env
})
expect(problems.join('\n')).toContain('verifyUpdateCodeSignature must be false')
expect(problems.join('\n')).toContain('publisherName is set to "SignPath Foundation"')
})
it('rejects a build packaging a version other than the tag the workflow created', () => {
const problems = collectDevChannelPackagingProblems({
channel: 'adhoc',
platform: 'win32',
config: { ...goodWinConfig, extraMetadata: { version: '1.4.178' } },
env
})
expect(problems.join('\n')).toContain('but the workflow computed')
})
// macOS builds are signed, so the Windows-only assertions must not fire there.
it('does not apply Windows signature rules to a mac dev build', () => {
expect(
collectDevChannelPackagingProblems({
channel: 'adhoc',
platform: 'darwin',
config: {
publish: { repo: 'orca-adhoc', releaseType: 'prerelease' },
extraMetadata: { version: '1.4.178-adhoc.20260819010203' },
win: { signtoolOptions: { publisherName: 'SignPath Foundation' } }
},
env
})
).toEqual([])
})
it('rejects an unknown channel', () => {
expect(
collectDevChannelPackagingProblems({
channel: 'nightly',
platform: 'win32',
config: goodWinConfig,
env
})
).toEqual(['Unknown dev channel "nightly"; expected one of hourly, daily, adhoc.'])
})
})