Skip to content

fix: canonicalize desktop runtime manifests - #364

Merged
pedronauck merged 2 commits into
mainfrom
issue-362
Aug 13, 2026
Merged

pedronauck merged 2 commits into
mainfrom
issue-362

Conversation

@pedronauck

@pedronauck pedronauck commented Aug 13, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Fixes Linux AppImage runtime provisioning by making release manifests match the desktop verifier's canonical JSON contract.
  • Requires signed candidate manifests to pass the desktop's exact verifier for every shipping runtime target before publication.
  • Keeps signed candidate installer verification explicit: macOS and Linux package walks remain pending until a candidate is built from this branch.

Closes #362.

Root cause

The Go release producer serialized runtime.json from structs. Go preserves struct-field order, while the Rust desktop verifier recursively sorts object keys and requires those canonical bytes to match the signed input exactly. Beta.13 therefore carried a valid signature and valid archive metadata but was rejected as non-canonical before provisioning could install the runtime.

Changes

  • Manifest producer: canonicalizes JSON through a number-preserving generic document before writing feeds, recursively sorting object keys while preserving compact bytes and exact numeric values.
  • Desktop verifier: adds a regression proving that a correctly signed but non-canonical runtime manifest is rejected.
  • Release publication: adds the tracked compozy-runtime-manifest-check CLI and invokes it after signing and before upload for darwin-aarch64, darwin-x86_64, and linux-x86_64.
  • Release contract tests: assert signing → exact consumer verification → publication ordering and the complete shipping-target matrix.
  • CI checkout integrity: anchors the root build-output ignore rule as /bin/, so Rust's standard src/bin/ source directory is tracked. Commit 2c7499d7 fixes the missing-source failure from Actions job 94311211872.
  • QA tracking: records the absence of signed branch candidates and the exact macOS/Linux follow-up walks.

Release notes

🐛 Bug Fixes

  • Desktop releases now publish canonical signed runtime manifests that the macOS and Linux apps can verify during first-run provisioning.
  • Release publication now stops before upload if the signed candidate manifest fails the desktop's exact verification contract for any shipping runtime target.

QA

Status: ready with blocked items at the code-contract level; not ready for signed package acceptance.

Coverage: Lea's macOS and Linux first-run charters were evaluated through the package precondition boundary in an isolated targeted QA lab.

Blocked / needs human: build the next signed candidate from this branch, then walk the DMG on a clean macOS machine and the AppImage on a clean Linux machine through install, provisioning, relaunch, single-daemon verification, and healthy compozy status.

Process hygiene: the QA evidence audit passed, and teardown completed with clean: true and no survivors.

Full report: 2026-08-12-runtime-manifest-verification.md

Test plan

  • CGO_ENABLED=1 go test -race ./internal/desktoprelease ./internal/config
  • cargo fmt --manifest-path desktop/src-tauri/Cargo.toml --all -- --check
  • cargo clippy --manifest-path desktop/src-tauri/Cargo.toml --all-targets --all-features -- -D warnings
  • cargo test --manifest-path desktop/src-tauri/Cargo.toml runtime::artifacts::tests::
  • cargo test --locked --manifest-path desktop/src-tauri/Cargo.toml --bin compozy-runtime-manifest-check
  • make desktop-lint
  • make desktop-test
  • git ls-tree -r --name-only HEAD -- desktop/src-tauri/src/bin/runtime_manifest_check.rs
  • Targeted QA evidence audit and mandatory teardown (clean: true)
  • make gate-full was not completed at the operator's request
  • Signed candidate DMG and AppImage first-run walks require release signing infrastructure

Compozy Impact Audit

  • Native tools: No impact. Checked compozy__* tool IDs, descriptors, schemas, capability gates, and CLI/API fallbacks; this change is confined to offline desktop release metadata and publication.
  • Extensibility and hooks: No impact. Checked extensions, hooks, skills/capabilities, tools/resources, registries, bridge SDKs, MCP sidecars, and config.toml; no runtime extension surface or configuration key changes.
  • Workspace data isolation: No impact. The changed datum is global distribution metadata, not workspace/session/agent data; no CLI, HTTP, UDS, store, cache, SSE, or event path changes.
  • Official Compozy skill: No impact. Checked skills/compozy/; public tools, CLI paths, hook events, capabilities, extension resources, and task/memory/network behavior are unchanged.

