Skip to content

ShadowCode 2.1: ARM64, APT repo, installer rollback, Flatpak allow-list, window size, model history - #5

Merged
ShadowfetchStudio merged 34 commits into
mainfrom
claude/adoring-allen-j9xho7
Oct 11, 2026
Merged

ShadowfetchStudio merged 34 commits into
mainfrom
claude/adoring-allen-j9xho7

Conversation

@ShadowfetchStudio

@ShadowfetchStudio ShadowfetchStudio commented Oct 11, 2026 •

Copy link
Copy Markdown
Owner

The next version's upgrades, plus a macOS port estimate. Each feature has a CHANGELOG.md entry under 2.1.0 (unreleased).

CI: every check passes on both x86_64 and aarch64, including the full verify jobs: build, Rust suite, native window and endurance, managed llama.cpp runtime, both packages, offline clean-host install, installer and packaged-app checks.

Features

  • ARM64 (aarch64) builds, signed and published.
    • Builds: AppImage, .deb (arm64), Flatpak, the AppImage runtime and llama.cpp are built natively on aarch64. x86_64 names and contents are unchanged. Native CI runs on ubuntu-24.04 and ubuntu-24.04-arm.
    • Release workflow: build-aarch64 hands off unsigned like build. sign signs each target separately, and publish stages both into one release (14 assets).
    • Signature file names: aarch64 uses RELEASE-AUTH-aarch64, RELEASE-AUTH-aarch64.sig, RELEASE-MANIFEST-aarch64.json and SHA256SUMS-aarch64. x86_64 keeps the historical names, so existing installers are unaffected.
    • Verification: the requested package selects which names the Node and shell verifiers read, and the envelope found there must name that target. Verified stages and installer receipts keep the historical names.
    • Release gates: gate scripts are built per architecture (gatesFor), so the x86_64 signer checks aarch64 receipts against the aarch64 scripts. The x86_64 scripts and their digests are byte-identical.
    • Trust: release/trust/policy trusts both targets, and the installer only accepts the AppImage for the machine's architecture.
    • App: on ARM64, the in-app update notice names the aarch64 files.
  • Packaging checks on pull requests (ci.yml). A Flatpak build (GNOME 50 SDK, CPU-only llama.cpp) and the packaging test suites run on both architectures for PRs that touch packaging, scripts, the app or workflows.
  • Signed APT repository. scripts/build-apt-repo.mjs and an apt-repository release job:
    • check the amd64 and arm64 .deb against their publisher signatures;
    • index them and sign InRelease/Release.gpg with a dedicated key;
    • publish to gh-pages.
  • Installer first-window check and database rollback.
    • Before updating, the installer copies the database with VACUUM INTO.
    • It then starts the new version and waits for it to report its first window.
    • If it hangs, crashes or reports the wrong version, the AppImage, runtime, launchers and database are all restored.
    • The highest-accepted-release record is not lowered.
  • Hostname allow-list mode in the Flatpak.
    • Host bubblewrap gets --unshare-net.
    • A small static relay forwards loopback traffic to the app's filtering proxy over a Unix socket.
    • If that can't be set up, the command is refused rather than run unrestricted.
  • Window size memory on Wayland.
    • The content size is saved in logical pixels and versioned; decorations are measured and subtracted.
    • The saved size is fitted to the screen as before.
  • Model history in the picker (schema 27→28).
    • Each finished task records time, tokens, the vendor-reported cost (never estimated) and observed verification, per model and project.
    • An opt-in suggestion offers the cheapest model that passed in this project; it never switches models by itself.
  • macOS port estimate (docs/MACOS_PORT_ESTIMATE.md): 17–36 engineer-weeks, with a phased plan.

