Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
804a789
Secure Scient state initialization
yaacovcorcos Jul 20, 2026
3433cfb
Allow legacy migration state test
yaacovcorcos Jul 20, 2026
5e93c43
Supervise the desktop backend lifecycle
yaacovcorcos Jul 20, 2026
a1d6b63
Supervise desktop connection recovery
yaacovcorcos Jul 20, 2026
a37c476
Define safe RPC recovery policies
yaacovcorcos Jul 20, 2026
b4b2558
Surface connection recovery diagnostics
yaacovcorcos Jul 20, 2026
0cbae5a
Support Linux desktop development launch
yaacovcorcos Jul 21, 2026
f1e0405
Gate packaged Linux desktop lifecycle
yaacovcorcos Jul 21, 2026
4839caa
Harden packaged Linux acceptance
yaacovcorcos Jul 21, 2026
f25d512
Use packaged server readiness contract
yaacovcorcos Jul 21, 2026
11d3f6a
Merge remote-tracking branch 'origin/main' into agent/secure-state-init
yaacovcorcos Jul 21, 2026
df79276
Merge branch 'agent/secure-state-init' into agent/backend-supervisor
yaacovcorcos Jul 21, 2026
4167480
Merge branch 'agent/backend-supervisor' into agent/connection-supervisor
yaacovcorcos Jul 21, 2026
56c88c6
Merge branch 'agent/connection-supervisor' into agent/rpc-recovery-po…
yaacovcorcos Jul 21, 2026
cd6c418
Merge branch 'agent/rpc-recovery-policies' into agent/recovery-ux-dia…
yaacovcorcos Jul 21, 2026
cd89cd3
Merge branch 'agent/recovery-ux-diagnostics' into agent/linux-launch-…
yaacovcorcos Jul 21, 2026
ffc98e2
Merge branch 'agent/linux-launch-hardening' into agent/linux-packaged…
yaacovcorcos Jul 21, 2026
958e088
Isolate provider dialog browser fixtures
yaacovcorcos Jul 21, 2026
65534c8
Merge branch 'agent/connection-supervisor' into agent/rpc-recovery-po…
yaacovcorcos Jul 21, 2026
050d5e6
Merge branch 'agent/rpc-recovery-policies' into agent/recovery-ux-dia…
yaacovcorcos Jul 21, 2026
a966cbf
Merge branch 'agent/recovery-ux-diagnostics' into agent/linux-launch-…
yaacovcorcos Jul 21, 2026
6cd35a3
Merge branch 'agent/linux-launch-hardening' into agent/linux-packaged…
yaacovcorcos Jul 21, 2026
3adb201
Verify graceful packaged process teardown
yaacovcorcos Jul 21, 2026
5a85631
Align packaged lifecycle with app controls
yaacovcorcos Jul 21, 2026
61f56f3
test(linux): shut down packaged app gracefully
yaacovcorcos Jul 21, 2026
301b645
test(linux): validate cleanup process identities
yaacovcorcos Jul 21, 2026
aabb53f
fix(desktop): fail closed on unsafe Linux sandbox
yaacovcorcos Jul 21, 2026
d6966c0
test(linux): bound packaged smoke requests
yaacovcorcos Jul 21, 2026
9fe970e
ci(release): smoke exact desktop artifacts
yaacovcorcos Jul 21, 2026
967b5d3
test(linux): forbid packaged sandbox bypass
yaacovcorcos Jul 21, 2026
57439dd
style(desktop): format Linux launcher hardening
yaacovcorcos Jul 21, 2026
6769e44
Merge remote-tracking branch 'origin/agent/linux-launch-hardening' in…
yaacovcorcos Jul 21, 2026
a3a7bd9
style(release): format packaged acceptance
yaacovcorcos Jul 21, 2026
d0a1440
fix(release): sanitize packaged smoke environment
yaacovcorcos Jul 21, 2026
09e1147
fix(release): preserve AppImage sandbox
yaacovcorcos Jul 21, 2026
7ac2277
Merge commit '09e114726458b36507fe09edf57f9ddfab350b24' into agent/li…
yaacovcorcos Jul 21, 2026
ba7b827
revert(release): keep AppImage migration out of launcher hardening
yaacovcorcos Jul 21, 2026
e9f0f09
Merge commit 'ba7b8274fbd5752a2d2073f54f0431e989cbd4c6' into agent/li…
yaacovcorcos Jul 21, 2026
2bcce8b
test(release): fail closed on packaged startup proof
yaacovcorcos Jul 21, 2026
ee3a38c
fix(desktop): honor Linux user namespace sandbox
yaacovcorcos Jul 21, 2026
53ef0e2
Merge commit 'ee3a38c6b316c90174c9920461e4562e9d6332f4' into agent/li…
yaacovcorcos Jul 21, 2026
45c7526
release(linux): ship sandboxed Debian package
yaacovcorcos Jul 24, 2026
f5e0afc
Merge current main and secure Debian lifecycle gate
yaacovcorcos Jul 24, 2026
8607d31
fix(release): close Debian acceptance gaps
yaacovcorcos Jul 24, 2026
7833a4b
fix(updater): prefer installed Linux package identity
yaacovcorcos Jul 24, 2026
f965e7a
fix(ci): follow packaged add-project source flow
yaacovcorcos Jul 24, 2026
13ef781
fix(ci): follow packaged path browser labels
yaacovcorcos Jul 24, 2026
852130a
fix(ci): use current packaged folder action
yaacovcorcos Jul 24, 2026
e6191ea
fix(ci): inspect persisted state after shutdown
yaacovcorcos Jul 24, 2026
3fccd49
fix(ci): settle project creation before recovery
yaacovcorcos Jul 24, 2026
514307e
Merge remote-tracking branch 'refs/remotes/origin/main' into agent/li…
yaacovcorcos Jul 24, 2026
3b13a22
Merge remote-tracking branch 'refs/remotes/origin/main' into agent/li…
yaacovcorcos Jul 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .docs/scripts.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
- `bun run dist:desktop:artifact -- --platform <mac|linux|win> --target <target> --arch <arch>` — Builds a desktop artifact for a specific platform/target/arch.
- `bun run dist:desktop:dmg` — Builds a shareable macOS `.dmg` into `./release`.
- `bun run dist:desktop:dmg:x64` — Builds an Intel macOS `.dmg`.
- `bun run dist:desktop:linux` — Builds a Linux AppImage into `./release`.
- `bun run dist:desktop:linux` — Builds the supported Linux Debian package into `./release`.
- `bun run dist:desktop:linux:appimage` — Builds the fail-closed AppImage compatibility artifact for packaging diagnostics; it is not the supported Ubuntu download.
- `bun run dist:desktop:win` — Builds a Windows NSIS installer into `./release`.