Summary by CodeRabbit

  • Bug Fixes

    • Improved validation of signed runtime manifests, including rejection of non-canonical JSON.
    • Release feeds now verify manifest signatures, versions, platform coverage, and expected publication ordering.
    • Generated release metadata is written in a consistent, canonical JSON format with restrictive file permissions.
  • Tests

    • Expanded coverage for manifest signing, consumer verification, canonical feed output, and supported runtime platforms.

@vercel

vercel Bot commented Aug 13, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
compozy-site Ready Ready Preview Aug 13, 2026 1:08am

Request Review

@coderabbitai

coderabbitai Bot commented Aug 13, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 68e00688-2fa6-4c36-88d8-c279a784eb42

📥 Commits

Reviewing files that changed from the base of the PR and between 13659fb and 2c7499d.

📒 Files selected for processing (2)
  • .gitignore
  • desktop/src-tauri/src/bin/runtime_manifest_check.rs

Walkthrough

The change adds canonical JSON utilities, a runtime manifest verification CLI, desktop verification tests, and release workflow coverage. manifest.go still references the removed writeCanonicalJSON helper.

Changes

Runtime manifest validation

Layer / File(s) Summary
Canonical manifest output
internal/desktoprelease/canonical_json.go, internal/desktoprelease/manifest.go, internal/desktoprelease/release_test.go, .gitignore
Canonical JSON encoding and restricted file writing are added. Feed tests verify canonical latest.json and runtime.json output. The root-only ignore rule is updated. manifest.go retains calls to the removed helper.
Runtime manifest checker
desktop/src-tauri/src/bin/runtime_manifest_check.rs
The new CLI reads manifest inputs, verifies signatures for requested targets, checks the release version, and reports failures. Its test covers signed canonical manifest verification.
Desktop manifest verification
desktop/src-tauri/src/runtime/artifacts.rs
Test helpers now separate manifest construction from signing. Tests verify that signed non-canonical JSON returns ArtifactError::NonCanonical.
Release workflow validation
internal/config/release_config_test.go
Workflow tests require signed runtime manifest verification before desktop publication and validate the signing key, signature path, release version, and Darwin/Linux entries.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Mergeability Score: ⚪ Minimal · up to 2c749

The change canonicalizes signed runtime manifests and adds exact verification before publication; the previously reported clean-checkout blocker is addressed at the current head, so no actionable merge-blocking risk remains after normal checks and review.

Possibly related PRs