CI fixes included

  • main has been failing verify because Alpine replaced pinned packages. This PR pins python3 to 3.12.15-r0 and zlib to 1.3.2-r1.
  • aarch64:
    • The GLib variant test links through LTO, since GNU ld can't link bitcode-only rlibs.
    • llama.cpp is built with GCC 14 when the default compiler can't target the Armv9.2 SME CPU variant (Ubuntu 24.04 ships GCC 13).
    • xdg-utils is installed for the AppImage bundler.
    • The clean-host Dockerfile no longer defaults TARGETARCH, which had selected the amd64 image.
  • The installer tests pin the host to x86_64 via a uname shim.
  • A worktree cleanup test that depended on directory order now sets up its intended starting state explicitly, with all assertions kept.
  • The Flatpak smoke run uses dbus-run-session.
  • routing.suggest_from_history now has its default (false) and is documented in config.example.yaml.

Verified locally (in addition to CI)

  • Node release suites, run on x86_64 and again with Node simulating an arm64 host. New tests cover signing and publishing both targets into one release, and envelopes filed under the wrong target's names.
  • Installer auth and shell suites, run as a non-root user on x86_64 and on a fully simulated aarch64 host, including an aarch64 handoff signed on x86_64 and then installed.
  • The full Rust core suite, src-tauri tests, cargo +1.95.0 clippy -D warnings and fmt --check.
  • UI: typecheck and 724 vitest tests.
  • actionlint on all workflows.

Before tagging 2.1.0

  • APT key: NATIVE_APT_SIGNING_KEY (secret) and NATIVE_APT_SIGNING_FINGERPRINT (variable, 1BC07B5ECFDB514673A296FB021CAB3F2F7A1F61) are set in the release-signing environment, and release/trust/APT-SIGNING-KEY records the public key (227ff15). A local dry run built and gpgv-verified a repository from the signed 1.0.1 .deb with it.
  • Pages: enable it from gh-pages after the first release run creates that branch.
  • Key version cap: key 1 in release/trust/policy now covers 0.33.0–2.1.0 (6d88efb).
  • Tooling pin: set NATIVE_RELEASE_TOOLING_COMMIT to the tagged 2.1.0 release commit (it must contain this multi-target signer). An older pin refuses the aarch64 signing call, so the release fails rather than shipping x86_64 alone.
  • Signing tag rule: the release-signing environment must allow v2.1.0.
  • Not run end to end yet: the release workflow only runs on a tag.

Also fixed: scripts/test-install-auth.mjs clears DISPLAY/WAYLAND_DISPLAY, so the installer's first-window check no longer fails 25 tests on a desktop machine (82dd25a).

🤖 Generated with Claude Code

https://claude.ai/code/session_01Fn7NbipAkYG7cYZxHk8xRu

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fn7NbipAkYG7cYZxHk8xRu
@coderabbitai

coderabbitai Bot commented Oct 11, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

scripts/build-apt-repo.mjs builds a pool + Packages/Release index for
amd64 and arm64 from already-authenticated .debs, signs InRelease and
Release.gpg with an OpenPGP key from APT_REPO_SIGNING_KEY (pinned by
fingerprint, imported into a throwaway GNUPGHOME), and emits the public
keyring and a deb822 .sources file. The release workflow gains an
apt-repository job that re-verifies the .deb against release/trust and
publishes apt/ to gh-pages. Tests use a throwaway key, dpkg-deb dummies,
gpgv and apt-get in a private root. Docs and changelog updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fn7NbipAkYG7cYZxHk8xRu

Copy link
Copy Markdown
Owner Author

The verify check in Native desktop failed. This PR didn't cause it: the head commit only adds a docs file, and the same workflow has failed on main for its last four runs, starting at 71e358b.

The failing test is actual isolated D-Bus activation and external wrapper exit without a display (scripts/test-native-test-session.mjs:324). The D-Bus test service (org.shadowcode.NativeFixture) received SIGTERM during activation. The same test passes locally. No fix exists yet; I'll look into it and add a fix to this PR if I find one.


Generated by Claude Code

Copy link
Copy Markdown
Owner Author

My earlier comment needs a correction. main is red in verify for a different reason: Alpine 3.21 replaced the pinned python3 3.12.14-r0 and zlib 1.3.2-r0 packages, so building the native runtime fails during apk add. A pin bump is being added to this PR, the same kind of fix as e923e5e.

