1# Portal public release runbook (issue #5)23This runbook completes the repository-controlled portion of4[issue #5](https://github.com/FelineStateMachine/goq/issues/5). The first5supported Portal download is **macOS arm64 only**. x86_64, universal, Linux,6and Windows builds remain unavailable until they receive equivalent signing7and release gates.89Portal is always a compiled DMG download. There is no shell installer and no10fallback to an ad-hoc, demo, or development build.1112The release build uses no optional Cargo features. In particular,13`demo-direct-node` and `experimental-non-macos-pointer-capture` are forbidden14from the default feature set and from the packaging command. The latter exists15only to compile the unverified Tao + browser Pointer Lock path during16non-macOS platform UAT; see17[Portal platform support](portal-platform-support.md).1819## One-time GitHub and Apple setup2021Protect the GitHub environment named `main` with required reviewers and limit22deployment branches/tags according to the repository's release policy. Add23these environment secrets:2425| Secret | Purpose |26| --- | --- |27| `PORTAL_APPLE_CERTIFICATE_BASE64` | Base64-encoded Developer ID Application PKCS#12 certificate |28| `PORTAL_APPLE_CERTIFICATE_PASSWORD` | PKCS#12 import password |29| `PORTAL_APPLE_SIGNING_IDENTITY` | Exact `Developer ID Application: ...` keychain identity |30| `PORTAL_APPLE_ID` | Apple account used by the notarization service |31| `PORTAL_APPLE_PASSWORD` | App-specific password for notarization |32| `PORTAL_APPLE_TEAM_ID` | Apple Developer team identifier |3334Do not put certificate bytes, passwords, API keys, or secret-derived output in35the repository, workflow artifacts, release notes, or website manifest. The36workflow imports the certificate into an ephemeral keychain and removes it in37an always-running cleanup step.3839The Apple team identifier itself is public release policy, not a credential.40Before the first release tag, replace `unconfigured` in41`release/portal-apple-team-id.txt` with the exact ten-character TeamIdentifier42for the protected Developer ID certificate. The protected43`PORTAL_APPLE_TEAM_ID` secret must match that committed pin. Packaging,44promotion, and hardware UAT all fail closed if the pin is missing, malformed,45or different from the application signature.4647## Prepare an exact tag4849Choose a SemVer tag such as `v0.3.0-alpha.1`. Before tagging, update every50workspace package version and `src-tauri/tauri.conf.json` to the tag's version.51The shared verifier rejects a dirty worktree, a tag not resolving exactly to52`HEAD`, any version mismatch, a non-arm64 target, or a Portal manifest that53enables `demo-direct-node` by default.5455Run the non-secret policy checks before pushing the tag:5657```bash58python3 scripts/verify-portal-release.py website \59 --manifest website/portal-release.json60./scripts/tests/portal-release.sh61./scripts/verify-website.sh62```6364Create and push the tag only from the reviewed clean release commit. Dispatch65the workflows from that tag ref, not from `main`; the Portal build refuses to66attest an input tag checked out by a different workflow ref. First run67the **Sigil Release** workflow with `build-candidate`; it creates the shared68draft release and attaches the two verified Sigil candidate assets. Then69manually run **Portal release** for the same tag. Both workflows share a70concurrency group, so their release mutations cannot overlap.7172## Publication transaction7374The workflow performs this fail-closed sequence:75761. Re-verifies clean tag, exact `HEAD`, workspace versions, Tauri version, and77 demo-feature policy on Linux.782. Uses a native `macos-26` arm64 runner and the protected `main` environment.793. Builds without feature flags and requires Developer ID Application signing,80 hardened runtime, notarization, app and DMG stapling, strict code-signature81 verification, and Gatekeeper acceptance.824. Confirms the executable contains only arm64, the Apple TeamIdentifier equals83 the committed signer pin, and the DMG, digest, and JSON manifest agree with84 the source tag and commit.855. Creates GitHub/Sigstore build-provenance attestations for the exact three86 Portal assets from the protected tag-ref workflow.876. Requires the existing draft to contain exactly the two Sigil candidate88 assets, then uploads exactly:8990 - `Portal-VERSION-arm64.dmg`91 - `Portal-VERSION-arm64.dmg.sha256`92 - `Portal-VERSION-arm64.json`93947. Reads GitHub's remote asset list and requires the exact five-file combined95 pre-signature set. It deliberately leaves the release as a draft.9697The operator next attaches the offline Sigil `.minisig`. The **Sigil Release**98`promote-signed-draft` operation requires exactly all six Portal and Sigil99assets, re-verifies the Sigil signature/archive/provenance and Portal100digest/manifest against the same tag, verifies every Portal asset's attestation101against the exact Portal workflow, source tag, and source commit, mounts the102downloaded DMG on a native arm64 macOS runner, repeats the pinned Developer ID,103Gatekeeper, and stapling checks, and only then publishes the prerelease with104final notes.105106If signing, upload, or remote verification fails, the shared release remains a107draft for diagnosis. Do not replace assets on an existing tag; fix the source,108delete an unpublished failed draft after review, and create a new version/tag109when provenance changed.110111For a local Apple-credentialed rehearsal on an arm64 Mac, use an empty directory112outside the repository:113114```bash115scripts/package-macos-client.sh \116 --release-tag v0.3.0-alpha.1 \117 --output-dir /absolute/empty/release-directory118```119120## Enable the reviewed website download121122The checked-in `website/portal-release.json` starts with `available: false` and123contains no URL. After the prerelease is promoted:1241251. Download all three GitHub assets on a separate machine.1262. Verify the digest file against the DMG and inspect the JSON manifest.1273. Verify Gatekeeper again on the downloaded DMG.1284. Submit a normal reviewed change that replaces only the `macos-arm64` entry129 with `available: true` and these exact fields:130131```json132{133 "architecture": "arm64",134 "asset": "Portal-0.3.0-alpha.1-arm64.dmg",135 "available": true,136 "checksum_asset": "Portal-0.3.0-alpha.1-arm64.dmg.sha256",137 "checksum_url": "https://github.com/FelineStateMachine/goq/releases/download/v0.3.0-alpha.1/Portal-0.3.0-alpha.1-arm64.dmg.sha256",138 "download_url": "https://github.com/FelineStateMachine/goq/releases/download/v0.3.0-alpha.1/Portal-0.3.0-alpha.1-arm64.dmg",139 "manifest_asset": "Portal-0.3.0-alpha.1-arm64.json",140 "manifest_url": "https://github.com/FelineStateMachine/goq/releases/download/v0.3.0-alpha.1/Portal-0.3.0-alpha.1-arm64.json",141 "platform": "macos",142 "release_tag": "v0.3.0-alpha.1",143 "release_url": "https://github.com/FelineStateMachine/goq/releases/tag/v0.3.0-alpha.1",144 "sha256": "REPLACE_WITH_VERIFIED_LOWERCASE_SHA256",145 "verification": "developer-id+hardened-runtime+notarized+stapled+gatekeeper",146 "version": "0.3.0-alpha.1"147}148```149150`scripts/verify-website.sh` validates the manifest and JavaScript gate before151the existing protected `main` website deployment. A missing or invalid152manifest, fetch failure, or unavailable entry leaves the anchor without an153`href` and explains that no signed download is offered.154155To withdraw a download, submit a reviewed manifest change back to:156157```json158{159 "available": false,160 "reason": "Signed Portal download temporarily unavailable."161}162```163164This disables the website link without substituting another artifact. GitHub165release deletion or withdrawal and website rollback are separate reviewed166operator actions.