Suggested reviewers: antoniofulg

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR covers canonical manifest production and exact-verifier release checks, but it does not implement the requested provisioning diagnostics [#362]. Add actionable provisioning error details to the logs or UI, or link a separate PR that implements this coding requirement.
✅ Passed checks (4 passed)
Check name Status Explanation
Out of Scope Changes check ✅ Passed All changes support [#362] through manifest canonicalization, signature verification, and release publication tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: canonicalizing desktop runtime manifests.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-362

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.

@pedronauck
pedronauck marked this pull request as ready for review August 13, 2026 01:00
@greptile-apps

greptile-apps Bot commented Aug 13, 2026 •

Copy link
Copy Markdown

Greptile Summary

The PR canonicalizes desktop runtime manifests and validates signed manifests with the desktop verifier before publication.

  • Adds recursively key-sorted, compact JSON generation for release feeds.
  • Adds an exact-consumer manifest-checking CLI covering every shipping runtime target.
  • Adds regression, workflow-contract, and release-feed canonicalization tests.
  • Records signed-package QA as blocked until branch candidates are available.

Confidence Score: 3/5

The PR is not yet safe to merge because mutable third-party actions still execute inside the privileged release publication job before signing and upload credentials are exposed.

The previously reported release-boundary issue remains: moving stable and v2 references can execute changed upstream code in a job that subsequently receives the release token, Tauri signing key, and R2 credentials.

Files Needing Attention: .github/workflows/release.yml

Important Files Changed

Filename Overview
.github/workflows/release.yml Adds the exact-consumer verification step before publication, while the previously reported mutable third-party action references remain.
internal/desktoprelease/canonical_json.go Adds number-preserving generic JSON re-encoding so object keys are recursively sorted and output remains compact.
desktop/src-tauri/src/bin/runtime_manifest_check.rs Adds a fail-closed CLI that verifies the signed manifest and expected version for every requested target.
desktop/src-tauri/src/runtime/artifacts.rs Refactors signing fixtures and adds regression coverage for rejecting correctly signed but non-canonical manifest bytes.
internal/config/release_config_test.go Verifies signing, exact-consumer validation, publication ordering, credentials, and the shipping-target matrix.
internal/desktoprelease/release_test.go Adds canonical-byte assertions for both generated release manifests.

Sequence Diagram

sequenceDiagram
  participant Producer as Go release producer
  participant Signer as Manifest signer
  participant Verifier as Desktop verifier CLI
  participant R2 as Release storage
  Producer->>Producer: Canonicalize runtime.json
  Producer->>Signer: Pass canonical bytes
  Signer-->>Producer: runtime.json.minisig
  Producer->>Verifier: Manifest, signature, version, targets
  loop Every shipping target
    Verifier->>Verifier: Verify signature and canonical bytes
    Verifier->>Verifier: Validate artifact and version
  end
  Verifier-->>Producer: Verification succeeds
  Producer->>R2: Publish payloads and manifests
Loading

Reviews (2): Last reviewed commit: "fix: track runtime manifest checker" | Re-trigger Greptile

Comment on lines +879 to +882
- uses: dtolnay/rust-toolchain@stable

- uses: Swatinem/rust-cache@v2
with:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 security Mutable actions in release job

The new Rust toolchain and cache steps use mutable @stable and @v2 references before this job accesses release, signing, and R2 credentials. Pinning both actions to full commit SHAs prevents upstream tag movement from silently changing code executed inside this privileged publication boundary.

How this was verified: The mutable actions run before subsequent steps receive the release token, Tauri signing key, and R2 publication credentials.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/config/release_config_test.go`:
- Around line 549-561: Update the ordering assertions around the workflow string
in the relevant release-config test so they inspect only the desktop release
job, not the complete workflow. Parse or isolate that job before locating the
signing, verification, and publication steps, then preserve the existing
presence and ordering checks within that scoped job.
- Around line 549-550: Add the missing runtime_manifest_check.rs source declared
by the compo zy-runtime-manifest-check binary, and narrow or remove the
repository bin ignore rule so the file is tracked. Update the release-order test
around signManifest and verifyManifest to inspect only the desktop-publish job,
then assert the checker runs before scripts/publish-desktop-release.sh within
that job.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 67e4c389-11bc-40fb-a411-0c231226e8aa

📥 Commits

Reviewing files that changed from the base of the PR and between 5786c79 and 13659fb.

⛔ Files ignored due to path filters (4)
  • .github/workflows/release.yml is excluded by !**/*.yml
  • desktop/src-tauri/Cargo.toml is excluded by !**/*.toml
  • docs/qa/reports/2026-08-12-runtime-manifest-verification.md is excluded by !**/*.md
  • docs/qa/scenarios/APP-install-first-run-provision.md is excluded by !**/*.md
📒 Files selected for processing (5)
  • desktop/src-tauri/src/runtime/artifacts.rs
  • internal/config/release_config_test.go
  • internal/desktoprelease/canonical_json.go
  • internal/desktoprelease/manifest.go
  • internal/desktoprelease/release_test.go
💤 Files with no reviewable changes (1)
  • internal/desktoprelease/manifest.go

Comment on lines +549 to +550
signManifest := strings.Index(workflow, "scripts/sign-runtime-manifest.sh")
verifyManifest := strings.Index(workflow, "--bin compozy-runtime-manifest-check")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

fd -HI -a '^(Cargo\.toml|runtime_manifest_check\.rs)$' .
fd -HI -a '^Cargo\.toml$' . -x rg -n -C 3 'compozy-runtime-manifest-check|runtime_manifest_check' {}
git ls-files --error-unmatch -- ':(glob)**/runtime_manifest_check.rs'
git status --ignored --short --untracked-files=all | rg 'runtime_manifest_check\.rs|/bin/' || true

Repository: compozy/compozy

Length of output: 451


🏁 Script executed:

#!/usr/bin/env bash
set -u

printf '%s\n' '--- candidate files ---'
fd -HI -a 'runtime_manifest_check\.rs|Cargo\.toml|release.*\.ya?ml|.*workflow.*\.ya?ml' .

printf '%s\n' '--- Cargo references ---'
rg -n -C 5 'compozy-runtime-manifest-check|runtime_manifest_check' --glob 'Cargo.toml' --glob '*.rs' --glob '*.ya?ml' --glob '*.yaml' --glob '*.yml' .

printf '%s\n' '--- tracked source status ---'
git ls-files -- '**/runtime_manifest_check.rs'
git status --ignored --short --untracked-files=all | rg 'runtime_manifest_check\.rs|/bin/' || true
git check-ignore -v --no-index /home/jailuser/git/desktop/src-tauri/src/bin/runtime_manifest_check.rs || true

printf '%s\n' '--- Cargo manifest ---'
cat -n /home/jailuser/git/desktop/src-tauri/Cargo.toml | sed -n '55,85p'

printf '%s\n' '--- release test ---'
cat -n internal/config/release_config_test.go | sed -n '530,580p'

printf '%s\n' '--- workflow references ---'
rg -n -C 8 'sign-runtime-manifest|compozy-runtime-manifest-check|manifest' .github internal/config --glob '*.yml' --glob '*.yaml' --glob '*.go'

Repository: compozy/compozy

Length of output: 18668


🏁 Script executed:

#!/usr/bin/env bash
set -u

cat -n .github/workflows/release.yml | sed -n '850,975p'
printf '%s\n' '--- relevant workflow occurrences ---'
rg -n '^[[:space:]]{2}[A-Za-z0-9_-]+:|scripts/sign-runtime-manifest\.sh|--bin compozy-runtime-manifest-check|scripts/publish-desktop-release\.sh' .github/workflows/release.yml
printf '%s\n' '--- ignore rule ---'
cat -n .gitignore | sed -n '1,15p'

Repository: compozy/compozy

Length of output: 7612


Commit runtime_manifest_check.rs and scope the release-order test.

  • desktop/src-tauri/Cargo.toml declares compozy-runtime-manifest-check with source desktop/src-tauri/src/bin/runtime_manifest_check.rs, but that source is absent and matches the repository-wide bin/ ignore rule. The desktop-publish job invokes it before scripts/publish-desktop-release.sh, so the release job cannot build the checker. Add the source and narrow or remove the ignore rule.
  • The test compares positions in the full workflow text. It does not prove that the commands belong to the same desktop-publish job. Scope the assertion to that job or parse the workflow structure.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/config/release_config_test.go` around lines 549 - 550, Add the
missing runtime_manifest_check.rs source declared by the compo
zy-runtime-manifest-check binary, and narrow or remove the repository bin ignore
rule so the file is tracked. Update the release-order test around signManifest
and verifyManifest to inspect only the desktop-publish job, then assert the
checker runs before scripts/publish-desktop-release.sh within that job.

Comment on lines +549 to +561
signManifest := strings.Index(workflow, "scripts/sign-runtime-manifest.sh")
verifyManifest := strings.Index(workflow, "--bin compozy-runtime-manifest-check")
publishFeed := strings.Index(workflow, "scripts/publish-desktop-release.sh")
if signManifest == -1 || verifyManifest == -1 || publishFeed == -1 {
t.Fatal(
"desktop release workflow is missing candidate manifest signing, " +
"consumer verification, or publication",
)
}
if signManifest > verifyManifest || verifyManifest > publishFeed {
t.Fatal("desktop release workflow must verify signed candidate manifest bytes before publication")
}
verification := workflow[signManifest:publishFeed]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Scope the ordering check to the release job.

Line 549 searches the complete workflow text. A checker command in another job can satisfy this test while the publishing job does not wait for verification.

Parse the workflow, or isolate the release job, before comparing the signing, verification, and publication steps.

As per path instructions, tests must verify behavior outcomes on critical paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/config/release_config_test.go` around lines 549 - 561, Update the
ordering assertions around the workflow string in the relevant release-config
test so they inspect only the desktop release job, not the complete workflow.
Parse or isolate that job before locating the signing, verification, and
publication steps, then preserve the existing presence and ordering checks
within that scoped job.

Source: Path instructions

This branch was successfully deployed

1 active deployment
Preview — 2c7499d7 Deployed Aug 13, 2026 by vercel[bot]
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.

[Linux/AppImage] beta.13 cannot provision runtime: runtime manifest is rejected as non-canonical

1 participant