The D-Bus test failure on this PR did not happen on main, and it can't be reproduced locally after 40 runs. I've re-run the failed job once to check whether it's a flake. A change is also coming so a failure in this test prints the wrapper's full report.


Generated by Claude Code

claude added 19 commits October 11, 2026 07:13
Alpine replaced 3.12.14-r0 and zlib 1.3.2-r0, so apk add in the runtime
build container failed on main (as e923e5e did for openssl).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fn7NbipAkYG7cYZxHk8xRu
Parameterize every architecture-dependent packaging name through a single
table (scripts/native-arch.mjs) so the AppImage, .deb, AppImage runtime,
Flatpak and release tooling build natively on aarch64 as well as x86_64.
x86_64 names, gate scripts and asset sets are unchanged.

- AppImage runtime: build.sh names runtime-$(uname -m); native-runtime.mjs
  builds in linux/amd64 or linux/arm64 (the pinned Alpine digest is a
  multi-arch index); aarch64 runtimes use a distinct input hash.
- build-native.mjs: package names from the host (Tauri's _aarch64.AppImage,
  _arm64.deb), linuxdeploy ARCH, aarch64 runtime-sources tarball name.
- AppRun and the Tauri entry point find WebKit in the aarch64 multiarch dir.
- Flatpak: build-flatpak.py --arch (host default), arch-named bundle,
  --state-dir, --install-deps-from and --cpu-only-llama for CI builds.
- Clean-host image pins debian:trixie-20260803-slim per architecture.
- Release auth: envelopes carry x86_64 or aarch64 targets with per-target
  asset names; trust policies may list several targets; a release whose
  target the policy does not name is refused, as is a target switch against
  accepted history. Node and shell verifiers agree.
- Installer: the host selects the installable role (_amd64 on x86_64,
  _aarch64 on aarch64), installs -aarch64 names, and requires the signed
  target to match the host.
- native.yml: x86_64/aarch64 matrix on ubuntu-24.04 and ubuntu-24.04-arm.
- release.yml: build-aarch64 job runs every required gate on the Arm runner
  and retains unsigned aarch64 packages; signing stays x86_64-only until the
  trust policy names aarch64.
- Tests: aarch64 cases for release auth, installer (uname shim), deb
  fixture, packaging names (new test-native-arch.mjs).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fn7NbipAkYG7cYZxHk8xRu
ci.yml gains three pull-request jobs gated by a Git-only change detector
(packaging/, scripts/, src-tauri/, native/, .github/workflows/):

- packaging-dry-run: x86_64 and aarch64 (ubuntu-24.04-arm) legs run the
  architecture-name, release-auth, Debian assembly/runtime repack, desktop
  metadata, packaging PATH, pinned-source and installer suites plus a shell
  syntax pass over the packaging scripts.
- flatpak: builds the Flatpak with flatpak-builder and the GNOME 50 SDK
  (installed from Flathub by flatpak-builder), llama.cpp CPU-only to save
  time, caching ~/.flatpak-builder and the SDK Cargo/native build caches,
  then installs the bundle and starts the app and llama-server.

build-in-sdk.sh records a CPU-only CI runtime as backend=cpu and allows it
explicitly for the notices pass; release builds are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fn7NbipAkYG7cYZxHk8xRu
DISTRIBUTING, FLATPAK, RELEASING, RELEASE_AUTHENTICATION and the README
install section describe the aarch64 package names, the trust-policy target
list, the installer's host-architecture rule, the new build-flatpak.py
options and the pull-request Flatpak job. CHANGELOG gains a 2.1.0
(unreleased) entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fn7NbipAkYG7cYZxHk8xRu
Host bubblewrap now unshares the network and runs a packaged static relay
that forwards loopback :3128 to the allow-list proxy over a Unix socket in
the app's shared runtime folder. Fails closed (exit 125, command not run)
when the relay, socket or probe is unavailable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fn7NbipAkYG7cYZxHk8xRu
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fn7NbipAkYG7cYZxHk8xRu
Persist the main window's inner (content) size in logical pixels in a
versioned window-content-size.json, separate from the window-state
plugin (which still does not remember SIZE). Whatever the window reports
beyond the size it was built with (GTK header bar and shadow on Wayland)
is measured once and taken off before saving, so save -> restore is a
fixed point and repeated launches never grow. The restored size becomes
the configured inner size and then goes through the existing 90% / work
area / 520x640 fit. Files without version 2 are ignored.

