ci(cli): surface notary log on non-Accepted; record signing validation

macOS validation (Developer ID D77MCV9NZ7) confirmed the hardened-runtime
entitlements are correct and load-bearing: amy init derives a secp256k1 key
cleanly, and dropping disable-library-validation reproduces the runtime
dlopen Team-ID failure. The one unverified gap is whether Apple's notary
service accepts the unsigned Mach-O dylibs embedded inside lib/*.jar
(secp256k1/jna/sqlite/skiko), which it inspects recursively.

- create-release.yml: the notarize step now submits with --output-format json,
  and on any non-Accepted status dumps `notarytool log` (per-file issues) and
  fails — so the first real run names the offending files instead of failing
  opaquely. No speculative in-jar signing yet; gather the log first.
- BUILDING.md: record the validation result, the embedded-jar-native risk, the
  one-run way to decide it (workflow_dispatch dry_run with MAC_* secrets), and
  the staged fixes (sign-in-jar and/or strip the skiko/Compose leak). Note the
  desktop app shares the same jars and needs its own dry-run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015sso31DfSF9B6EFCVkEqWD
This commit is contained in:
Claude
2026-06-18 00:49:26 +00:00
parent d10e6c4d81
commit 3c422b1824
2 changed files with 47 additions and 4 deletions
+25 -4
View File
@@ -323,10 +323,31 @@ jobs:
done < <(find "$IMG" -type f)
codesign --verify --strict --verbose=2 "$IMG/runtime/bin/java"
# Notarize: zip the signed image, submit, wait for Apple's verdict.
ditto -c -k --keepParent "$IMG" "$RUNNER_TEMP/amy-notarize.zip"
xcrun notarytool submit "$RUNNER_TEMP/amy-notarize.zip" \
--apple-id "$NOTARY_APPLE_ID" --password "$NOTARY_PASSWORD" \
--team-id "$NOTARY_TEAM_ID" --wait
# The notary service recursively inspects the lib/*.jar files, so any
# unsigned Mach-O embedded in them (secp256k1/jna/sqlite/skiko natives)
# can come back Invalid. Surface the per-file log so the first real run
# is diagnostic rather than a bare failure.
ZIP="$RUNNER_TEMP/amy-notarize.zip"
OUT="$RUNNER_TEMP/notary-submit.json"
ditto -c -k --keepParent "$IMG" "$ZIP"
if ! xcrun notarytool submit "$ZIP" \
--apple-id "$NOTARY_APPLE_ID" --password "$NOTARY_PASSWORD" \
--team-id "$NOTARY_TEAM_ID" --wait --output-format json > "$OUT"; then
echo "::warning::notarytool submit exited non-zero"
fi
cat "$OUT"
STATUS="$(jq -r '.status // "Unknown"' "$OUT" 2>/dev/null || echo Unknown)"
SUBMISSION_ID="$(jq -r '.id // empty' "$OUT" 2>/dev/null || true)"
if [ "$STATUS" != "Accepted" ]; then
echo "::error::Notarization status: $STATUS"
if [ -n "$SUBMISSION_ID" ]; then
echo "----- notary log -----"
xcrun notarytool log "$SUBMISSION_ID" \
--apple-id "$NOTARY_APPLE_ID" --password "$NOTARY_PASSWORD" \
--team-id "$NOTARY_TEAM_ID" || true
fi
exit 1
fi
# jpackage pins libicu to the build host's version (libicu74 on
# ubuntu-24.04). Rewrite the .deb so it installs across Debian/Ubuntu.
+22
View File
@@ -281,6 +281,28 @@ Gatekeeper verifies notarization **online** on first run — fine for a CLI.
Note the Homebrew-core jvm bundle (`amy-<version>-jvm.tar.gz`) is **not** signed:
Homebrew removes the quarantine attribute on its own downloads.
> **Validated (Developer ID `D77MCV9NZ7`):** signing every Mach-O in the bundled
> JRE with hardened runtime + `amy.entitlements` lets `amy init` derive a key via
> secp256k1 with no library-validation crash. Dropping `disable-library-validation`
> reproduces `UnsatisfiedLinkError: … different Team IDs` on the runtime-extracted
> `libsecp256k1-jni.dylib` — so that entitlement is load-bearing, not decorative.
>
> **Open risk — embedded jar natives.** The notary service unpacks `lib/*.jar`
> recursively and checks every Mach-O for a signature + hardened runtime. Our
> sign loop only touches loose files, so 9 unsigned natives ride along inside
> jars on a macOS build: `secp256k1` (1, required at runtime), `jna` (2),
> `sqlite` (2), and `skiko` (4, dead weight — Compose UI the CLI never renders).
> Whether `notarytool` returns `Accepted` or `Invalid` on these is **unverified**
> (the local validation had no notary creds). **Decide it with one run:** set the
> six `MAC_*` secrets and trigger `create-release.yml` via `workflow_dispatch`
> with `dry_run=true` — the sign+notarize step runs regardless of `dry_run` and
> now prints the per-file notary log on a non-`Accepted` verdict. If it comes
> back `Invalid`, the fix is to codesign the dylibs *inside* those jars before
> zipping (and/or strip the unused `skiko`/Compose jars from the CLI image — the
> `:commons` core/ui split the size budget already flags). The **desktop** app
> bundles the same jars through Compose/jpackage notarization, so run a desktop
> dry-run too; its in-jar handling differs and is likewise unverified.
Generating the values:
```bash