Skip to content

ci(release): build, sign and notarize the DMG on a hosted macOS runner - #69

Open
div0-space wants to merge 10 commits into
mainfrom
ci/release-dmg-runner
Open

ci(release): build, sign and notarize the DMG on a hosted macOS runner#69
div0-space wants to merge 10 commits into
mainfrom
ci/release-dmg-runner

Conversation

@div0-space

Copy link
Copy Markdown
Member

Why

Three clean local make release runs (PR66 and main) stop at the payload-hygiene gate: Helpers/vc-frame carries ~411 $HOME literals that --remap-path-prefix cannot reach (inlined upstream spans + rustup std). No DMG since 4.1.0 (2026-08-17) passes the gate, and that one fails it in 17 places. The operator build cannot satisfy its own gate.

A hosted macOS runner has no operator identity: /Users/runner and $GITHUB_WORKSPACE are identical on every runner. Operator decision 2026-08-22: signing and notarization may run there.

What

  • .github/workflows/release-dmg.yml — macos-15, workflow_dispatch (ref, donor refs, notarize toggle) + v* tags. Checks out vibecrafted + donors vc-frame/vc-terminal, materializes $KEYS from repository secrets, make release, re-walks the DMG through payload-hygiene-artifact.sh, scrubs keys, uploads DMG + receipts as a run artifact. No publishpublish-vibecrafted-release.sh stays the explicit operator step.
  • scripts/lib/payload-hygiene.shPAYLOAD_HYGIENE_EPHEMERAL_ROOTS: caller-declared builder roots that identify nobody. Not an allowlist of payload strings: every surviving literal is still refused. Unset = operator boundary unchanged.
  • assets/fonts/SpotMono.ttc.enc — licensed font as AES-256-CBC/PBKDF2 ciphertext (434 KB > 48 KB secret limit; plaintext must not sit in a public repo). Decrypted on the runner with VC_FONT_PASSPHRASE.

Secrets (repo-level, no collision with org)

VC_CERT_P12_B64, VC_CERT_PASSWORD, VC_SIGNING_IDENTITY, VC_SIGNING_KEY, VC_NOTARY_APPLE_ID, VC_NOTARY_TEAM_ID, VC_NOTARY_PASSWORD, VC_FONT_PASSPHRASE. Org secret VIBECRAFTED_SIGNING_KEY (2026-04-08, ALL) is not used by this workflow — to reconcile.

Verification

First hosted run dispatched from this branch with ref=main; result linked in comments. docs/RELEASE_KICKOFF.md still says "tag workflow is read-only" — to update once the run is green.

🤖 Generated with Claude Code

… a hosted macOS runner

- new workflow release-dmg.yml (macos-15, workflow_dispatch + v* tags): checks out vibecrafted + donors vc-frame/vc-terminal, materializes $KEYS from secrets, runs make release, re-walks the DMG through the hygiene gate, scrubs keys, uploads DMG + receipts as artifact (no publish)
- payload-hygiene.sh: PAYLOAD_HYGIENE_EPHEMERAL_ROOTS declares builder roots that identify nobody (/Users/runner, $GITHUB_WORKSPACE); not an allowlist of payload strings — every surviving literal is still refused; unset = operator boundary unchanged
- why: operator decision 2026-08-22 — the local operator build cannot pass its own gate (rustc leaves ~411 $HOME literals in vc-frame via inlined upstream spans); a hosted runner has no operator identity to leak, so the gate's promise holds by construction
- measured: three clean local builds of PR66/main all stop at 'names the build host in 3 place(s)' (vc-frame only)

Authored-By: claude <agents@vetcoders.io>

session_id: 67428033-45fe-4310-abb5-09b998f5da06

time: 2026-08-22T19:53:05+02:00

runtime: terminal
…s an encrypted asset

- assets/fonts/SpotMono.ttc.enc: AES-256-CBC/PBKDF2 ciphertext of the licensed font (434 KB exceeds the 48 KB Actions secret limit; plaintext must not sit in a public repo)
- release-dmg.yml: decrypts it into $KEYS/fonts with secret VC_FONT_PASSPHRASE; replaces the VC_SPOT_MONO_TTC_B64 secret
- passphrase also kept in the operator's ~/.keys for rotation