## Desktop `.dmg` packaging notes
Expand Down
83 changes: 83 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,89 @@ jobs:
- name: Exercise Windows release staging
run: bun run release:smoke

linux_packaged:
name: Packaged Linux Acceptance
needs: quality
runs-on: ubuntu-24.04
timeout-minutes: 40
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version-file: package.json

- name: Setup Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: package.json

- name: Cache Bun and Turbo
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.bun/install/cache
.turbo
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}-${{ hashFiles('turbo.json') }}
restore-keys: |
${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}-

- name: Install dependencies
run: bun install --frozen-lockfile

- name: Install packaged desktop runtime dependencies
env:
DEBIAN_FRONTEND: noninteractive
run: |
./apps/web/node_modules/.bin/playwright install-deps chromium
command -v apparmor_status >/dev/null 2>&1 || \
sudo apt-get install --no-install-recommends --yes apparmor-utils

- name: Build Linux Debian package
run: bun run dist:desktop:linux

- name: Exercise installed Debian lifecycle
run: bun run test:linux-deb

- name: Build fail-closed AppImage compatibility artifact
run: |
node scripts/build-desktop-artifact.ts \
--platform linux \
--target AppImage \
--arch x64 \
--output-dir release-appimage \
--skip-build

- name: Verify AppImage never injects a sandbox bypass
shell: bash
run: |
set -euo pipefail
appimage_path="$(find release-appimage -maxdepth 1 -type f -name '*.AppImage' -print -quit)"
test -n "$appimage_path"
chmod +x "$appimage_path"
extraction_directory="$(mktemp -d)"
trap 'rm -rf "$extraction_directory"' EXIT
(
cd "$extraction_directory"
"$GITHUB_WORKSPACE/$appimage_path" --appimage-extract >/dev/null
)
launcher="$extraction_directory/squashfs-root/AppRun"
test -f "$launcher"
if grep -F -- '--no-sandbox' "$launcher"; then
echo "Fail-closed AppImage launcher injects --no-sandbox." >&2
exit 1
fi

