Summary
Ship the notarized .pkg + .dmg. This is orthogonal to parity and can run in parallel with every feature issue. The pipeline was audited end-to-end on 2026-09-05 and is sound; what remains is owner-held credentials, two open decisions, and the run itself.
Command-level detail lives in App-Dmg-Pkg-Deployment.md.
Two decisions the owner must make before anything is cut
1. The version number is inconsistent across three places
| Where |
Value |
MARKETING_VERSION (build settings) |
0.1.0 |
releases/appcast.xml |
0.0.1 — "Version 0.0.1 Alpha", enclosure InterlinedList-0.0.1-alpha.pkg |
| The tag step in the checklist |
v1.0.0 |
Pick one. ⚠️ The appcast enclosure filename must match what the script actually produces or Sparkle will 404 on every update check — a silent failure that only surfaces later.
2. The appcast URL is documented in two places
Info.plist's SUFeedURL points at https://interlinedlist.com/appcast.xml; the comment block inside releases/appcast.xml documents the feed as living at …/downloads/apple/appcast.xml. Harmless today, but reconcile so the served path and the polled path cannot drift.
Already fixed — do not re-litigate
notarize-and-package.sh derived the release version by reading CFBundleShortVersionString out of App/Resources/Info.plist, which stores the unexpanded $(MARKETING_VERSION). Every artifact would have been named InterlinedList-$(MARKETING_VERSION).pkg. It now resolves the value via xcodebuild -showBuildSettings and hard-fails with an actionable message if it is empty or still contains $(. Verified.
Also verified sound in the same audit: all 8 scripts/*.sh parse (bash -n), ExportOptions.plist is developer-id with automatic signing, the sync-agent embed paths and both entitlements files exist, the Sparkle path is fully wired (SparkleController + UpdatesMenuCommands, SUFeedURL set), and the appcast's minimumSystemVersion 15.0 matches MACOSX_DEPLOYMENT_TARGET.
Owner-held work (secrets and credentials — not delegable)
Sparkle keys
Developer ID credentials
The release run
Backend prerequisite — gates a feature that already ships client-side
Pre-flight
Acceptance criteria
- One version number, used consistently by the build, the appcast, and the tag.
- A notarized
.pkg installs on a clean machine and Sparkle finds the appcast.
Notes
work-consolidation.md §3a. The Document Sync Agent's on-device validation is a separate issue and is part of the same ship gate.
Summary
Ship the notarized
.pkg+.dmg. This is orthogonal to parity and can run in parallel with every feature issue. The pipeline was audited end-to-end on 2026-09-05 and is sound; what remains is owner-held credentials, two open decisions, and the run itself.Command-level detail lives in
App-Dmg-Pkg-Deployment.md.Two decisions the owner must make before anything is cut
1. The version number is inconsistent across three places
MARKETING_VERSION(build settings)releases/appcast.xmlInterlinedList-0.0.1-alpha.pkgPick one.⚠️ The appcast enclosure filename must match what the script actually produces or Sparkle will 404 on every update check — a silent failure that only surfaces later.
2. The appcast URL is documented in two places
Info.plist'sSUFeedURLpoints athttps://interlinedlist.com/appcast.xml; the comment block insidereleases/appcast.xmldocuments the feed as living at…/downloads/apple/appcast.xml. Harmless today, but reconcile so the served path and the polled path cannot drift.Already fixed — do not re-litigate
notarize-and-package.shderived the release version by readingCFBundleShortVersionStringout ofApp/Resources/Info.plist, which stores the unexpanded$(MARKETING_VERSION). Every artifact would have been namedInterlinedList-$(MARKETING_VERSION).pkg. It now resolves the value viaxcodebuild -showBuildSettingsand hard-fails with an actionable message if it is empty or still contains$(. Verified.Also verified sound in the same audit: all 8
scripts/*.shparse (bash -n),ExportOptions.plistisdeveloper-idwith automatic signing, the sync-agent embed paths and both entitlements files exist, the Sparkle path is fully wired (SparkleController+UpdatesMenuCommands,SUFeedURLset), and the appcast'sminimumSystemVersion15.0 matchesMACOSX_DEPLOYMENT_TARGET.Owner-held work (secrets and credentials — not delegable)
Sparkle keys
./bin/generate_keys— store the private key in a password manager, never commit itApp/Resources/Info.plist→SUPublicEDKeyString(currentlyTODO_REPLACE_WITH_ED25519_PUBLIC_KEY)SUFeedURLandSUPublicEDKeyStringresolve against the published appcastDeveloper ID credentials
scripts/store-notarization-profile.sh(Apple ID, Team IDBJA9558E4B, app-specific password) → creates theNotarizationProfileKeychain itemApp-Dmg-Pkg-Deployment.md§1b):CERTIFICATES_P12,CERTIFICATES_P12_PASSWORD,CODESIGN_IDENTITY,INSTALLER_IDENTITY,NOTARIZATION_PASSWORD..envCODESIGN_IDENTITY/INSTALLER_IDENTITYvalues are placeholders — replace with real Developer ID certs.The release run
scripts/notarize-and-package.sh(env perApp-Dmg-Pkg-Deployment.md§3b) →.pkg+.dmginreleases/. This step also builds and embeds the Document Sync Agent../bin/sign_update releases/InterlinedList-<version>.pkg; copy theedSignatureand byte count intoreleases/appcast.xml, replacingTODO_REPLACE_WITH_SIGNATUREandlength="0".pkg,.dmg,.sha256tohttps://interlinedlist.com/downloads/apple/releases/appcast.xmlto the agreed feed URL (needs distribution infra on interlinedlist.com)git tag v<version> && git push origin v<version>→ triggersrelease.yml(draft GitHub release). Owner-driven, per project convention.Backend prerequisite — gates a feature that already ships client-side
npm run db:migrate:deploy. Two are pending:add_moderation_tables,add_moderation_versioning_sessions. Moderation ships in the client today and depends on these.GET /api/messagesunauthenticated-behaviour decision (tracked with the backend asks).Pre-flight
mainis behinddev— catch it up before cutting.Acceptance criteria
.pkginstalls on a clean machine and Sparkle finds the appcast.Notes
work-consolidation.md§3a. The Document Sync Agent's on-device validation is a separate issue and is part of the same ship gate.