Skip to content

Commit 4f4610f

Browse files
authored
Merge pull request #76 from TrentBrown/tb-portreeve-apple-trust-03-verification-sealed-distribution
feat(release): verify and seal Apple trust distribution
2 parents d54fdc0 + 4a96c4f commit 4f4610f

31 files changed

Lines changed: 1933 additions & 133 deletions

.agents/skills/release-portreeve/SKILL.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ gate.
2929
- For a local engine check, run `release:prepare`. Explain that one host stops before
3030
the complete native matrix.
3131
- For a complete candidate or rehearsal, use the manual GitHub workflow with
32-
`publish=false`. Download `distribution-<version>` after it succeeds and run
32+
`trust=true` and `publish=false`. Approval of `release-trust` authorizes only the
33+
nonpublic Apple producer. Download `distribution-<version>` after it succeeds and run
3334
`release:inspect` against its `release-record.json`, followed by the disposable
3435
`release:homebrew-smoke` formula/cask installation check on macOS.
3536
- For an interrupted candidate, resume only through the exact recovery path documented
@@ -67,6 +68,7 @@ For the complete hosted rehearsal:
6768
gh workflow run release.yml \
6869
-f channel=preview \
6970
-f version=0.1.0-preview.1 \
71+
-f trust=true \
7072
-f publish=false
7173
```
7274

@@ -85,6 +87,11 @@ temporary tap, and must clean up without changing supervision or PortReeve data.
8587
Do not invent signing evidence for stable. Missing Developer ID, notarization, stapling,
8688
Gatekeeper, or native evidence must remain a failure.
8789

90+
Require the protected producer plus one current native ARM64 and one current native
91+
Intel Apple trust document for every new public preview as well as stable. A personal
92+
manual-install check on another architecture is optional; hosted native evidence is not.
93+
Historical previews through `0.1.0-preview.4` remain immutable unsigned history.
94+
8895
## Preserve the publication boundary
8996

9097
Do not set `publish=true`, call `release:publish`, create or move a tag, create a GitHub
@@ -94,7 +101,8 @@ explicitly requests those public mutations after the exact plan is available.
94101
When publication is explicitly requested:
95102

96103
1. Revalidate the completed record and every artifact digest.
97-
2. Present the plan path and SHA-256 plus GitHub, tap, and update-metadata targets.
104+
2. Require `publication-plan.sha256` to match the exact plan, then present the plan path
105+
and SHA-256 plus GitHub, tap, and update-metadata targets.
98106
3. Obtain the user's explicit approval for that exact candidate if it has not already
99107
been given.
100108
4. Use the normal `release-publication` environment or the documented direct command.
@@ -118,6 +126,7 @@ Separate:
118126
- generated artifact names and digests;
119127
- preview/stable, maturity, and Desktop trust;
120128
- public mutations performed, or an explicit statement that none occurred;
129+
- protected trust approval and publication approval as separate facts;
121130
- generated Homebrew and Desktop PR URLs plus verified merge commits when published;
122131
- exact recovery or next command.
123132

.github/workflows/release.yml

Lines changed: 86 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ env:
3636
BUN_VERSION: 1.3.14
3737
NODE_VERSION: 22
3838
RELEASE_ROOT: dist/releases/${{ inputs.version }}
39+
TRUSTED_ROOT: dist/trusted/${{ inputs.version }}
3940
PORTREEVE_HOMEPAGE_URL: ${{ github.server_url }}/${{ github.repository }}
4041
PORTREEVE_RELEASE_BASE_URL:
4142
${{ github.server_url }}/${{ github.repository }}/releases/download
@@ -186,7 +187,6 @@ jobs:
186187
permissions:
187188
contents: read
188189
env:
189-
TRUSTED_OUTPUT_ROOT: dist/trusted/${{ inputs.version }}
190190
PORTREEVE_APPLE_SIGNING_IDENTITY: ${{ vars.PORTREEVE_APPLE_SIGNING_IDENTITY }}
191191
PORTREEVE_APPLE_TEAM_ID: ${{ vars.PORTREEVE_APPLE_TEAM_ID }}
192192
PORTREEVE_APPLE_NOTARY_KEY_ID: ${{ vars.PORTREEVE_APPLE_NOTARY_KEY_ID }}
@@ -215,15 +215,94 @@ jobs:
215215
run: >-
216216
bun run release:produce-apple-trust -- --record
217217
"$RELEASE_ROOT/release-record.json" --qualification
218-
"$RELEASE_ROOT/evidence/trust-qualification.json" --output
219-
"$TRUSTED_OUTPUT_ROOT"
218+
"$RELEASE_ROOT/evidence/trust-qualification.json" --output "$TRUSTED_ROOT"
220219
- uses: actions/upload-artifact@v7
221220
with:
222221
name: trusted-${{ inputs.version }}
223-
path: ${{ env.TRUSTED_OUTPUT_ROOT }}
222+
path: ${{ env.TRUSTED_ROOT }}
224223
if-no-files-found: error
225224
retention-days: 7
226225

226+
trusted-native-evidence:
227+
needs: release-trust
228+
strategy:
229+
fail-fast: false
230+
matrix:
231+
include:
232+
- runner: macos-15
233+
architecture: arm64
234+
- runner: macos-15-intel
235+
architecture: x64
236+
runs-on: ${{ matrix.runner }}
237+
name: Apple trust evidence (macos-${{ matrix.architecture }})
238+
permissions:
239+
contents: read
240+
steps:
241+
- uses: actions/checkout@v7
242+
- uses: actions/setup-node@v7
243+
with:
244+
node-version: ${{ env.NODE_VERSION }}
245+
- uses: oven-sh/setup-bun@v2
246+
with:
247+
bun-version: ${{ env.BUN_VERSION }}
248+
- run: bun ci
249+
- uses: actions/download-artifact@v8
250+
with:
251+
name: trusted-${{ inputs.version }}
252+
path: ${{ env.TRUSTED_ROOT }}
253+
- name: Restore authoritative executable modes
254+
run: chmod 755 "$TRUSTED_ROOT"/artifacts/portreeve-v*
255+
- name: Verify exact Apple artifacts on their native architecture
256+
run: >-
257+
bun run release:apple-native-evidence -- --record
258+
"$TRUSTED_ROOT/release-record.json" --producer-evidence
259+
"$TRUSTED_ROOT/evidence/apple-trust-producer.json"
260+
- uses: actions/upload-artifact@v7
261+
with:
262+
name: apple-native-${{ matrix.architecture }}
263+
path:
264+
${{ env.TRUSTED_ROOT }}/evidence/apple-native-${{ matrix.architecture
265+
}}.json
266+
if-no-files-found: error
267+
retention-days: 7
268+
269+
finalize-trusted-distribution:
270+
needs: trusted-native-evidence
271+
runs-on: ubuntu-24.04
272+
permissions:
273+
contents: read
274+
steps:
275+
- uses: actions/checkout@v7
276+
- uses: actions/setup-node@v7
277+
with:
278+
node-version: ${{ env.NODE_VERSION }}
279+
- uses: oven-sh/setup-bun@v2
280+
with:
281+
bun-version: ${{ env.BUN_VERSION }}
282+
- run: bun ci
283+
- uses: actions/download-artifact@v8
284+
with:
285+
name: trusted-${{ inputs.version }}
286+
path: ${{ env.TRUSTED_ROOT }}
287+
- uses: actions/download-artifact@v8
288+
with:
289+
pattern: apple-native-*
290+
path: ${{ env.TRUSTED_ROOT }}/evidence
291+
merge-multiple: true
292+
- name: Seal exact distribution metadata and publication plan
293+
run: >-
294+
bun run release:finalize-desktop -- --record
295+
"$TRUSTED_ROOT/release-record.json" --producer-evidence
296+
"$TRUSTED_ROOT/evidence/apple-trust-producer.json" --apple-evidence
297+
"$TRUSTED_ROOT"/evidence/apple-native-*.json --current-update-manifest
298+
distribution/desktop-update.json
299+
- uses: actions/upload-artifact@v7
300+
with:
301+
name: distribution-${{ inputs.version }}
302+
path: ${{ env.TRUSTED_ROOT }}
303+
if-no-files-found: error
304+
retention-days: 14
305+
227306
desktop-evidence:
228307
needs: aggregate-native-evidence
229308
strategy:
@@ -265,6 +344,7 @@ jobs:
265344
retention-days: 7
266345

267346
finalize-distribution:
347+
if: ${{ !inputs.trust }}
268348
needs: desktop-evidence
269349
runs-on: ubuntu-24.04
270350
steps:
@@ -299,8 +379,8 @@ jobs:
299379
retention-days: 14
300380

301381
publish:
302-
if: inputs.publish
303-
needs: finalize-distribution
382+
if: ${{ always() && inputs.publish && !failure() && !cancelled() }}
383+
needs: [finalize-distribution, finalize-trusted-distribution]
304384
runs-on: ubuntu-24.04
305385
environment: release-publication
306386
permissions:

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
> [!WARNING]
44
>
55
> **Alpha Preview** — PortReeve is evolving quickly. Breaking changes remain possible,
6-
> and the current macOS preview is unsigned until Apple Developer ID signing and
7-
> notarization are configured. Review the
8-
> [installation and removal guide](docs/installation.md), including the scoped macOS
9-
> **Open Anyway** procedure, before installing it.
6+
> and the currently published macOS previews through `0.1.0-preview.4` are immutable
7+
> unsigned history. New public previews require Apple Developer ID signing and
8+
> notarization. Review the [installation and removal guide](docs/installation.md),
9+
> including the narrowly scoped historical-preview guidance, before installing.
1010
1111
<p align="center">
1212
<img src="apps/desktop/assets/branding/portreeve-lockup.svg" width="570" alt="PortReeve, Local Port Authority">
@@ -32,8 +32,8 @@ brew install trentbrown/portreeve/portreeve
3232
```
3333