Adds unit tests for the fixed point, no growth over 20 launches, small
screen clamping, version handling and implausible decoration deltas.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fn7NbipAkYG7cYZxHk8xRu
The suites assumed an x86_64 host; on ubuntu-24.04-arm 28 of 39
install-auth cases failed. A uname shim now fixes the default host and
the aarch64 cases put their own shim ahead of it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fn7NbipAkYG7cYZxHk8xRu
The release profile's thin LTO makes Cargo emit bitcode-only rlibs.
rust-lld (x86_64's default) accepts them; the GNU linker used on
aarch64 does not ('file format not recognized'). Reproduced on x86_64
with -fuse-ld=bfd; -C lto=fat links and the six regressions pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fn7NbipAkYG7cYZxHk8xRu
…est-passing suggestion

Schema 28 adds model_outcomes, written in the agent.completed transaction:
duration, tokens, cost only when reported (estimates stored as NULL), and
observed verification pass/fail. GET /api/models/outcomes summarizes it and,
when routing.suggest_from_history is on, suggests the cheapest model that
passed here before, respecting offline mode and spending limits. The picker
shows per-row history, a side-by-side table, and the suggestion, which the
user must accept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fn7NbipAkYG7cYZxHk8xRu
Before activation the installer snapshots the profile database through the
verified candidate's hidden `install-snapshot` command (SQLite VACUUM INTO,
no migration) and refuses while ShadowCode holds the profile lock. With a
display (or SHADOWCODE_FIRST_START_CHECK=require) it then starts the new
version, which writes a first-start marker once its database opened and the
main window finished loading. A hang, early exit or wrong-version report
stops the started session and restores AppImage, runtime, integrations and
the database snapshot. A new first_start_check journal phase lets
--recover do the same after an installer crash. The accepted-release record
is not lowered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fn7NbipAkYG7cYZxHk8xRu
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fn7NbipAkYG7cYZxHk8xRu
Fault-injection and download-mutation cases built PATH from the runner's
environment, bypassing the uname pin, so 16 cases failed on aarch64.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fn7NbipAkYG7cYZxHk8xRu
@ShadowfetchStudio ShadowfetchStudio changed the title ShadowCode 2.1 upgrades (work in progress) ShadowCode 2.1: ARM64, APT repo, installer rollback, Flatpak allow-list, window size, model history Oct 11, 2026
…the Flatpak smoke run a session bus

git worktree remove stops at the first entry it cannot delete, so
whether q/.git was removed depended on readdir order (fails on tmpfs and
on the aarch64 runner). Remove it explicitly to model the intended
partial removal; the assertions are unchanged. All worktrees::cleanup
tests pass on ext4, tmpfs and overlay as a non-root user.

The Flatpak built on CI but 'flatpak run' needs a session bus; run the
smoke checks under dbus-run-session.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fn7NbipAkYG7cYZxHk8xRu
The publisher's policy now names both targets, so the Node and shell
verifiers and the installer accept a signed aarch64 envelope. Signing
and publication remain x86_64-only until the release workflow emits a
per-target aarch64 envelope; docs and the workflow comment say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fn7NbipAkYG7cYZxHk8xRu
The outcome-history suggestion reads routing.suggest_from_history, but
Config::default and config.example.yaml lacked it, so config_keys failed.
It defaults to false (opt-in).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fn7NbipAkYG7cYZxHk8xRu
Each target is built, handed off, signed and authenticated separately,
and both publish into one GitHub release.

- Release metadata names are per target. x86_64 keeps the historical
  RELEASE-AUTH, RELEASE-AUTH.sig, RELEASE-MANIFEST.json and SHA256SUMS;
  aarch64 uses the same names with an -aarch64 suffix. The requested
  package selects the names a verifier reads (Node and shell), and the
  envelope found there must name that target. Verified stages and
  installer receipts keep the historical names for every target.