Authored-By: claude <agents@vetcoders.io>

session_id: 67428033-45fe-4310-abb5-09b998f5da06

time: 2026-08-22T20:09:33+02:00

runtime: terminal
Copilot AI lite review requested due to automatic review settings August 22, 2026 18:13

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Code review skipped — your organization has reached its monthly code review spending cap.

An organization admin can view or raise the cap at claude.ai/admin-settings/claude-code. The cap resets at the start of the next billing period.

Once the cap resets or is raised, reopen this pull request to trigger a review.

…rst hosted run

- workflow_dispatch is only registered once the file exists on main; a push trigger on this branch lets PR #69 prove the runner path before merge
- remove before/at merge

Authored-By: claude <agents@vetcoders.io>

session_id: 67428033-45fe-4310-abb5-09b998f5da06

time: 2026-08-22T20:14:17+02:00

runtime: terminal

Copilot AI 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.

Pull request overview

This PR adds a GitHub Actions workflow to build/sign/(optionally) notarize Vibecrafted DMGs on a hosted macOS runner, and updates the payload hygiene gate to optionally ignore caller-declared “ephemeral” runner roots that don’t identify a specific builder.

Changes:

  • Add .github/workflows/release-dmg.yml to build on macos-15, materialize signing material from repo secrets, run make release, re-run the payload hygiene gate, and upload artifacts.
  • Extend scripts/lib/payload-hygiene.sh with PAYLOAD_HYGIENE_EPHEMERAL_ROOTS support so CI can declare non-identifying roots (e.g., hosted runner home/workspace) to skip in the literal set.

Summary

Primary functional risk is around the workflow’s notarize toggle: the current job structure doesn’t align with what the release script actually produces in --no-notarize mode.

Files Changed

  • scripts/lib/payload-hygiene.sh
  • .github/workflows/release-dmg.yml

Verification Performed

  • None (review-only).

Verification Not Performed

  • No hosted workflow run validation.
  • No local make release / make payload-hygiene execution.

Risks Or Follow-Up

  • The workflow_dispatch notarize: false mode currently won’t work as intended (missing outputs + unconditional secrets), and needs either a separate build path or a release-script behavior change.

Reviewed changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated 1 comment.

File Description
scripts/lib/payload-hygiene.sh Adds an opt-in mechanism to treat hosted-runner roots as “ephemeral” and exclude them from forbidden literal roots.
.github/workflows/release-dmg.yml New workflow to build/sign/notarize DMGs on hosted macOS and upload build artifacts.
Suppressed comments (1)

.github/workflows/release-dmg.yml:142

  • When workflow_dispatch inputs.notarize=false, RELEASE_FLAGS becomes "--no-notarize" but the job still runs make release and then asserts files that are only produced in the notarization path (dist/*.dmg.sha256 and dist/release-output.json). In scripts/build-vibecrafted-release.sh, MODE=dmg exits right after create_dmg (no checksum, no release-output tuple), so this step (and the upload paths below) will fail for sign-only builds.
            -pass env:VC_FONT_PASSPHRASE
          file "$KEYS/Certificates.p12" "$KEYS/fonts/SpotMono.ttc"

      - name: Build, sign, notarize
        working-directory: vibecrafted
        env:
          RELEASE_FLAGS: ${{ (github.event_name == 'workflow_dispatch' && inputs.notarize == false) && '--no-notarize' || '' }}
        run: |
          set -euo pipefail

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +118 to +128
for v in VC_CERT_P12_B64 VC_CERT_PASSWORD VC_SIGNING_IDENTITY VC_SIGNING_KEY VC_NOTARY_APPLE_ID VC_NOTARY_TEAM_ID VC_NOTARY_PASSWORD VC_FONT_PASSPHRASE; do
[[ -n "${!v:-}" ]] || { echo "::error::missing secret $v"; exit 1; }
done
umask 077
mkdir -p "$KEYS/fonts"
printf '%s' "$VC_CERT_P12_B64" | base64 --decode > "$KEYS/Certificates.p12"
printf '%s\n' "$VC_CERT_PASSWORD" > "$KEYS/cert_password.txt"
printf '%s\n' "$VC_SIGNING_IDENTITY" > "$KEYS/signing-identity.txt"
printf '%s\n' "$VC_SIGNING_KEY" > "$KEYS/vibecrafted-signing.key"
printf 'NOTARY_APPLE_ID=%s\nNOTARY_TEAM_ID=%s\nNOTARY_PASSWORD=%s\n' \
"$VC_NOTARY_APPLE_ID" "$VC_NOTARY_TEAM_ID" "$VC_NOTARY_PASSWORD" > "$KEYS/.notary.env"
Copilot AI review requested due to automatic review settings August 22, 2026 18:19

Copilot AI 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.

Pull request overview

Copilot reviewed 2 out of 3 changed files in this pull request and generated 1 comment.

Suppressed comments (2)

Previously missed (1) — in code that hasn't changed since the last review.

scripts/lib/payload-hygiene.sh:80

  • payload_hygiene_is_ephemeral uses Bash pattern matching ([[ "$path" == "$root"/* ]]), so any glob metacharacters in an env-provided root (e.g. *, ?, [) could unintentionally match more than the declared prefix and skip additional forbidden literals. Since this function is meant to treat roots as literal path prefixes, the check should avoid pattern semantics.
  while IFS= read -r root; do
    root="${root%/}"
    [[ -n "$root" && "$root" != "/" ]] || continue
    [[ "$path" == "$root" || "$path" == "$root"/* ]] && return 0
  done <<< "$PAYLOAD_HYGIENE_EPHEMERAL_ROOTS"

.github/workflows/release-dmg.yml:114

  • The workflow advertises notarize: false as “sign only”, but the “Materialize signing keys from secrets” step still requires the notary secrets and always writes $KEYS/.notary.env. This makes sign-only runs fail unnecessarily (and forces storing notary creds even when not used).
        env:
          VC_CERT_P12_B64: ${{ secrets.VC_CERT_P12_B64 }}
          VC_CERT_PASSWORD: ${{ secrets.VC_CERT_PASSWORD }}
          VC_SIGNING_IDENTITY: ${{ secrets.VC_SIGNING_IDENTITY }}
          VC_SIGNING_KEY: ${{ secrets.VC_SIGNING_KEY }}

set -euo pipefail
dmg="$(ls dist/Vibecrafted_*.dmg | sort | tail -1)"
bash scripts/payload-hygiene-artifact.sh "$dmg"
spctl -a -t open --context context:primary-signature -v "$dmg" || true
- vetcoders/vc-frame main (7ab84069, 2026-08-14) lacks the release-binary target and the baked-manifest fix; develop (017e3839) is 13 ahead and is what the operator's local donor builds from
- first hosted run 32590319025 proved toolchain, keys and vc-terminal; it stopped exactly on 'No rule to make target release-binary'

Authored-By: claude <agents@vetcoders.io>

session_id: 67428033-45fe-4310-abb5-09b998f5da06

time: 2026-08-22T20:29:46+02:00

runtime: terminal
Copilot AI review requested due to automatic review settings August 22, 2026 18:33

Copilot AI 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.

Pull request overview

Copilot reviewed 2 out of 3 changed files in this pull request and generated 2 comments.

Suppressed comments (3)

Previously missed (1) — in code that hasn't changed since the last review.

scripts/lib/payload-hygiene.sh:75

  • New behavior for PAYLOAD_HYGIENE_EPHEMERAL_ROOTS (skipping caller-declared CI runner roots) isn’t covered by tests. There are existing contract tests for payload_hygiene_literals in tests/tui/test_payload_hygiene.py, but none assert that ephemeral roots are filtered while non-ephemeral roots still fail the gate when the env var is set.
payload_hygiene_is_ephemeral() {
  local path="${1%/}" root
  [[ -n "${PAYLOAD_HYGIENE_EPHEMERAL_ROOTS:-}" ]] || return 1
  while IFS= read -r root; do
    root="${root%/}"

.github/workflows/release-dmg.yml:153

  • This spctl assessment is currently ignored (|| true), which means the workflow can upload a DMG even if Gatekeeper assessment fails. Since this job is meant to produce a canonical release artifact, the assessment should fail the run.
          spctl -a -t open --context context:primary-signature -v "$dmg" || true

.github/workflows/release-dmg.yml:123

  • The workflow supports notarize: false (sign-only), but this step still requires the notary secrets and always writes .notary.env. That makes sign-only dispatch runs fail even though notarization is disabled.
          for v in VC_CERT_P12_B64 VC_CERT_PASSWORD VC_SIGNING_IDENTITY VC_SIGNING_KEY VC_NOTARY_APPLE_ID VC_NOTARY_TEAM_ID VC_NOTARY_PASSWORD VC_FONT_PASSPHRASE; do
            [[ -n "${!v:-}" ]] || { echo "::error::missing secret $v"; exit 1; }
          done

Comment on lines +95 to +97
set -euo pipefail
sudo xcode-select -s "$(ls -d /Applications/Xcode*.app | sort -V | tail -1)/Contents/Developer"
xcodebuild -version
done
umask 077
mkdir -p "$KEYS/fonts"
printf '%s' "$VC_CERT_P12_B64" | base64 --decode > "$KEYS/Certificates.p12"
- run 32591086444 reached xtask build and panicked in prost-build: no protoc on macos-15; brew protobuf provides it

Authored-By: claude <agents@vetcoders.io>

session_id: 67428033-45fe-4310-abb5-09b998f5da06

time: 2026-08-22T20:41:58+02:00

runtime: terminal
Copilot AI review requested due to automatic review settings August 22, 2026 18:45

Copilot AI 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.

Pull request overview

Copilot reviewed 2 out of 3 changed files in this pull request and generated 1 comment.

Suppressed comments (3)

Previously missed (1) — in code that hasn't changed since the last review.

.github/workflows/release-dmg.yml:144

  • The notarize input is described as “false = sign only”, but in that mode RELEASE_FLAGS adds --no-notarize while still running make release. The underlying builder exits early in --no-notarize mode (no release tuple, no .sha256), so later steps that expect dist/*.dmg.sha256 and dist/release-output.json will fail (and the upload list includes those files unconditionally). This likely needs an explicit branch: run make release only when notarizing, otherwise run the appropriate make dmg/make dmg-signed target and adjust the follow-up checks and uploaded artifacts accordingly.
          RELEASE_FLAGS: ${{ (github.event_name == 'workflow_dispatch' && inputs.notarize == false) && '--no-notarize' || '' }}
        run: |
          set -euo pipefail
          rustup show active-toolchain || true
          make release RELEASE_FLAGS="$RELEASE_FLAGS" KEYS="$KEYS"

.github/workflows/release-dmg.yml:153

  • This spctl assessment is currently allowed to fail (|| true), which can result in uploading a DMG that Gatekeeper would reject (especially in a future “sign only” path). If this is meant as a safety check, it should fail the job on error (or be conditioned on an explicit “informational only” mode).
          dmg="$(ls dist/Vibecrafted_*.dmg | sort | tail -1)"
          bash scripts/payload-hygiene-artifact.sh "$dmg"
          spctl -a -t open --context context:primary-signature -v "$dmg" || true

.github/workflows/release-dmg.yml:123

  • notarize: false is intended to allow a sign-only run, but this step hard-requires the notary secrets (VC_NOTARY_*) and always writes $KEYS/.notary.env. This makes the sign-only option unusable (even before considering the downstream artifact expectations). Consider only requiring/materializing notary credentials when notarization is enabled.
          set -euo pipefail
          for v in VC_CERT_P12_B64 VC_CERT_PASSWORD VC_SIGNING_IDENTITY VC_SIGNING_KEY VC_NOTARY_APPLE_ID VC_NOTARY_TEAM_ID VC_NOTARY_PASSWORD VC_FONT_PASSPHRASE; do
            [[ -n "${!v:-}" ]] || { echo "::error::missing secret $v"; exit 1; }
          done

Comment on lines +71 to +75
with:
path: vibecrafted
ref: ${{ inputs.ref || github.ref }}
fetch-depth: 0
persist-credentials: false
…wn for the server shell

- run 32591700337 built vc-frame and vc-start on the runner; build-server-release stopped on missing cargo-leptos
- pinned to 0.3.7, the version the operator's machine releases with

Authored-By: claude <agents@vetcoders.io>

session_id: 67428033-45fe-4310-abb5-09b998f5da06

time: 2026-08-22T21:08:29+02:00

runtime: terminal
Copilot AI review requested due to automatic review settings August 22, 2026 19:12

Copilot AI 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.

Pull request overview

Copilot reviewed 2 out of 3 changed files in this pull request and generated no new comments.

Suppressed comments (5)

Previously missed (2) — in code that hasn't changed since the last review.

.github/workflows/release-dmg.yml:50

  • The workflow overrides permissions to only contents: read, but actions/upload-artifact requires a token that can write workflow run artifacts. With the current permissions block, the upload step is likely to fail with "Resource not accessible by integration" / 403.

This issue also appears in the following locations of the same file:

  • line 139
  • line 139
permissions:
  contents: read

scripts/lib/payload-hygiene.sh:79

  • payload_hygiene_is_ephemeral changes the gate’s literal set based on a new environment variable, but there’s no corresponding contract test coverage. The repo already has payload-hygiene contract tests (e.g. tests/tui/test_payload_hygiene.py with run_library(...)), so adding a test that verifies payload_hygiene_literals excludes candidates under PAYLOAD_HYGIENE_EPHEMERAL_ROOTS would help prevent regressions.
payload_hygiene_is_ephemeral() {
  local path="${1%/}" root
  [[ -n "${PAYLOAD_HYGIENE_EPHEMERAL_ROOTS:-}" ]] || return 1
  while IFS= read -r root; do
    root="${root%/}"
    [[ -n "$root" && "$root" != "/" ]] || continue
    [[ "$path" == "$root" || "$path" == "$root"/* ]] && return 0
  done <<< "$PAYLOAD_HYGIENE_EPHEMERAL_ROOTS"
  return 1

.github/workflows/release-dmg.yml:125

  • The "notarize" input isn’t honored in the secrets materialization step: even when inputs.notarize is false (sign-only), this step still requires the notary secrets and always writes $KEYS/.notary.env. scripts/build-vibecrafted-release.sh only needs notary credentials when notarizing, so this currently prevents running a sign-only build without also configuring the notary secrets.
        run: |
          set -euo pipefail
          for v in VC_CERT_P12_B64 VC_CERT_PASSWORD VC_SIGNING_IDENTITY VC_SIGNING_KEY VC_NOTARY_APPLE_ID VC_NOTARY_TEAM_ID VC_NOTARY_PASSWORD VC_FONT_PASSPHRASE; do
            [[ -n "${!v:-}" ]] || { echo "::error::missing secret $v"; exit 1; }
          done

.github/workflows/release-dmg.yml:146

  • This workflow currently doesn’t enable --snapshot-donors. The release builder warns that without it vc-frame’s git-tracked WASM plugin blobs ship as-is (potentially compiled elsewhere with embedded host paths), and only --snapshot-donors rebuilds them under the release remaps (scripts/build-vibecrafted-release.sh around the vc-frame plugin rebuild). For a hosted release workflow, it’s safer to always build with --snapshot-donors.
      - name: Build, sign, notarize
        working-directory: vibecrafted
        env:
          RELEASE_FLAGS: ${{ (github.event_name == 'workflow_dispatch' && inputs.notarize == false) && '--no-notarize' || '' }}
        run: |
          set -euo pipefail
          rustup show active-toolchain || true
          make release RELEASE_FLAGS="$RELEASE_FLAGS" KEYS="$KEYS"

.github/workflows/release-dmg.yml:147

  • When inputs.notarize is false the workflow passes --no-notarize into make release, but the builder’s --no-notarize mode exits after create_dmg and does not emit dist/*.dmg.sha256 nor dist/release-output.json / .sig (see scripts/build-vibecrafted-release.sh: it exits early for MODE == dmg). This means the subsequent ls and the artifact upload paths will fail in the sign-only case.
      - name: Build, sign, notarize
        working-directory: vibecrafted
        env:
          RELEASE_FLAGS: ${{ (github.event_name == 'workflow_dispatch' && inputs.notarize == false) && '--no-notarize' || '' }}
        run: |
          set -euo pipefail
          rustup show active-toolchain || true
          make release RELEASE_FLAGS="$RELEASE_FLAGS" KEYS="$KEYS"
          ls -la dist/*.dmg dist/*.dmg.sha256 dist/release-output.json

…not name the operator

- run 32593008654 assembled the whole app on macos-15 and stopped at the gate with 'no --forbid literal given': every build-host root was ephemeral, so the scanner refused an empty proof — correctly
- payload-hygiene.sh: PAYLOAD_HYGIENE_EXTRA_LITERALS appends caller literals; the workflow passes the operator's home and workshop, which is exactly what a signed artifact built elsewhere must not carry
- these two paths already appear in the public git history; forbidding them leaks nothing

Authored-By: claude <agents@vetcoders.io>

session_id: 67428033-45fe-4310-abb5-09b998f5da06

time: 2026-08-22T21:48:37+02:00

runtime: terminal
Copilot AI review requested due to automatic review settings August 22, 2026 19:52

Copilot AI 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.

Pull request overview

Copilot reviewed 2 out of 3 changed files in this pull request and generated 1 comment.

Suppressed comments (3)

Previously missed (1) — in code that hasn't changed since the last review.

.github/workflows/release-dmg.yml:151

  • The notarize toggle is currently inconsistent with the build + artifact expectations: when RELEASE_FLAGS includes --no-notarize, scripts/build-vibecrafted-release.sh exits after create_dmg and does not write dist/*.dmg.sha256 or dist/release-output.json(.sig). This makes the ls -la ... dist/release-output.json line and the upload path: list fail for sign-only runs.
          RELEASE_FLAGS: ${{ (github.event_name == 'workflow_dispatch' && inputs.notarize == false) && '--no-notarize' || '' }}
        run: |
          set -euo pipefail
          rustup show active-toolchain || true
          make release RELEASE_FLAGS="$RELEASE_FLAGS" KEYS="$KEYS"

.github/workflows/release-dmg.yml:142

  • base64 --decode is not portable on macOS runners (BSD base64 typically lacks --decode), and the step currently always requires/writes notary credentials even when inputs.notarize is false. This makes the workflow brittle and prevents running a sign-only build without Apple notary secrets once that path is wired up.
          set -euo pipefail
          for v in VC_CERT_P12_B64 VC_CERT_PASSWORD VC_SIGNING_IDENTITY VC_SIGNING_KEY VC_NOTARY_APPLE_ID VC_NOTARY_TEAM_ID VC_NOTARY_PASSWORD VC_FONT_PASSPHRASE; do
            [[ -n "${!v:-}" ]] || { echo "::error::missing secret $v"; exit 1; }
          done
          umask 077
          mkdir -p "$KEYS/fonts"
          printf '%s' "$VC_CERT_P12_B64" | base64 --decode > "$KEYS/Certificates.p12"
          printf '%s\n' "$VC_CERT_PASSWORD" > "$KEYS/cert_password.txt"
          printf '%s\n' "$VC_SIGNING_IDENTITY" > "$KEYS/signing-identity.txt"
          printf '%s\n' "$VC_SIGNING_KEY" > "$KEYS/vibecrafted-signing.key"
          printf 'NOTARY_APPLE_ID=%s\nNOTARY_TEAM_ID=%s\nNOTARY_PASSWORD=%s\n' \
            "$VC_NOTARY_APPLE_ID" "$VC_NOTARY_TEAM_ID" "$VC_NOTARY_PASSWORD" > "$KEYS/.notary.env"
          openssl enc -d -aes-256-cbc -pbkdf2 -iter 200000 \
            -in vibecrafted/assets/fonts/SpotMono.ttc.enc -out "$KEYS/fonts/SpotMono.ttc" \
            -pass env:VC_FONT_PASSPHRASE
          file "$KEYS/Certificates.p12" "$KEYS/fonts/SpotMono.ttc"

.github/workflows/release-dmg.yml:160

  • spctl assessment is always ignored via || true, so this step won't fail even if the DMG fails Gatekeeper assessment. If this is meant as a verification gate, it should be conditional (e.g. enforce for notarized builds) rather than unconditionally silenced.
          spctl -a -t open --context context:primary-signature -v "$dmg" || true

Comment on lines +71 to +75
payload_hygiene_is_ephemeral() {
local path="${1%/}" root
[[ -n "${PAYLOAD_HYGIENE_EPHEMERAL_ROOTS:-}" ]] || return 1
while IFS= read -r root; do
root="${root%/}"
…pshot-donors)

- run #5 assembled Vibecrafted.app but the hygiene gate refused it:
  411x /Users/polyversai + 13x /Volumes/vc-workspace in Contents/Helpers/vc-frame
- source: vc-frame's git-tracked zellij-utils/assets/plugins/*.wasm, built on
  the operator machine and embedded via include_bytes!; no runner flag can
  rewrite bytes compiled elsewhere
- the builder already recompiles them under its remaps behind --snapshot-donors;
  on a hosted runner the donors are throwaway checkouts, so it is always on

Authored-By: claude <agents@vetcoders.io>

session_id: 67428033-45fe-4310-abb5-09b998f5da06

time: 2026-08-22T22:29:45+02:00

runtime: terminal
Copilot AI review requested due to automatic review settings August 22, 2026 20:33

Copilot AI 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.

Pull request overview

Copilot reviewed 2 out of 3 changed files in this pull request and generated no new comments.

Suppressed comments (2)

.github/workflows/release-dmg.yml:130

  • The step documents notarize: false = sign only, but the secret validation loop still requires the notarytool credentials even when notarization is disabled, preventing sign-only runs when those secrets are intentionally unset.
          set -euo pipefail
          for v in VC_CERT_P12_B64 VC_CERT_PASSWORD VC_SIGNING_IDENTITY VC_SIGNING_KEY VC_NOTARY_APPLE_ID VC_NOTARY_TEAM_ID VC_NOTARY_PASSWORD VC_FONT_PASSPHRASE; do
            [[ -n "${!v:-}" ]] || { echo "::error::missing secret $v"; exit 1; }
          done

.github/workflows/release-dmg.yml:134

  • base64 --decode is a GNU option and is not supported by the default macOS /usr/bin/base64 on GitHub-hosted runners; this will likely fail to decode the .p12.
          umask 077
          mkdir -p "$KEYS/fonts"
          printf '%s' "$VC_CERT_P12_B64" | base64 --decode > "$KEYS/Certificates.p12"
          printf '%s\n' "$VC_CERT_PASSWORD" > "$KEYS/cert_password.txt"

…the search list

- run #6 passed the hygiene gate (0 literals) and died in codesign:
  "The specified item could not be found in the keychain" although
  find-identity saw the Developer ID in the ephemeral keychain
- codesign chains the leaf through the search list; the hosted image also
  lacks Apple's Developer ID G2 intermediate
- builder: KEYCHAIN_SESSION_REGISTER_SEARCH_LIST now honours
  VIBECRAFTED_KEYCHAIN_SEARCH_LIST (default 0 — the operator machine keeps
  its Codescribe-safe behaviour)
- workflow: sets it to 1 and imports DeveloperIDG2CA.cer into the runner's
  login keychain

Authored-By: claude <agents@vetcoders.io>

session_id: 67428033-45fe-4310-abb5-09b998f5da06

time: 2026-08-22T23:31:15+02:00

runtime: terminal
Copilot AI review requested due to automatic review settings August 22, 2026 21:35

Copilot AI 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.

Pull request overview

Copilot reviewed 3 out of 4 changed files in this pull request and generated 1 comment.

Suppressed comments (2)

Previously missed (1) — in code that hasn't changed since the last review.

scripts/lib/payload-hygiene.sh:80

  • payload_hygiene_is_ephemeral uses Bash pattern matching for prefix checks ([[ "$path" == "$root"/* ]]). If a caller accidentally provides a root containing glob metacharacters (e.g. [ or *) or an overly generic root like /Users, the comparison can match more than intended and silently drop critical forbid literals, weakening the hygiene gate.
  while IFS= read -r root; do
    root="${root%/}"
    [[ -n "$root" && "$root" != "/" ]] || continue
    [[ "$path" == "$root" || "$path" == "$root"/* ]] && return 0
  done <<< "$PAYLOAD_HYGIENE_EPHEMERAL_ROOTS"

scripts/lib/payload-hygiene.sh:104

  • This change adds two new environment-driven behaviors (PAYLOAD_HYGIENE_EPHEMERAL_ROOTS and PAYLOAD_HYGIENE_EXTRA_LITERALS) that directly affect what literals are forbidden. There are existing contract tests for payload_hygiene_literals in tests/tui/test_payload_hygiene.py, but none cover the new env vars, so regressions here could weaken the gate without being caught.
  # PAYLOAD_HYGIENE_EXTRA_LITERALS — newline-separated literals a caller adds on
  # top of the build-host set. A hosted runner declares its own roots ephemeral
  # and then has nothing of its own to forbid; what it must still prove is that
  # the payload does not name the OPERATOR whose keys sign it. The operator's
  # home and workshop are those literals.

Comment on lines +149 to +151
curl -fsSL -o DeveloperIDG2CA.cer https://www.apple.com/certificateauthority/DeveloperIDG2CA.cer
security import DeveloperIDG2CA.cer -k "$HOME/Library/Keychains/login.keychain-db" -T /usr/bin/codesign
rm -f DeveloperIDG2CA.cer
…r, document the hosted path

- run 32600012658 produced Vibecrafted_4.2.4-20260822-8d041616.dmg:
  notarized, stapled, accepted by spctl, clean through the hygiene gate on
  the operator machine
- the push trigger on ci/release-dmg-runner was only for bring-up;
  tags v* and workflow_dispatch remain
- RELEASE_KICKOFF: the root workflow is no longer read-only; hosted runner vs
  operator machine, publication stays an explicit step

Authored-By: claude <agents@vetcoders.io>
session_id: 67428033-45fe-4310-abb5-09b998f5da06
time: 2026-08-23T00:19:26+02:00
runtime: claude-code
Copilot AI review requested due to automatic review settings August 22, 2026 22:21

Copilot AI 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.

Pull request overview

Copilot reviewed 4 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (2)

Previously missed (1) — in code that hasn't changed since the last review.

.github/workflows/release-dmg.yml:177

  • The “Scrub keys” step uses rm -rf "$KEYS" without any guard. A defensive check helps avoid accidental deletion if the variable ever becomes empty or / due to a future edit or env change.
      - name: Scrub keys
        if: always()
        run: rm -rf "$KEYS"

.github/workflows/release-dmg.yml:127

  • In the “Materialize signing keys from secrets” step, (1) the script always requires notary secrets even when inputs.notarize is false (sign-only mode), and (2) base64 --decode is GNU-specific and is likely to fail on the hosted macOS runner’s BSD base64. Consider gating the notary secrets and .notary.env creation on a computed NOTARIZE flag, and use base64 -D on macOS.
          for v in VC_CERT_P12_B64 VC_CERT_PASSWORD VC_SIGNING_IDENTITY VC_SIGNING_KEY VC_NOTARY_APPLE_ID VC_NOTARY_TEAM_ID VC_NOTARY_PASSWORD VC_FONT_PASSPHRASE; do
            [[ -n "${!v:-}" ]] || { echo "::error::missing secret $v"; exit 1; }
          done

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