- name: Upload packaged Linux diagnostics
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: linux-packaged-diagnostics
path: test-results/linux-deb
if-no-files-found: ignore

release_smoke:
name: Release Smoke
runs-on: ubuntu-24.04
Expand Down
101 changes: 58 additions & 43 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,17 @@ jobs:
- name: Verify release-note catalog structure
run: node scripts/verify-release-notes.ts "${{ steps.release_meta.outputs.version }}"

- name: Verify current Linux packaging lane
env:
RELEASE_LANE: ${{ steps.release_meta.outputs.release_lane }}
run: |
set -euo pipefail
if [[ "$RELEASE_LANE" != "clean" ]]; then
echo "The current release workflow supports only the clean Debian-package lane." >&2
echo "Historical compatibility releases must use their original tagged workflow and artifacts." >&2
exit 1
fi

- name: Verify public updater repository
env:
GH_TOKEN: ${{ github.token }}
Expand Down Expand Up @@ -176,10 +187,9 @@ jobs:
required_assets=(
latest-mac.yml
latest.yml
latest-linux.yml
"${UPDATE_CHANNEL}-deb-linux.yml"
"${UPDATE_CHANNEL}-mac.yml"
"${UPDATE_CHANNEL}.yml"
"${UPDATE_CHANNEL}-linux.yml"
)
for required_asset in "${required_assets[@]}"; do
if ! grep -Fxq "$required_asset" <<< "$bridge_assets"; then
Expand Down Expand Up @@ -224,7 +234,7 @@ jobs:
- label: Linux x64
runner: ubuntu-24.04
platform: linux
target: AppImage
target: deb
arch: x64
timeout_minutes: 30
- label: Windows x64
Expand Down Expand Up @@ -325,36 +335,6 @@ jobs:
"${{ matrix.arch }}" \
"${{ needs.preflight.outputs.version }}"

- name: Verify Linux AppImage sandbox policy
if: matrix.platform == 'linux'
shell: bash
run: |
set -euo pipefail

appimage_path="$(find release -maxdepth 1 -type f -name '*.AppImage' -print -quit)"
if [[ -z "$appimage_path" ]]; then
echo "Linux release did not produce an AppImage." >&2
exit 1
fi

chmod +x "$appimage_path"
extraction_directory="$(mktemp -d)"
trap 'rm -rf "$extraction_directory"' EXIT
(
cd "$extraction_directory"
"$GITHUB_WORKSPACE/$appimage_path" --appimage-extract >/dev/null
)

launcher="$extraction_directory/squashfs-root/AppRun"
if [[ ! -f "$launcher" ]]; then
echo "Extracted AppImage is missing AppRun." >&2
exit 1
fi
if grep -F -- '--no-sandbox' "$launcher"; then
echo "Packaged Linux launcher disables Electron's sandbox." >&2
exit 1
fi

- name: Collect release assets
shell: bash
run: |
Expand All @@ -365,7 +345,7 @@ jobs:
for pattern in \
"release/*.dmg" \
"release/*.zip" \
"release/*.AppImage" \
"release/*.deb" \
"release/*.exe" \
"release/*.blockmap" \
"release/latest*.yml"; do
Expand All @@ -380,6 +360,42 @@ jobs:
fi
fi

- name: Install Linux packaged smoke dependencies
if: ${{ matrix.platform == 'linux' }}
shell: bash
env:
DEBIAN_FRONTEND: noninteractive
run: |
set -euo pipefail
./apps/web/node_modules/.bin/playwright install-deps chromium
command -v apparmor_status >/dev/null 2>&1 || \
sudo apt-get install --no-install-recommends --yes apparmor-utils

- name: Smoke exact packaged desktop startup
if: ${{ matrix.platform != 'linux' }}
shell: bash
run: |
node scripts/verify-packaged-desktop-startup.ts \
--assets-dir release-publish \
--platform "${{ matrix.platform }}" \
--arch "${{ matrix.arch }}" \
--version "${{ needs.preflight.outputs.version }}"

- name: Exercise exact packaged Linux lifecycle
if: ${{ matrix.platform == 'linux' }}
env:
SCIENT_LINUX_ARTIFACT_DIR: release-publish
SCIENT_LINUX_SMOKE_ARTIFACT_DIR: test-results/linux-release-deb
run: node apps/web/scripts/linux-deb-smoke.mjs

- name: Upload packaged Linux smoke diagnostics
if: ${{ failure() && matrix.platform == 'linux' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: release-linux-packaged-diagnostics-${{ matrix.arch }}
path: test-results/linux-release-deb
if-no-files-found: ignore

- name: Upload build artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
Expand Down Expand Up @@ -466,7 +482,7 @@ jobs:

mapfile -d '' payloads < <(
find . -maxdepth 1 -type f \
\( -name '*.dmg' -o -name '*.zip' -o -name '*.AppImage' -o -name '*.exe' \) \
\( -name '*.dmg' -o -name '*.zip' -o -name '*.deb' -o -name '*.exe' \) \
-print0 | sort -z
)
if [[ "${#payloads[@]}" -eq 0 ]]; then
Expand All @@ -492,13 +508,12 @@ jobs:
"Scient-${RELEASE_VERSION}-arm64.zip"
"Scient-${RELEASE_VERSION}-x64.zip"
"Scient-${RELEASE_VERSION}-x64.exe"
"Scient-${RELEASE_VERSION}-x86_64.AppImage"
"Scient-${RELEASE_VERSION}-amd64.deb"
"latest-mac.yml"
"latest.yml"
"latest-linux.yml"
"${UPDATE_CHANNEL}-mac.yml"
"${UPDATE_CHANNEL}.yml"
"${UPDATE_CHANNEL}-linux.yml"
"${UPDATE_CHANNEL}-deb-linux.yml"
"SHA256SUMS.txt"
)

Expand All @@ -515,7 +530,7 @@ jobs:
"Scient-${RELEASE_VERSION}-arm64.zip"
"Scient-${RELEASE_VERSION}-x64.zip"
"Scient-${RELEASE_VERSION}-x64.exe"
"Scient-${RELEASE_VERSION}-x86_64.AppImage"
"Scient-${RELEASE_VERSION}-amd64.deb"
)
for payload in "${required_payloads[@]}"; do
if ! grep -Eq "^[0-9a-f]{64} ${payload}$" SHA256SUMS.txt; then
Expand Down Expand Up @@ -553,7 +568,7 @@ jobs:
channel_manifests=(
"${UPDATE_CHANNEL}-mac.yml"
"${UPDATE_CHANNEL}.yml"
"${UPDATE_CHANNEL}-linux.yml"
"${UPDATE_CHANNEL}-deb-linux.yml"
)
existing_manifests=()
for manifest in "${channel_manifests[@]}"; do
Expand Down Expand Up @@ -627,7 +642,7 @@ jobs:
files: |
release-assets/*.dmg
release-assets/*.zip
release-assets/*.AppImage
release-assets/*.deb
release-assets/*.exe
release-assets/*.blockmap
release-assets/*.yml
Expand All @@ -654,14 +669,14 @@ jobs:
payloads=(
release-assets/*.dmg
release-assets/*.zip
release-assets/*.AppImage
release-assets/*.deb
release-assets/*.exe
release-assets/*.blockmap
)
manifests=(
"release-assets/${UPDATE_CHANNEL}-mac.yml"
"release-assets/${UPDATE_CHANNEL}.yml"
"release-assets/${UPDATE_CHANNEL}-linux.yml"
"release-assets/${UPDATE_CHANNEL}-deb-linux.yml"
)

if [[ "${#payloads[@]}" -eq 0 ]]; then
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ release/
.idea/
apps/web/.playwright
apps/web/playwright-report
/test-results/linux-deb/
/test-results/linux-release-deb/
.playwright-cli/
output/playwright/
apps/web/src/components/__screenshots__
Expand Down
30 changes: 29 additions & 1 deletion apps/desktop/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import {
SCIENT_DESKTOP_UPDATE_CHANNEL,
SCIENT_DESKTOP_UPDATES_ENABLED,
scientBundleId,
scientDesktopUpdateChannel,
} from "@synara/shared/desktopIdentity";
import { NetService } from "@synara/shared/Net";
import { RotatingFileSink } from "@synara/shared/logging";
Expand Down Expand Up @@ -105,6 +106,7 @@ import {
hasDownloadProgressAdvanced,
isExpectedStalledDownloadCancellationError,
isUpdateVersionNewer,
resolveLinuxPackageType,
shouldBroadcastDownloadProgress,
shouldCheckForUpdatesOnForeground,
} from "./updateState";
Expand Down Expand Up @@ -930,6 +932,21 @@ function parseAppUpdateYml(): Record<string, string> | null {
}
}

function readLinuxPackageType(): string | null {
if (process.platform !== "linux" || !app.isPackaged) return null;
let resourcePackageType: string | null = null;
try {
resourcePackageType = FS.readFileSync(
Path.join(process.resourcesPath, "package-type"),
"utf8",
).trim();
} catch {}
return resolveLinuxPackageType({
resourcePackageType,
appImage: process.env.APPIMAGE,
});
}

function normalizeCommitHash(value: unknown): string | null {
if (typeof value !== "string") {
return null;
Expand Down Expand Up @@ -1371,6 +1388,7 @@ function resolveAutoUpdateDisabledReason(): string | null {
isPackaged: app.isPackaged,
platform: process.platform,
appImage: process.env.APPIMAGE,
linuxPackageType: readLinuxPackageType() ?? undefined,
disabledByEnv: process.env.SYNARA_DISABLE_AUTO_UPDATE === "1",
hasUpdateFeedConfig: hasConfiguredUpdateFeed(),
});
Expand Down Expand Up @@ -2603,7 +2621,7 @@ function configureAutoUpdater(): void {
// Stable production builds use the dedicated Scient-owned update channel.
// resolveAutoUpdateDisabledReason still keeps development, unpackaged, and
// unsupported runtime environments away from the public feed.
autoUpdater.channel = SCIENT_DESKTOP_UPDATE_CHANNEL;
autoUpdater.channel = scientDesktopUpdateChannel(process.platform, readLinuxPackageType());
autoUpdater.allowPrerelease = DESKTOP_UPDATE_ALLOW_PRERELEASE;
autoUpdater.allowDowngrade = false;
// Match electron-updater's native GitHub provider path; the packaged
Expand Down Expand Up @@ -3502,9 +3520,19 @@ function createWindow(): BrowserWindow {
window.setTitle(APP_DISPLAY_NAME);
});
window.webContents.on("did-finish-load", () => {
writeDesktopLogHeader("renderer main frame loaded");
window.setTitle(APP_DISPLAY_NAME);
emitUpdateState();
});
window.webContents.on(
"did-fail-load",
(_event, errorCode, errorDescription, validatedUrl, isMainFrame) => {
if (!isMainFrame) return;
writeDesktopLogHeader(
`renderer main frame load failed code=${errorCode} url=${validatedUrl} message=${errorDescription}`,
);
},
);
window.once("ready-to-show", () => {
// Preserve the original first-launch behavior, then respect the state saved
// by subsequent closes. Normal bounds are restored before maximizing so the
Expand Down
Loading
Loading