- Release gate scripts are built per architecture (gatesFor), so an x86_64
  signer checks aarch64 receipts against the aarch64 scripts. x86_64 gate
  scripts and digests are byte-identical.
- prepare/sign take a target (the handoff uses the build host; the signer
  an optional fourth argument) and the signer refuses a target the policy
  does not trust. publish takes several bundles, authenticates each for
  its own target, requires x86_64 and refuses repeated targets or names.
- release.yml: build-aarch64 hands off like build; sign waits for both
  and signs each into its own folder; publish passes both bundles; the
  APT job verifies and indexes amd64 and arm64.
- The in-app update notice names the aarch64 AppImage and its signature
  files on ARM64 and treats a release as signed only when they exist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fn7NbipAkYG7cYZxHk8xRu
claude and others added 8 commits October 11, 2026 09:48
…t SME

GGML_CPU_ALL_VARIANTS builds an Armv9.2 SME variant on aarch64
(-march=armv9.2-a+...+sme). Ubuntu 24.04's GCC 13 rejects the +sme
modifier, so the aarch64 verify job failed in the managed runtime build.
build-llama.cpp.sh now probes the C and C++ compilers on aarch64 and uses
gcc-14/g++-14 when the default cannot target armv9.2-a+sme, honours
SHADOWCODE_CC/SHADOWCODE_CXX, and otherwise stops with a clear message.
The aarch64 CI and release jobs install gcc-14 and g++-14; x86_64 builds
are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fn7NbipAkYG7cYZxHk8xRu
Tauri's AppImage bundler copies /usr/bin/xdg-open into the AppImage. The
GitHub x86_64 image ships xdg-utils; the ubuntu-24.04-arm image does not,
so the aarch64 packages gate failed after the runtime and app built.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fn7NbipAkYG7cYZxHk8xRu
…ture

`ARG TARGETARCH=amd64` overrode the TARGETARCH BuildKit sets, so the
aarch64 clean-host gate built the amd64 Debian stage and failed with
"exec format error". Declare the argument without a default; the gate's
docker build command (and its digest) is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fn7NbipAkYG7cYZxHk8xRu
The installer turns on its first-window check when DISPLAY or
WAYLAND_DISPLAY is set. These tests inherited the caller's environment, so
on a desktop 25 of them failed at "Could not snapshot the profile
database": their fixture AppImages do not implement the check. They now
clear those variables, as scripts/test-install-appimage.sh already does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The publisher key's version interval stopped at 1.0.1, so a 2.1.0 envelope
would be refused as above the key's range. Raise it to 2.1.0, as each past
release commit did, and update the docs that quote the range.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The repository job signs InRelease and Release.gpg with a dedicated
RSA-4096 OpenPGP key, fingerprint 1BC07B5ECFDB514673A296FB021CAB3F2F7A1F61,
held as NATIVE_APT_SIGNING_KEY with NATIVE_APT_SIGNING_FINGERPRINT in the
release-signing environment. This is its public half and fingerprint, so
users can check the keyring they download, as README.md and DISTRIBUTING.md
describe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Version 2.1.0 in Cargo, Tauri, the UI package, both desktop entries, the
Flatpak package test, the AppStream release list (dated after 2.0.0) and the
GitHub Action defaults. The changelog entry gets its title, the release notes
describe 2.1.0, the README names the 2.1.0 packages and the APT repository,
and the release docs name the v2.1.0 signing rule and tooling pin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Dockerfile selects its Debian stage from TARGETARCH and declares no
default, so BuildKit's own value wins. Docker's classic builder sets no
TARGETARCH at all, so on such a host the stage name was empty ("invalid
reference format") and the clean-host-packages gate failed before
building. Both build commands now pass the host architecture, which is
the value BuildKit would choose; tests check every build command does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ShadowfetchStudio
ShadowfetchStudio merged commit a32c2af into main Oct 11, 2026

This branch was successfully deployed

1 active deployment
release-signing — a32c2af9 Deployed Oct 11, 2026 by ShadowfetchStudio via apt-repository #36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants