diff --git a/config/build-plugins/cloud-build-defaults.ts b/config/build-plugins/cloud-build-defaults.ts index c3629a53a81..d3c0769ee84 100644 --- a/config/build-plugins/cloud-build-defaults.ts +++ b/config/build-plugins/cloud-build-defaults.ts @@ -3,11 +3,11 @@ import { resolve } from 'node:path' /** Integration-branch backends baked into main (packaged builds read no shell env); build-time env overrides the file. */ export function cloudBuildDefaultsDefine(env: NodeJS.ProcessEnv = process.env): string { - const file: { billingApiUrl?: string; cloudSandboxUrl?: string } = JSON.parse( - readFileSync(resolve('config/cloud-build-defaults.json'), 'utf8') - ) + const file: { billingApiUrl?: string; cloudSandboxUrl?: string; allowDevSigning?: boolean } = + JSON.parse(readFileSync(resolve('config/cloud-build-defaults.json'), 'utf8')) return JSON.stringify({ billingApiUrl: env.ORCA_BUILD_BILLING_API_URL || file.billingApiUrl, - cloudSandboxUrl: env.ORCA_BUILD_CLOUD_SANDBOX_URL || file.cloudSandboxUrl + cloudSandboxUrl: env.ORCA_BUILD_CLOUD_SANDBOX_URL || file.cloudSandboxUrl, + allowDevSigning: file.allowDevSigning === true }) } diff --git a/config/cloud-build-defaults.json b/config/cloud-build-defaults.json index b8046b75191..fcfbdefb2f2 100644 --- a/config/cloud-build-defaults.json +++ b/config/cloud-build-defaults.json @@ -1,5 +1,6 @@ { - "_comment": "Integration-branch only (stablyai/orca-private cloud/integration). Public dev endpoints baked into packaged builds; no secrets. ORCA_BUILD_BILLING_API_URL / ORCA_BUILD_CLOUD_SANDBOX_URL at build time, or ORCA_BILLING_API_URL / ORCA_CLOUD_SANDBOX_URL at run time, override them.", + "_comment": "Integration-branch only (stablyai/orca-private cloud/integration). Public dev endpoints baked into packaged builds; no secrets. ORCA_BUILD_BILLING_API_URL / ORCA_BUILD_CLOUD_SANDBOX_URL at build time, or ORCA_BILLING_API_URL / ORCA_CLOUD_SANDBOX_URL at run time, override them. allowDevSigning lets this packaged build honor ORCA_CLOUD_SANDBOX_DEV_KEY_FILE (dev operator key) until login.onorca.dev ships the sandbox-token endpoint; the key itself is never in git.", "billingApiUrl": "https://orca-billing-dev.lovecast.workers.dev", - "cloudSandboxUrl": "https://orca-sandbox-control-dev.lovecast.workers.dev" + "cloudSandboxUrl": "https://orca-sandbox-control-dev.lovecast.workers.dev", + "allowDevSigning": true } diff --git a/config/scripts/cloud-integration/sync-cloud-integration.sh b/config/scripts/cloud-integration/sync-cloud-integration.sh index 056c8de6e1d..b7185d92044 100755 --- a/config/scripts/cloud-integration/sync-cloud-integration.sh +++ b/config/scripts/cloud-integration/sync-cloud-integration.sh @@ -1,20 +1,20 @@ #!/usr/bin/env bash -# Rebuild cloud/integration (pushed only to nwparker/orca-private) from scratch: -# stablyai/orca main -# + merge nwparker/orca-private nwparker/accounts-billing, nwparker/vercel-driver -# + merge open upstream PR branches still under review (stablyai/orca nwparker/agent-host-sign-in #40228, -# nwparker/agent-api-keys #40303); drop each from STACKED once it lands on main -# + cherry-pick every "chore(cloud-integration)" commit already on cloud/integration (build defaults, this script). +# Move cloud/integration (on `private` = nwparker/orca-private) forward. Other workstreams branch from it and merge +# into it, so it is NEVER rebuilt or force-pushed: this script only merges new commits from its sources. +# stablyai/orca main (origin) +# + nwparker/orca-private nwparker/accounts-billing, nwparker/vercel-driver (until they are retired) +# + open upstream PR branches stacked in while under review: nwparker/agent-host-sign-in (#40228), +# nwparker/agent-api-keys (#40303). Once a PR lands, main brings it; drop it from STACKED. # Never based on stablyai/orca-private (Bots). git rerere replays conflict resolutions made before in this clone. # Conflict policy: billing files (cloud/apps/billing, cloud/docs/accounts-billing-design.md, src/main/billing, -# src/renderer/src/components/billing, src/shared/orca-billing.ts, billing keys in en.json) keep accounts-billing -# ("ours"); vercel-driver carries an older billing snapshot. Elsewhere keep both sides. +# src/renderer/src/components/billing, src/shared/orca-billing.ts, billing keys in en.json) keep accounts-billing; +# vercel-driver carries an older billing snapshot. Elsewhere keep both sides. # -# Usage (from a clone of stablyai/orca with remote `private` = nwparker/orca-private, never an Orca worktree): -# config/scripts/cloud-integration/sync-cloud-integration.sh # builds local branch cloud/integration-next -# on conflict: resolve, `git add -A && git commit --no-edit` (or `git cherry-pick --continue`), rerun with --resume -# pnpm install --frozen-lockfile && pnpm typecheck && pnpm lint -# git push --force-with-lease=cloud/integration private cloud/integration-next:cloud/integration +# Usage (clone of stablyai/orca as origin, remote `private`; never an Orca worktree): +# config/scripts/cloud-integration/sync-cloud-integration.sh +# on conflict: resolve, `git add -A && git commit --no-edit`, rerun (already-merged sources are skipped) +# then: pnpm install --frozen-lockfile && pnpm typecheck && pnpm lint && targeted vitest +# git push private cloud/integration # plain fast-forward push; if rejected, rerun (someone merged) set -euo pipefail PRIVATE_BRANCHES=(${PRIVATE_BRANCHES:-nwparker/accounts-billing nwparker/vercel-driver}) @@ -26,22 +26,18 @@ git config rerere.autoupdate true git fetch -q origin main for b in "${STACKED[@]}"; do git fetch -q origin "+$b:refs/remotes/origin/$b"; done for b in "${PRIVATE_BRANCHES[@]}"; do git fetch -q private "+$b:refs/remotes/private/$b"; done -git fetch -q private +cloud/integration:refs/remotes/private/cloud/integration 2>/dev/null || true +git fetch -q private +cloud/integration:refs/remotes/private/cloud/integration -[[ "${1:-}" == --resume ]] || git checkout -q -B cloud/integration-next origin/main +if [[ -n "$(git status --porcelain --untracked-files=no)" ]]; then + echo "Working tree has changes; commit or set them aside first." >&2; exit 1 +fi +git checkout -q -B cloud/integration private/cloud/integration -for ref in "${PRIVATE_BRANCHES[@]/#/private/}" "${STACKED[@]/#/origin/}"; do +for ref in origin/main "${PRIVATE_BRANCHES[@]/#/private/}" "${STACKED[@]/#/origin/}"; do git merge-base --is-ancestor "$ref" HEAD && continue - git merge --no-ff --no-edit "$ref" || { - echo "Conflict merging $ref. Resolve (policy above), git add -A && git commit --no-edit, rerun with --resume." >&2 + git merge --no-ff --no-edit -m "Merge $ref into cloud/integration" "$ref" || { + echo "Conflict merging $ref. Resolve (policy above), git add -A && git commit --no-edit, rerun." >&2 exit 1 } done - -if git rev-parse -q --verify private/cloud/integration >/dev/null; then - for c in $(git log --reverse --no-merges --format=%H --grep='^chore(cloud-integration)' origin/main..private/cloud/integration); do - git log --format=%s origin/main..HEAD | grep -qxF "$(git log -1 --format=%s "$c")" && continue - git cherry-pick "$c" || { echo "Conflict re-applying $c; resolve, git cherry-pick --continue, rerun with --resume." >&2; exit 1; } - done -fi -echo "cloud/integration-next ready at $(git rev-parse --short HEAD) on stablyai/orca main $(git rev-parse --short origin/main)." +echo "cloud/integration at $(git rev-parse --short HEAD), $(git rev-list --count private/cloud/integration..HEAD) new commit(s) over private. Verify, then: git push private cloud/integration" diff --git a/src/main/cloud-build-defaults.test.ts b/src/main/cloud-build-defaults.test.ts index 87b734c4074..a0d92feccaa 100644 --- a/src/main/cloud-build-defaults.test.ts +++ b/src/main/cloud-build-defaults.test.ts @@ -1,5 +1,5 @@ import { afterEach, describe, expect, it, vi } from 'vitest' -import { withCloudBuildDefaults } from './cloud-build-defaults' +import { cloudDevSigningAllowed, withCloudBuildDefaults } from './cloud-build-defaults' describe('withCloudBuildDefaults', () => { afterEach(() => vi.unstubAllGlobals()) @@ -23,4 +23,11 @@ describe('withCloudBuildDefaults', () => { ORCA_CLOUD_SANDBOX_URL: 'https://override.example' }) }) + + it('allows dev signing in packaged builds only when the build opts in', () => { + expect(cloudDevSigningAllowed(false)).toBe(true) + expect(cloudDevSigningAllowed(true)).toBe(false) + vi.stubGlobal('ORCA_CLOUD_BUILD_DEFAULTS', { allowDevSigning: true }) + expect(cloudDevSigningAllowed(true)).toBe(true) + }) }) diff --git a/src/main/cloud-build-defaults.ts b/src/main/cloud-build-defaults.ts index 395695a8d45..568fddb4b53 100644 --- a/src/main/cloud-build-defaults.ts +++ b/src/main/cloud-build-defaults.ts @@ -1,8 +1,16 @@ // Packaged builds read no shell env, so the integration build bakes in its backend URLs // (config/cloud-build-defaults.json via electron.vite.config.ts). Run-time env still wins. -declare const ORCA_CLOUD_BUILD_DEFAULTS: { billingApiUrl?: string; cloudSandboxUrl?: string } | null +declare const ORCA_CLOUD_BUILD_DEFAULTS: { + billingApiUrl?: string + cloudSandboxUrl?: string + allowDevSigning?: boolean +} | null -function buildDefaults(): { billingApiUrl?: string; cloudSandboxUrl?: string } { +function buildDefaults(): { + billingApiUrl?: string + cloudSandboxUrl?: string + allowDevSigning?: boolean +} { // oxlint-disable-next-line unicorn/no-typeof-undefined -- undeclared outside electron-vite builds; a bare read throws. return (typeof ORCA_CLOUD_BUILD_DEFAULTS === 'undefined' ? null : ORCA_CLOUD_BUILD_DEFAULTS) ?? {} } @@ -15,3 +23,8 @@ export function withCloudBuildDefaults(env: NodeJS.ProcessEnv): NodeJS.ProcessEn ORCA_CLOUD_SANDBOX_URL: env.ORCA_CLOUD_SANDBOX_URL?.trim() || defaults.cloudSandboxUrl } } + +/** Dev-key sandbox signing: always in unpackaged runs; in packaged builds only when the integration build opts in. */ +export function cloudDevSigningAllowed(packaged: boolean): boolean { + return !packaged || buildDefaults().allowDevSigning === true +} diff --git a/src/main/hosted-sandbox/hosted-sandbox-config.ts b/src/main/hosted-sandbox/hosted-sandbox-config.ts index 3bd965dc481..378224f30a6 100644 --- a/src/main/hosted-sandbox/hosted-sandbox-config.ts +++ b/src/main/hosted-sandbox/hosted-sandbox-config.ts @@ -3,6 +3,7 @@ import { createPrivateKey, sign } from 'node:crypto' import { readFile } from 'node:fs/promises' import { z } from 'zod' +import { cloudDevSigningAllowed } from '../cloud-build-defaults' import { cleanCloudServiceUrl } from '../../shared/cloud-service-url' import { getOrcaCloudAuthConfig } from '../orca-profiles/profile-cloud-auth-config' import { OrcaCloudRequestError } from '../orca-profiles/profile-cloud-client' @@ -42,7 +43,9 @@ export function getHostedSandboxConfig( if (!controlPlaneUrl) { return null } - const devKeyFile = packaged ? undefined : env.ORCA_CLOUD_SANDBOX_DEV_KEY_FILE?.trim() + const devKeyFile = cloudDevSigningAllowed(packaged) + ? env.ORCA_CLOUD_SANDBOX_DEV_KEY_FILE?.trim() + : undefined let cached: { token: string; expiresAt: number } | null = null return { controlPlaneUrl, diff --git a/src/main/hosted-sandbox/hosted-sandbox-install.ts b/src/main/hosted-sandbox/hosted-sandbox-install.ts index 844b4dfdc81..bee7f8c2c36 100644 --- a/src/main/hosted-sandbox/hosted-sandbox-install.ts +++ b/src/main/hosted-sandbox/hosted-sandbox-install.ts @@ -1,4 +1,4 @@ -import { withCloudBuildDefaults } from '../cloud-build-defaults' +import { cloudDevSigningAllowed, withCloudBuildDefaults } from '../cloud-build-defaults' // Wires Orca Cloud into the desktop when this build has a control plane configured (off by default). import { app, BrowserWindow } from 'electron' import { ensureActiveOrcaProfile } from '../orca-profiles/profile-index-store' @@ -72,7 +72,7 @@ export function installHostedSandbox(): void { /** Orca Cloud auth's `sandbox` capability rolls it out per account; the dev key bypasses it in dev builds. */ async function orcaCloudOffered(): Promise { - if (!app.isPackaged && process.env.ORCA_CLOUD_SANDBOX_DEV_KEY_FILE) { + if (cloudDevSigningAllowed(app.isPackaged) && process.env.ORCA_CLOUD_SANDBOX_DEV_KEY_FILE) { return true } const auth = getOrcaCloudAuthConfig(process.env, app.isPackaged)