3434
Homebrew does not silently install or start the supervised service. Direct macOS DMGs,
35-
Linux executables, checksum verification, unsigned-app guidance, and removal procedures
36-
are covered in the [installation guide](docs/installation.md).
35+
Linux executables, checksum verification, historical unsigned-preview guidance, and
36+
removal procedures are covered in the [installation guide](docs/installation.md).
3737

3838
<!-- product-overview:identity-problem -->
3939

docs/desktop.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ bundles a verified CLI artifact and can install that artifact into the one manag
77
per-user location used by native supervision.
88

99
Desktop is currently alpha software. Its persistent header notice reports product
10-
maturity independently of update channel and macOS trust; a preview download may be
11-
unsigned without making every future preview unsigned.
10+
maturity independently of update channel and macOS trust. Historical previews through
11+
`0.1.0-preview.4` remain unsigned, while every new public preview requires Developer ID
12+
signing and notarization.
1213

1314
## Overview and lifecycle
1415

docs/installation.md

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,18 @@
22

33
> **Alpha Preview**
44
>
5-
> PortReeve is evolving quickly and may make breaking changes. The current public alpha
6-
> preview is available on
7-
> [GitHub Releases](https://github.com/TrentBrown/portreeve/releases), and its macOS
8-
> application is unsigned until Apple Developer ID signing and notarization are
9-
> configured. Product maturity (`alpha`), release channel (`preview`), and macOS trust
10-
> (`unsigned`) are separate facts.
11-
12-
The preview is ad-hoc signed so macOS can verify that the assembled application bundle
13-
and its nested executables have not changed. An ad-hoc signature carries no verified
14-
developer identity and is not notarization, so Gatekeeper may still require the scoped
15-
**Open Anyway** procedure below.
5+
> PortReeve is evolving quickly and may make breaking changes. Public alpha previews are
6+
> available on [GitHub Releases](https://github.com/TrentBrown/portreeve/releases).
7+
> Published previews through `0.1.0-preview.4` are immutable unsigned history; every new
8+
> public preview requires Apple Developer ID signing and notarization. Product maturity
9+
> (`alpha`), release channel (`preview`), and macOS trust are separate facts.
10+
11+
Historical previews through `0.1.0-preview.4` are ad-hoc signed so macOS can verify that
12+
the assembled application bundle and its nested executables have not changed. An ad-hoc
13+
signature carries no verified developer identity and is not notarization, so Gatekeeper
14+
may still require the scoped **Open Anyway** procedure below. Newer public previews fail
15+
closed unless they are Developer ID-signed, notarized, stapled, accepted by Gatekeeper,
16+
and independently verified on native Apple Silicon and Intel runners.
1617

1718
Use [GitHub Releases](https://github.com/TrentBrown/portreeve/releases) to identify the
1819
newest preview and verify its assets. The commands below install the currently published
@@ -79,9 +80,10 @@ shasum -a 256 PortReeve-VERSION-macos-ARCH.dmg
7980
Compare the complete output with the corresponding line in the downloaded checksum
8081
file. Do not continue when the values differ.
8182

82-
## Opening an unsigned preview safely
83+
## Opening a historical unsigned preview safely
8384

84-
Try to open PortReeve normally first. An unsigned preview may be blocked because Apple
85+
This exception applies only to immutable previews through `0.1.0-preview.4`. Try to open
86+
PortReeve normally first. A historical unsigned preview may be blocked because Apple
8587
cannot verify a Developer ID signature or notarization ticket. Only continue when the
8688
DMG came from the PortReeve GitHub Release and its SHA-256 matches the release checksum.
8789

docs/issues/tb-portreeve-apple-trust/decisions.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,20 @@ PortReeve accepts only its exact Developer ID identity, Team ID, and product-spe
4242
- Allow unbounded polling - rejected because failure must terminate predictably.
4343

4444
**Promoted:** 2026-08-28. PR: https://github.com/TrentBrown/portreeve/pull/74.
45+
46+
---
47+
48+
## Transform signed CLI metadata as one authority set
49+
50+
**Confidence:** HIGH
51+
52+
**Blast Radius:** Protected producer output, release record, manifest, Homebrew formula, checksums, native verification, and finalization
53+
54+
When the protected producer transforms either macOS CLI, it must rewrite the corresponding manifest entries, Homebrew checksums, SHA256SUMS document, and release-record metadata identities before the output is verified or uploaded. Native Apple jobs and finalization consume only this synchronized protected tree; they do not tolerate metadata that still names the unsigned predecessor.
55+
56+
**Triggered by:** Developer ID signing changes the macOS CLI bytes after preliminary qualification
57+
58+
**Alternatives considered:**
59+
Defer metadata repair until finalization - rejected because native verification must consume a self-consistent protected output. Keep separate unsigned and signed manifests - rejected because multiple authorities would make downstream selection ambiguous. Rebuild metadata independently in each native job - rejected because read-only verifiers must not mutate the producer output.
60+
61+
**Promoted:** 2026-08-29. PR: https://github.com/TrentBrown/portreeve/pull/76.

0 commit comments

Comments
 (0)