Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 21 additions & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,19 @@ jobs:
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_INSTALLER_SIGNING_IDENTITY: ${{ secrets.APPLE_INSTALLER_SIGNING_IDENTITY }}
run: |
if [ -z "$APPLE_INSTALLER_SIGNING_IDENTITY" ]; then
echo "::error::APPLE_INSTALLER_SIGNING_IDENTITY secret is not set"
exit 1
fi
case "$APPLE_INSTALLER_SIGNING_IDENTITY" in
"Developer ID Installer:"*) ;;
*)
echo "::error::APPLE_INSTALLER_SIGNING_IDENTITY must name a Developer ID Installer identity"
exit 1
;;
esac
echo "$APPLE_CERTIFICATE" | base64 --decode > cert.p12
KEYCHAIN="preflight-$$.keychain"
security create-keychain -p "" "$KEYCHAIN"
Expand Down Expand Up @@ -375,14 +387,16 @@ jobs:
done

- name: Build .pkg installer
env:
APPLE_INSTALLER_SIGNING_IDENTITY: ${{ secrets.APPLE_INSTALLER_SIGNING_IDENTITY }}
run: |
VERSION="${GITHUB_REF_NAME#v}"
bash scripts/build-pkg.sh \
"target/release/bundle/macos/Capsem.app" \
"target/release" \
"assets" \
"$VERSION" \
"${{ secrets.APPLE_INSTALLER_SIGNING_IDENTITY }}"
"$APPLE_INSTALLER_SIGNING_IDENTITY"

- name: Verify .pkg payload manifest
run: |
Expand Down Expand Up @@ -420,6 +434,12 @@ jobs:
xcrun stapler staple "packages/Capsem-$VERSION.pkg"
xcrun stapler validate "packages/Capsem-$VERSION.pkg"

- name: Verify .pkg signature and Gatekeeper acceptance
run: |
VERSION="${GITHUB_REF_NAME#v}"
pkgutil --check-signature "packages/Capsem-$VERSION.pkg"
spctl -a -vv -t install "packages/Capsem-$VERSION.pkg"

- name: Generate SBOM
run: cargo sbom --output-format spdx_json_2_3 > capsem-sbom.spdx.json

Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
as the first FAQ.
- Added a reusable `.deb` payload verifier and wired release CI to validate
Linux package helper binaries, signed manifests, and manifest signatures.
- Added a macOS release CI gate that requires a Developer ID Installer identity
and runs `pkgutil --check-signature` plus Gatekeeper assessment after
notarization and stapling.

### Fixed
- Fixed the marketing-site installer for the stamped v1.1 package assets:
macOS now installs the downloaded `.pkg` with the native installer, and
package downloads are checked against the release manifest when local tools
are available.
- Fixed `.deb` payload verification for zstd-compressed packages without an
embedded content-size header, matching the published Debian package format.
- Fixed Linux KVM unit-test compilation issues surfaced by PR CI before the
site/download installer hardening can merge.
- Fixed macOS PR CI's clean-checkout Rust unit gate by creating a minimal
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ dependencies = [
"click>=8.0",
"jinja2>=3.0",
"blake3>=1.0.8",
"zstandard>=0.25.0",
]

[project.scripts]
Expand Down
3 changes: 2 additions & 1 deletion scripts/verify_deb_payload.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ def _decompress(name: str, payload: bytes) -> bytes:
stderr = result.stderr.decode("utf-8", errors="replace")
raise VerificationError(f"zstd failed to decompress {name}: {stderr}")
return result.stdout
return zstandard.ZstdDecompressor().decompress(payload)
with zstandard.ZstdDecompressor().stream_reader(BytesIO(payload)) as reader:
return reader.read()
return payload


Expand Down
13 changes: 12 additions & 1 deletion skills/release-process/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,14 @@ Test runs in parallel with builds. A test failure blocks `create-release` but do
- **`just cross-compile` is not a perfect CI replica.** It runs in a docker container on macOS, which has FUSE (via Colima's Linux VM). CI runners may not have FUSE, so AppImage bundling that works locally can fail in CI. The recipe catches compile errors and most packaging issues, but environment differences (FUSE, linuxdeploy availability) can still slip through. Always verify the first CI run of a new Linux packaging change.
- **Platform-gate all macOS-only APIs.** Every use of `libc::clonefile`, `AppleVzHypervisor`, `core_foundation_sys`, etc. must be wrapped in `#[cfg(target_os = "macos")]` -- struct, impl, AND tests. The Linux app build compiles the full workspace. `cargo test --test platform_gating` catches ungated symbols at unit test time. This burned v0.14.7 through v0.14.9.
- **Pin Xcode version on macOS runners.** Always `sudo xcode-select -s /Applications/Xcode_16.2.app` (or latest) before any Apple toolchain use. GitHub periodically updates runner images and the default Xcode can break (Abort trap in xcodebuild). The preflight may pass on one runner instance while build-app-macos gets a different one. v0.14.12 failed because Xcode 15.4's xcodebuild crashed with `Abort trap: 6` when Tauri tried to locate notarytool -- despite zero workflow changes from v0.14.11 which passed 9 hours earlier.
- **Installer identity and Gatekeeper checks are release gates.** Release
preflight must require `APPLE_INSTALLER_SIGNING_IDENTITY`, and it must start
with `Developer ID Installer:`. Pass it into `scripts/build-pkg.sh` through
the job environment, not inline expressions. After `xcrun stapler validate`,
`build-app-macos` must run `pkgutil --check-signature` and
`spctl -a -vv -t install` against the built `.pkg`. If a local macOS host
reports Code Signing subsystem errors for multiple known-good releases, treat
the host as suspect, but keep the CI macOS gate release-blocking.
- **`latest.json` is optional in `gh release create`.** Tauri only generates updater `latest.json` for bundle types that produce `.tar.gz` + `.sig` artifacts (AppImage, not deb). With deb-only builds, no `latest.json` exists. The create-release step must handle this gracefully.
- **AppImage was dropped after 14 failed releases.** linuxdeploy (a FUSE2 AppImage) cannot run on Ubuntu 24.04 CI runners (FUSE3 only). Tested: `libfuse2` install, `APPIMAGE_EXTRACT_AND_RUN=1` env var, both together -- none worked reliably. If AppImage support is needed in the future, the approach would be to pre-extract linuxdeploy (`--appimage-extract`) and run the extracted binary directly, bypassing FUSE entirely.

Expand Down Expand Up @@ -220,6 +228,7 @@ Propagation can lag 1-5 min after accepting. `notarytool history` must return a
| `APPLE_CERTIFICATE` | Base64 `.p12` (legacy 3DES) |
| `APPLE_CERTIFICATE_PASSWORD` | Password for p12 |
| `APPLE_SIGNING_IDENTITY` | `Developer ID Application: Elie Bursztein (L8EGK4X86T)` |
| `APPLE_INSTALLER_SIGNING_IDENTITY` | `Developer ID Installer: Elie Bursztein (L8EGK4X86T)` |
| `APPLE_API_ISSUER` | App Store Connect issuer UUID |
| `APPLE_API_KEY` | App Store Connect key ID |
| `APPLE_API_KEY_PATH` | Contents of `.p8` private key |
Expand Down Expand Up @@ -250,7 +259,9 @@ Use `scripts/verify_deb_payload.py` for `.deb` inspection instead of ad hoc
`tar`/`strings` checks. It validates control metadata, companion binaries, the
signed manifest files, and optional minisign verification. The manifest
signature check is mandatory for local-signature releases; a release is not
verified until `minisign -Vm` passes against `config/manifest-sign.pub`.
verified until `minisign -Vm` passes against `config/manifest-sign.pub`. The
script handles `.tar.zst` Debian payloads with a streaming zstandard reader
because published `.deb` members may omit an embedded content-size header.

For a demo-facing macOS release, also prove the installer path users see:

Expand Down
18 changes: 9 additions & 9 deletions sprints/release-policy-hardening/MASTER.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

## Goal

Prepare the next `1.1.1778456247` release candidate by fixing the blocker-class issues
Prepare the next `1.1.1778542197` release candidate by fixing the blocker-class issues
found in the post-sprint swarm review: release artifacts must install and boot
on fresh machines, Policy V2 settings must be usable from the UI, hook runtime
must fail closed safely, and docs must not overclaim shipped behavior.

T9 selected exact version `1.1.1778456247` and owns keeping the stamp recipe,
T9 selected exact version `1.1.1778542197` and owns keeping the stamp recipe,
docs, binaries, and tag plan on that line.

## Status
Expand All @@ -22,11 +22,11 @@ docs, binaries, and tag plan on that line.
| T5 service/process/package helpers | Implementation complete; focused package/VM proof passed in T10 | P0 | T0 package layout, T1 rootfs gate | Focused Rust/Python checks + compile gate + T10 Gate B/E2E/package proof | Helper packaging, spec route/auth proof, rootfs validation, env isolation, async cleanup, and builtin-aware reload/refresh are implemented; clean installed-package launch remains T11. |
| T6 telemetry/session tooling | Implementation complete; focused real-session trace proof passed in T10 | P2 | T3/T8 telemetry semantics | Logger/core/service/MCP/session/frontend gates passed + focused T8 timeline assertion | Old/core DB compatibility, Policy V2 schema checks, MCP/tool correlation, dns/hook/audit/snapshot timeline layers, triage, frontend policy fields, lifecycle tests, and legacy migration coverage are implemented. |
| T7 swarm intake and review control | Owner mapping complete; downstream closeout open | P0 | T8-T12 downstream resolution | FD01-FD14 transfer trackers + Galileo audit | Final investigation wave and mapping audit are captured; every finding doc is linked to owner T-track rows, while downstream blocker checkboxes stay open until resolved or deferred. |
| T8 policy integration E2E | Implementation complete; focused VM proof passed in T10 | P1 | T2/T3/T5/T6 | Hook defer decision + 388 frontend tests + focused Rust checks + focused VM E2E | Configured external hook dispatch is deferred for `1.1.1778456247`; non-hook Policy V2 settings/reload/timeline E2E path, reload banner dismissal, backend hook rejection, runtime support matrix, and live `/settings` + `/reload-config` MCP E2E proof are implemented. |
| T9 release metadata and changelog | Implementation complete; commit discipline pending | P1 | T0-T8 final decisions | version sync + latest-release extraction + release page + docs build + partial workflow check | Exact `1.1.1778456247` stamp, changelog, latest release, 1.1 release page, lockfile, stamp recipe, and internal dependency metadata are synchronized; workflow preflight still needs local signing prerequisites. |
| T8 policy integration E2E | Implementation complete; focused VM proof passed in T10 | P1 | T2/T3/T5/T6 | Hook defer decision + 388 frontend tests + focused Rust checks + focused VM E2E | Configured external hook dispatch is deferred for `1.1.1778542197`; non-hook Policy V2 settings/reload/timeline E2E path, reload banner dismissal, backend hook rejection, runtime support matrix, and live `/settings` + `/reload-config` MCP E2E proof are implemented. |
| T9 release metadata and changelog | Implementation complete; commit discipline pending | P1 | T0-T8 final decisions | version sync + latest-release extraction + release page + docs build + partial workflow check | Exact `1.1.1778542197` stamp, changelog, latest release, 1.1 release page, lockfile, stamp recipe, and internal dependency metadata are synchronized; workflow preflight still needs local signing prerequisites. |
| T10 focused verification | Complete; T11 blockers explicit | P0 | T0-T9 fixes | focused Rust/Python/frontend/docs + `.deb` install + `.pkg` expansion + Gate A/B + T8 E2E proof | Targeted checks, host doctor, strict `just test-install`, `just exec "echo cli-ok"`, `just exec "capsem-doctor"`, focused T8 policy E2E, rootfs validation, `.pkg` expansion/signature proof, frontend coverage, and `just ui` visual evidence are passing/captured; clean installed-package proof remains a T11/manual-host gate. |
| T11 local release candidate gate | Full suite/private preflight/install smoke/installed doctor/demo UI green; manual sign-off open | P0 | T10 green | Full `just test`, final doctor, restored-private preflight, VM doctor, Docker install e2e, host install smoke | Final `just test`, final host `just doctor`, direct B3SUMS/signature checks, `just exec "capsem-doctor"`, restored Apple/notary/manifest preflight, `just install`, installed CLI run, installed doctor, rebuilt `.pkg` app-materialization fix, `/Applications` demo UI launch, `just run-ui --` process proof, and installed-app tray relaunch proof passed by 2026-05-11. Remaining blockers are Elie Gate C/Gate D visual sign-off and the no-tag/no-push hold before T12. |
| T12 CI green release landing | Not started | P0 | T11 signed off | 10 CI/live-release gates | Tag `v1.1.1778456247`, CI green, release assets verified, release landed. |
| T12 CI green release landing | Release landed; CI hardening follow-up in progress | P0 | T11 signed off | CI green + live asset verification + follow-up package gates | `v1.1.1778542197` is published/latest, CI and site publish are green, live manifest/packages verify, and follow-up CI now blocks future releases on macOS pkg signature/Gatekeeper checks. |

## Phases

Expand Down Expand Up @@ -64,7 +64,7 @@ docs, binaries, and tag plan on that line.
`tracker.md`.
7. Run T11 locally: full suite, package generation, local install, and Elie +
Codex CLI/UI/full-launch verification.
8. Only after T11 is signed off, run T12: tag `v1.1.1778456247`, wait for CI, verify
8. Only after T11 is signed off, run T12: tag `v1.1.1778542197`, wait for CI, verify
published assets, and mark the release landed.

## Elie + Codex Manual Gates
Expand Down Expand Up @@ -150,7 +150,7 @@ The release workflow must fail before publish if any expected item is missing.
- `scripts/preflight.sh` and `scripts/check-release-workflow.sh`: verify Apple
signing/notarization readiness, Tauri signing readiness if updater remains
enabled, and manifest-signing readiness.
- Release job: after tag `v1.1.1778456247`, wait for CI green and require live
- Release job: after tag `v1.1.1778542197`, wait for CI green and require live
GitHub release asset verification before declaring the release landed.

## Immediate Release Blockers
Expand Down Expand Up @@ -299,7 +299,7 @@ findings, and had its output copied into a durable finding doc under
- Docs/release notes distinguish shipped hook Spec0/runtime infrastructure from
not-yet-wired user/corp hook dispatch.
- T8 records that configured external hook dispatch does not ship in
`1.1.1778456247`; UI/docs/tests reject or describe hook dispatch as
`1.1.1778542197`; UI/docs/tests reject or describe hook dispatch as
infrastructure-only.
- Frontend runtime/image truth has an owner: asset readiness, image/fork UI
contract, create defaults, and service/gateway status truth cannot remain
Expand All @@ -311,5 +311,5 @@ findings, and had its output copied into a durable finding doc under
release tagging.
- `just install` generates and installs the package locally before CI/tagging,
and Elie signs off CLI, JS UI, desktop launch, and installed app behavior.
- T12 waits for CI green and verifies live `v1.1.1778456247` release assets before the
- T12 waits for CI green and verifies live `v1.1.1778542197` release assets before the
release is marked landed.
Loading
Loading