PrintToVeraeTimeStamp/docs/COMMERCIAL-INSTALLER-BACKLOG.md
George Lambert 3140e6c76f
Some checks failed
windows-msi / full-msi-unsigned (push) Has been cancelled
feat: PUBLISH-DIGESTS.md + commented Authenticode CI paths + EmbedBrandFonts TTF
windows-msi-sign.yml.example; gofpdf register when EmbedBrandFonts and fonts present.
2026-10-14 01:24:11 -04:00

230 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Commercial installer backlog (MSI + notarized macOS .app)
Backlog for **shipping-grade** installers: a full Windows **MSI** and a **codesigned + notarized** macOS **`.app`** (and related distribution). This is inventory of what already exists versus what is still missing.
Day-to-day packaging (tray, menu bar, version stamps, CERT-01, IPP GET vs POST) is documented in **[PACKAGING.md](PACKAGING.md)**. This file does not replace that runbook; it tracks commercial distribution gaps only.
**Product version source of truth:** `internal/job.HelperVersion` (see [PACKAGING.md](PACKAGING.md) § Version source of truth). All commercial artifacts must stamp that value (or an explicit `VERSION=` override), not hard-coded older defaults.
---
## Scaffold shipped (this leftover)
Repo now includes **authoring/docs scaffolds** for the two commercial gaps that were still empty trees. These are **not** shipping-grade release artifacts.
| Scaffold | Path | What it is | What it is not |
|----------|------|------------|----------------|
| **WiX tray presence MSI skeleton** | [`platform/windows/wix/`](../platform/windows/wix/) — `VeraeTrayShortcuts.wxs`, `VeraeTrayShortcutsFragment.wxs`, [`README.md`](../platform/windows/wix/README.md); driver [`scripts/build-tray-msi.ps1`](../platform/windows/scripts/build-tray-msi.ps1) | Non-elevated **per-user** WiX v3 product + shortcut fragment: stages `verae-tray.ps1`, Start Menu `Programs\Verae\Verae Tray.lnk`, Startup `Verae Tray.lnk`. **ProductVersion** wired from `Get-VeraePackageVersion` → `internal/job.HelperVersion` (or `VERSION=` / `-Version`). `build-tray-msi.ps1` runs **candle/light** when present; otherwise prints WiX install tip (exit 0). | Full elevated commercial MSI (printers, Program Files, Authenticode, NSIS parity, CI-signed MSI in `dist/`). No MSI binary without the WiX toolset on the build host. |
| **Menubar notarize checklist** | [`platform/macos/menubar/NOTARIZE-CHECKLIST.md`](../platform/macos/menubar/NOTARIZE-CHECKLIST.md) | Operator step list: unsigned build → codesign hardened runtime → `ditto` zip → `notarytool` (keychain profile **or** API key placeholders) → staple → `spctl`. No secrets. | Automated sign/notarize in `build-menubar-app.sh`, stapled `.app.zip`/DMG release, or live CI keychain import. |
| **Menubar CI notary sketch** | [`platform/macos/menubar/ci-notarize.yml.example`](../platform/macos/menubar/ci-notarize.yml.example) | GHA/Forgejo **example** workflow: default **unsigned** `build-menubar-app.sh` + artifact; commented notarytool job gated `if: false`; secret **names** only (`APPLE_API_KEY_*`, `APPLE_CODESIGN_IDENTITY`, …). | Not a live workflow under `.github/`; no staple; not Gatekeeper-proven. |
| **Menubar notary dry-run** | [`platform/macos/menubar/notary-dry-run.sh`](../platform/macos/menubar/notary-dry-run.sh) | Local helper: if `notarytool` present print codesign/`ditto`/submit/staple recipe with env **names** only; else Xcode CLT tip (exit 0). Links [`ci-notarize.yml.example`](../platform/macos/menubar/ci-notarize.yml.example) + this backlog. **Never submits** unless `NOTARY_SUBMIT=1`. | Not live CI; does not codesign by default; not Gatekeeper-proven. |
| **Full elevated MSI (StageFull + light)** | [`Product-Full.wxs`](../platform/windows/wix/Product-Full.wxs) + [`scripts/build-full-msi.ps1`](../platform/windows/scripts/build-full-msi.ps1) (+ outline, [`SILENT_PROPS.md`](../platform/windows/wix/SILENT_PROPS.md), [`CI-SIGN-NOTARY.md`](../platform/windows/wix/CI-SIGN-NOTARY.md), [`DUAL-SHIP.md`](../platform/windows/wix/DUAL-SHIP.md)) | Elevated WiX v3 product: perMachine, Feature/ComponentGroups for **bin** (`verae-print.exe` required), **scripts**, **docs**, Start Menu; silent public props; distinct UpgradeCode. `build-full-msi.ps1` creates `StageFull/{bin,scripts}`, copies or Go-builds EXEs, copies scripts/LICENSE/README, stamps **ProductVersion** from `Get-VeraePackageVersion`, runs candle/light → `VeraePrintStamp-windows-*-<version>.msi`. WiX missing → **exit 1** + clear tip. Version defaults on build-windows / build-pkg / NSIS / build-installer aligned to HelperVersion. Commented deferred CA + `InstallExecuteSequence` stubs for `install-printer.ps1` (no binaries required). Dual-ship runbook documents NSIS vs MSI clean-VM gates + **thin-NSIS exit checklist**. | **Not** Authenticode-signed; printer CAs still **commented** (not scheduled/tested); properties not yet honored by live CAs; not Intune-proven; NSIS still primary full install; CI builds **unsigned** MSI only (sign step not live). |
| **CI unsigned full MSI** | [`.github/workflows/windows-msi.yml`](../.github/workflows/windows-msi.yml) + DUAL-SHIP § CI artifact | `windows-latest`: install WiX v3 → `build-full-msi.ps1` → upload artifact `VeraePrintStamp-windows-msi-unsigned`. Commented **signtool** + **Trusted Signing** steps (secret **names** only). | No live `signtool`; no certs in git; not a production download channel. |
| **CI sign example** | [`.github/workflows/windows-msi-sign.yml.example`](../.github/workflows/windows-msi-sign.yml.example) | GHA/Forgejo **example**: build + Path A PFX/`signtool` + Path B Azure Trusted Signing; all gated `if: false`; secret **names** only. | Not a live workflow; no certs; does not claim SmartScreen. |
| **Publish digests runbook** | [`docs/PUBLISH-DIGESTS.md`](PUBLISH-DIGESTS.md) | Operator order: build → sign → upload → `fill-manifest-sha256.ps1` → paste TBD. | Does not upload or enable live sign. |
### Issue 1 — what landed vs still open
| Done (advanced) | Still left (Issue 1 commercial) |
|-----------------|----------------------------------|
| `Product-Full.wxs` File components match StageFull; candle/light-ready | **Enable** (`enable-printer-cas.ps1`) **+ test** printer deferred CAs on clean VM (DUAL-SHIP VM C); parity with NSIS |
| `build-full-msi.ps1` StageFull + ProductVersion + exit codes | **Enable** live CI Authenticode: wire org secrets + flip `windows-msi-sign.yml.example` / uncomment `windows-msi.yml` sign steps |
| `CI-SIGN-NOTARY.md` GHA/Forgejo draft (secret **names** only) + [`sign-dry-run.ps1`](../platform/windows/scripts/sign-dry-run.ps1) local verify/print | Live CI **signtool** / Trusted Signing still **not** running; live notarytool staple on macOS; clean-VM Gatekeeper proof |
| `SILENT_PROPS.md` msiexec one-liner + exit codes for full MSI | Properties honored by **scheduled** CAs (config write, skip printers/key) |
| HelperVersion path on build-windows / build-pkg / NSIS / build-installer / full MSI | Dual-ship **clean-VM validation** (A/B/C); then execute thin-NSIS exit checklist |
| Commented CA + `InstallExecuteSequence` stubs (`install-printer` schedule) | Intune/SCCM silent smoke with real exit codes; publish 0.3.6 signed blobs + manifest checksums |
| [`DUAL-SHIP.md`](../platform/windows/wix/DUAL-SHIP.md) NSIS+MSI + CI artifact + **thin-NSIS exit checklist** | Site primary download still NSIS; thin/demote NSIS only after exit checklist A–D |
| Site product string → **0.3.6**; manifest header notes HelperVersion vs last-published 0.3.2 | Do **not** claim Authenticode / notary / dual-ship / thin-NSIS complete |
| [`.github/workflows/windows-msi.yml`](../.github/workflows/windows-msi.yml) unsigned MSI + **commented** signtool / Trusted Signing (secret names) | Org secrets + enable sign; publish signed assets to site |
| [`windows-msi-sign.yml.example`](../.github/workflows/windows-msi-sign.yml.example) full sign sketch (`if: false`) | Promote example → live workflow when certs exist |
| [`ci-notarize.yml.example`](../platform/macos/menubar/ci-notarize.yml.example) menubar GHA/Forgejo sketch (unsigned default; secret names) | Promote example → live workflow; automate `build-menubar-app.sh` sign when `SIGNING_ENV` set |
| **0.3.6 Windows NSIS+MSI placeholder rows** ([`DOWNLOADS-MANIFEST.txt`](../site/printit.verae.ai/DOWNLOADS-MANIFEST.txt) + [`DOWNLOADS-MANIFEST.md`](../site/printit.verae.ai/DOWNLOADS-MANIFEST.md); sha256 **TBD**) | Build/upload signed 0.3.6 blobs; replace TBD with real digests |
| [`fill-manifest-sha256.ps1`](../platform/windows/scripts/fill-manifest-sha256.ps1) + [`PUBLISH-DIGESTS.md`](PUBLISH-DIGESTS.md) (hash → paste; **no upload**) | Operator still must upload first, then paste digests over TBD |
| **Printable VM A/B/C sheet** [`VM-DUAL-SHIP-CHECKLIST.md`](../platform/windows/wix/VM-DUAL-SHIP-CHECKLIST.md) | Operator sign-off on clean VMs (C blocked until CAs uncommented) |
| **Offline dual-ship smoke** [`dual-ship-smoke.ps1`](../platform/windows/scripts/dual-ship-smoke.ps1) (prints A/B/C; **VM C blocked until CAs enabled**; `-VerifyArtifacts` → existence + `fill-manifest-sha256.ps1`) | Does **not** replace clean-VM sign-off; no install/sign/upload |
| **Enable printer CAs** [`enable-printer-cas.ps1`](../platform/windows/scripts/enable-printer-cas.ps1) + wix README § Enable printer CAs | Unwraps `DEFERRED_PRINTER_CAS:OFF` in Product-Full for VM C; does **not** build/test/sign; default remains disabled |
| **Authenticode dry-run** [`sign-dry-run.ps1`](../platform/windows/scripts/sign-dry-run.ps1) + [`CI-SIGN-NOTARY.md`](../platform/windows/wix/CI-SIGN-NOTARY.md) | Local verify + print sign cmds only (**no secrets**). Live CI signtool / Trusted Signing still **not** enabled |
| **Notary dry-run** [`notary-dry-run.sh`](../platform/macos/menubar/notary-dry-run.sh) + [`ci-notarize.yml.example`](../platform/macos/menubar/ci-notarize.yml.example) | Local print submit recipe (env **names**); tip if no notarytool; submit only if `NOTARY_SUBMIT=1`. Live notary CI still **not** enabled |
**Still use for day-to-day presence (no WiX / no Apple certs):**
Windows `scripts/build-tray-startmenu.ps1` + `install-tray-startup.ps1`; macOS `verae-print menubar --install` / unsigned `build-menubar-app.sh`. Full product install remains NSIS (`installer-windows.nsi`) and macOS `pkg/build-pkg.sh`. Optional: `build-tray-msi.ps1` (WiX tip exit 0); `build-full-msi.ps1` (needs WiX; exit 1 if missing).
---
## Goals (definition of done)
| Platform | Ship artifact | Done when |
|----------|---------------|-----------|
| **Windows** | Elevated **MSI** (WiX or equivalent) + **Authenticode**-signed payload | Enterprise deployable via Intune/SCCM/GPO; silent install; upgrade/uninstall ARP; printer + helper + tray; signed binaries and MSI |
| **macOS** | **Developer ID** codesigned, **notarized**, **stapled** **`.app`** (and/or signed `.pkg` that installs it) | Gatekeeper clean on fresh Mac (`spctl` assess pass); hardened runtime; no right-click Open workaround; version = `HelperVersion` |
Related but out of scope for “done” above (still backlog-adjacent): Linux `.deb`, auto-update channels, MSIX / Print Support App.
---
## Windows — full MSI
### Exists today
| Item | Location / notes | Status |
|------|------------------|--------|
| **NSIS GUI setup.exe** (elevated product install) | [`platform/windows/installer-windows.nsi`](../platform/windows/installer-windows.nsi) | **Shipped path** for full product (not MSI) |
| Older/simpler NSIS script | [`platform/windows/installer.nsi`](../platform/windows/installer.nsi) | Present; used by cross-build helper |
| Native Windows build → setup.exe | [`platform/windows/build-windows.ps1`](../platform/windows/build-windows.ps1) | Builds `verae-print.exe` + GUI helper, runs `makensis` |
| Cross-host NSIS build | [`platform/windows/build-installer.sh`](../platform/windows/build-installer.sh) | Needs prebuilt binaries from `scripts/build-win-linux.sh` |
| Printer install / uninstall | [`platform/windows/scripts/install-printer.ps1`](../platform/windows/scripts/install-printer.ps1), `uninstall-printer.ps1` | Used by NSIS product |
| Tray NotifyIcon + GET `/status` | [`platform/windows/scripts/verae-tray.ps1`](../platform/windows/scripts/verae-tray.ps1) | User-level; see [PACKAGING.md](PACKAGING.md) § Windows tray |
| Startup-folder tray install | [`platform/windows/scripts/install-tray-startup.ps1`](../platform/windows/scripts/install-tray-startup.ps1) | **Not** MSI; no elevation |
| WiX-free Start Menu stub | [`platform/windows/scripts/build-tray-startmenu.ps1`](../platform/windows/scripts/build-tray-startmenu.ps1) | Explicitly “future MSI can supersede” |
| **WiX tray presence skeleton** (per-user, non-elevated) | [`platform/windows/wix/`](../platform/windows/wix/) + [`scripts/build-tray-msi.ps1`](../platform/windows/scripts/build-tray-msi.ps1) | **Scaffold shipped** — ProductVersion ← HelperVersion; candle/light when present, else WiX tip; not full product MSI |
| **WiX full elevated MSI** (StageFull driver) | [`Product-Full.wxs`](../platform/windows/wix/Product-Full.wxs) + [`scripts/build-full-msi.ps1`](../platform/windows/scripts/build-full-msi.ps1) | **Authoring + stage/light path** — MSI when WiX present; unsigned; printer CAs commented stubs; dual-ship NSIS |
| **CI unsigned full MSI** | [`.github/workflows/windows-msi.yml`](../.github/workflows/windows-msi.yml) | **Live workflow** — WiX install → `build-full-msi.ps1` → unsigned artifact; sign step commented (secret names only) |
| **Dual-ship runbook** | [`wix/DUAL-SHIP.md`](../platform/windows/wix/DUAL-SHIP.md) | **Docs** — NSIS + MSI clean-VM matrix; CI artifact section; **thin-NSIS exit checklist** (A–E) |
| **Printable VM checklist** | [`wix/VM-DUAL-SHIP-CHECKLIST.md`](../platform/windows/wix/VM-DUAL-SHIP-CHECKLIST.md) | **Docs** — tick boxes for shared pre-checks + VM A/B/C + side-by-side |
| **0.3.6 download placeholders** | [`site/.../DOWNLOADS-MANIFEST.md`](../site/printit.verae.ai/DOWNLOADS-MANIFEST.md) + `.txt` TBD rows | **Docs** — NSIS setup + full MSI filenames reserved; digests TBD until publish |
| **Manifest SHA-256 row helper** | [`scripts/fill-manifest-sha256.ps1`](../platform/windows/scripts/fill-manifest-sha256.ps1) | **Tool** — local `Get-FileHash` → SHA256SUMS / MD rows; does **not** upload or rewrite site files |
| **Offline dual-ship smoke** | [`scripts/dual-ship-smoke.ps1`](../platform/windows/scripts/dual-ship-smoke.ps1) | **Tool** — prints VM A/B/C checklist; notes **C blocked until CAs enabled**; optional local MSI/NSIS existence + hash via fill-manifest; does **not** install or drive VMs |
| **Enable deferred printer CAs** | [`scripts/enable-printer-cas.ps1`](../platform/windows/scripts/enable-printer-cas.ps1) | **Tool** — toggles `DEFERRED_PRINTER_CAS:OFF` / `:ON` markers in Product-Full.wxs (VM C prereq); default disabled |
| **Authenticode dry-run** | [`scripts/sign-dry-run.ps1`](../platform/windows/scripts/sign-dry-run.ps1) | **Tool** — find `signtool`; verify unsigned MSI/EXE; print sign command templates (secret **names** only); exit 0 if no signtool; **does not sign** |
| Shared version helper | [`platform/windows/scripts/common.ps1`](../platform/windows/scripts/common.ps1) `Get-VeraePackageVersion` | Aligns tray/full MSI/NSIS drivers with `HelperVersion` |
| End-user Windows readme | [`platform/windows/README-WINDOWS.txt`](../platform/windows/README-WINDOWS.txt), [`README.txt`](../platform/windows/README.txt) | Documents NSIS flow; states **no elevated MSI** for tray |
| Packaging policy doc | [PACKAGING.md](PACKAGING.md) | “**No elevated MSI**” for tray presence by design |
| Spec intent (historical) | [`docs/superpowers/specs/2026-10-03-print-to-verae-timestamp.md`](superpowers/specs/2026-10-03-print-to-verae-timestamp.md) | Mentions MSI + Authenticode as product direction |
| Plan checkbox (open) | [`docs/superpowers/plans/2026-10-03-print-to-verae-timestamp.md`](superpowers/plans/2026-10-03-print-to-verae-timestamp.md) | “Tray helper build, WiX MSI, Authenticode signing” still unchecked |
NSIS product already covers much of the *install experience* (files under Program Files, key setup UI, IPP + Local Port printers, helper task, Start Menu, uninstall, diagnostics). That is **not** an MSI and is not WiX-based.
### Missing for commercial MSI
| Gap | Why it matters | Suggested work |
|-----|----------------|----------------|
| **Full-product WiX authoring + StageFull driver** | **Exists** — `Product-Full.wxs` + `build-full-msi.ps1` | MSI binary only on hosts with candle/light; still dual-ship NSIS |
| **MSI output when WiX present** | `wix/out/VeraePrintStamp-windows-<arch>-<version>.msi` via `build-full-msi.ps1` | Not yet published to `dist/` / site downloads; local hash helper `fill-manifest-sha256.ps1` (no upload/CI publish job yet) |
| **No Authenticode signing pipeline** in-repo | SmartScreen / enterprise trust | Local dry-run: [`sign-dry-run.ps1`](../platform/windows/scripts/sign-dry-run.ps1). Live: `signtool` (or Azure Trusted Signing) for EXE/DLL **and** MSI; store certs in CI secrets only — never in git. **Live sign not shipped.** |
| **Silent / unattended install contract** | Fleet deploy | Props documented (`SILENT_PROPS.md`); wire scheduled CAs to honor them |
| **Upgrade / repair / ARP metadata** | Clean major-upgrade, Add/Remove Programs | WiX `UpgradeCode` present; migrate ARP fully from NSIS `UNINST_KEY` after dual-ship |
| **Custom actions parity with NSIS** | Printers, scheduled helper, per-user key | Port `install-printer.ps1`, helper registration into deferred/impersonated CAs (stubs commented in Product-Full) |
| **Per-user vs per-machine story** | Tray is user-level today; MSI is typically machine | Machine full MSI + optional per-user tray MSI (both authored; product decision open) |
| **Version drift in build scripts** | **Largely fixed** — HelperVersion path on build-windows, build-full-msi, build-pkg, build-installer, NSIS fallbacks **0.3.6**; site index product string **0.3.6** | Keep smoke failing on new hard-coded older defaults; `DOWNLOADS-MANIFEST` still checksums **last-published 0.3.2** blobs until release upload |
| **CI job that builds + signs MSI** | Reproducible release | **Unsigned build live** (`windows-msi.yml`); sign **example** + commented Path A/B (`windows-msi-sign.yml.example`); signtool / Trusted Signing still **not live** |
| **MSIX / Print Support App** (later) | Modern print path in original spec | Explicitly **deferred** unless enterprise asks; not blocking first MSI |
### Windows backlog order (suggested)
1. ~~Freeze install surface / WiX file components + StageFull driver~~ (**done**: outline + Product-Full + `build-full-msi.ps1`).
2. ~~Dual-ship runbook + commented printer CA schedule stubs~~ (**done**: [`DUAL-SHIP.md`](../platform/windows/wix/DUAL-SHIP.md); Product-Full commented `InstallExecuteSequence`).
3. ~~CI unsigned full MSI workflow + artifact~~ (**done**: [`.github/workflows/windows-msi.yml`](../.github/workflows/windows-msi.yml); DUAL-SHIP § CI artifact).
4. **Enable** printer/helper deferred CAs (`enable-printer-cas.ps1`) + test on clean VM (DUAL-SHIP VM C); keep NSIS primary until parity. `dual-ship-smoke` blocks C until CAs enabled.
5. Authenticode on binaries + MSI (**not shipped** — enable [`windows-msi-sign.yml.example`](../.github/workflows/windows-msi-sign.yml.example) / commented steps in `windows-msi.yml`; secret names in `CI-SIGN-NOTARY.md`; local dry-run [`sign-dry-run.ps1`](../platform/windows/scripts/sign-dry-run.ps1)).
6. Honor silent props in live CAs + Intune sample command line proof.
7. Publish 0.3.6 artifacts; refresh site `DOWNLOADS-MANIFEST` per [`PUBLISH-DIGESTS.md`](PUBLISH-DIGESTS.md) + `fill-manifest-sha256.ps1` (hash-only; no upload); keep smoke on HelperVersion drift.
8. Deprecate or thin NSIS once MSI is the primary download (**thin-NSIS exit checklist** A–D in DUAL-SHIP.md — all boxes).
---
## macOS — notarized `.app` (and signed `.pkg`)
### Exists today
| Item | Location / notes | Status |
|------|------------------|--------|
| **Signed + notarized `.pkg` builder** | [`platform/macos/pkg/build-pkg.sh`](../platform/macos/pkg/build-pkg.sh) | **Full commercial path for installer package**: universal `verae-print`, codesign hardened runtime, stages `Verae Print Stamp.app`, `pkgbuild` + `productbuild` (Developer ID Installer), `notarytool submit --wait`, `stapler staple`, SHA256SUMS |
| Entitlements | [`platform/macos/pkg/entitlements.plist`](../platform/macos/pkg/entitlements.plist) | network client, user-selected files, allow-jit |
| Postinstall (printer, LaunchAgent, key setup) | [`platform/macos/pkg/scripts/postinstall`](../platform/macos/pkg/scripts/postinstall) | Console-user IPP queue + `mac-key-setup` |
| App bundle **inside** pkg stage | Built by `build-pkg.sh` → `/Applications/Verae Print Stamp.app` | Thin wrapper: runs `verae-print mac-key-setup` or `stamp -open` on dropped PDFs; codesigned in pkg flow |
| Signing env (external) | `$HOME/fie/installers/macos/signing.env` (default); not in repo | API key path / issuer / Developer ID identities — correct secret hygiene |
| IPP LaunchAgent + install scripts | [`platform/macos/ipp/`](../platform/macos/ipp/) | Used by pkg postinstall |
| Menu bar Swift + LaunchAgent | [`platform/macos/menubar/`](../platform/macos/menubar/) | Runtime presence; see [PACKAGING.md](PACKAGING.md) § macOS menu bar |
| **Unsigned** menu-bar `.app` builder | [`platform/macos/menubar/build-menubar-app.sh`](../platform/macos/menubar/build-menubar-app.sh) | Dev/local only; copies entitlements but **does not** codesign |
| Notarization **documentation only** (menubar) | [`platform/macos/menubar/README.txt`](../platform/macos/menubar/README.txt) + [PACKAGING.md](PACKAGING.md) § Codesign + notarization | Manual steps; no automated menubar notarize |
| **Notarize checklist** (menubar) | [`platform/macos/menubar/NOTARIZE-CHECKLIST.md`](../platform/macos/menubar/NOTARIZE-CHECKLIST.md) | **Scaffold shipped** — notarytool placeholders, staple, `spctl`; no secrets; automation still open |
| **Notary dry-run** (menubar) | [`platform/macos/menubar/notary-dry-run.sh`](../platform/macos/menubar/notary-dry-run.sh) | **Tool** — print submit recipe (env names) or Xcode tip; links `ci-notarize.yml.example` + this backlog; **no submit** unless `NOTARY_SUBMIT=1` |
| Packaging smoke | [`platform/smoke-packaging-metadata.sh`](../platform/smoke-packaging-metadata.sh) | Version + “no elevated MSI” + IPP purity — not Gatekeeper |
### Missing for commercial notarized `.app`
| Gap | Why it matters | Suggested work |
|-----|----------------|----------------|
| **No first-class standalone notarized `.app` zip/DMG release** | Many users expect drag-to-Applications `.app`, not only `.pkg` | After codesign: `ditto` zip **or** DMG → `notarytool` → staple **app** (and DMG if used); publish `VeraePrintStamp-<version>.app.zip` / `.dmg` + SHA256 |
| **Menu-bar `.app` not in automated sign/notarize path** | PT-06 presence still unsigned by default | Extend `build-menubar-app.sh` (or share a `sign-and-notarize.sh`) to apply Developer ID + notarytool + staple when `SIGNING_ENV` is set; keep unsigned default for dev |
| **Product `.app` is a thin launcher**, not a full bundled helper | App depends on files under `/Library/Application Support/Verae/PrintStamp/` from the **pkg** | Either (a) keep `.pkg` as primary and document that, or (b) ship a self-contained `.app` with embedded `verae-print`, registration-pdf, IPP assets, and first-run setup |
| **Menubar not installed by pkg postinstall** | Users get printer + key setup but not menu bar unless they run `verae-print menubar --install` | Optional: postinstall or app first-run installs LaunchAgent / offers menu-bar `.app` |
| **Version default drift** | **Fixed** — `build-pkg.sh` greps `HelperVersion` (fallback **0.3.6**) | Keep smoke if pkg default ≠ HelperVersion; site manifest still lists older shipped pkgs |
| **No DMG branding / layout** (optional) | Nicer download UX | `create-dmg` or `hdiutil` with Applications symlink; notarize DMG |
| **No Sparkle / appcast auto-update** | Commercial expectation for `.app` | Deferred unless product asks; if added, EdDSA keys outside repo |
| **Stapled artifact CI publish** | Repeatable Gatekeeper-clean builds | macOS runner with custom keychain import of Developer ID + notary API key; upload stapled `.pkg` and optional `.app.zip` |
| **Gatekeeper verification in smoke** | Catch regressions | On signed hosts: `spctl -a -vv -t install` on `.pkg`, `spctl --assess --type execute` on `.app`, `stapler validate` |
| **Hardened runtime audit of nested binaries** | Notary rejections | Ensure registration-pdf node bits are not executed under hardened runtime incorrectly; only sign Mach-O helpers that actually run |
| **Bundle ID / naming consistency** | Support and updates | Pkg app: `ai.verae.print-stamp`; menubar: `ai.verae.timestamping.menubar` — document which is “the” commercial app vs accessory |
### macOS backlog order (suggested)
1. ~~Point `build-pkg.sh` `VERSION` at `HelperVersion`~~ (**done**). Verify stapled `.pkg` on a clean VM (Gatekeeper + printer + key setup).
2. Decide distribution primary: **`.pkg` only** (already closest to done) vs **self-contained notarized `.app`** (+ optional DMG).
3. If `.app` primary: bundle helper + resources inside the app; first-run equals today’s postinstall responsibilities without root where possible.
4. Automate menubar sign/notarize as accessory artifact (or embed menubar in product app) — **not shipped** as automated path.
5. CI: promote [`ci-notarize.yml.example`](../platform/macos/menubar/ci-notarize.yml.example) → live notarize + staple + `spctl` + checksums; never commit `signing.env`.
6. Optional: DMG, Sparkle.
---
## Cross-platform commercial gaps
| Gap | Notes |
|-----|--------|
| **Single release checklist** | One version (`HelperVersion`), Windows MSI + setup.exe (transition), macOS stapled `.pkg` / `.app`, SHA256SUMS, site download links (`site/printit.verae.ai/DOWNLOADS-MANIFEST.txt`) |
| **Secrets policy** | Already correct direction: no certs/passwords in git; Apple `signing.env` and Windows PFX/Trusted Signing only in CI or operator home |
| **Install API / registration** | [`docs/installer-registration-api.md`](installer-registration-api.md) + `internal/installapi` — keep MSI/pkg silent props aligned |
| **Channel / metering defaults** | PrintIt → zapier channel; document MSI/pkg property overrides |
| **Hold ≠ evidence** | Tray/menubar must stay GET-only; commercial installers must not teach IPP POST from presence UI ([PACKAGING.md](PACKAGING.md) § IPP) |
| **Linux commercial** | `platform/linux/deb/build-deb.sh` exists; not part of MSI/app done criteria |
---
## Quick status matrix
| Deliverable | Exists | Commercial-ready |
|-------------|--------|------------------|
| Windows tray / Startup / Start Menu (user) | Yes | Yes for **presence**; not enterprise MSI |
| Windows NSIS `setup.exe` | Yes | **Partial** — full GUI install; needs Authenticode + version align; not MSI |
| Windows **MSI** (WiX) tray presence skeleton | **Yes** (`.wxs` + `build-tray-msi.ps1`; ProductVersion ← HelperVersion) | **No** — scaffold only; MSI only when candle/light installed; not full product MSI |
| Windows **MSI** full product (files + ARP) | **Yes authoring** (`Product-Full.wxs` + `build-full-msi.ps1` StageFull → light; CA schedule stubs commented) | **No commercial** — unsigned; printer CAs not live; NSIS still primary |
| Dual-ship NSIS↔MSI runbook | **Yes** ([`DUAL-SHIP.md`](../platform/windows/wix/DUAL-SHIP.md)) | **Docs only** — clean-VM validation + thin-NSIS exit checklist still unchecked |
| Menubar CI notary example | **Yes** ([`ci-notarize.yml.example`](../platform/macos/menubar/ci-notarize.yml.example)) | **Example only** — unsigned default; notary job `if: false` |
| Menubar notary dry-run | **Yes** ([`notary-dry-run.sh`](../platform/macos/menubar/notary-dry-run.sh)) | **Scaffold** — print recipe / tip; submit gated `NOTARY_SUBMIT=1` only |
| Windows Authenticode pipeline | **Draft** (`wix/CI-SIGN-NOTARY.md`) + **dry-run** [`sign-dry-run.ps1`](../platform/windows/scripts/sign-dry-run.ps1) + commented steps in [`windows-msi.yml`](../.github/workflows/windows-msi.yml) + [`windows-msi-sign.yml.example`](../.github/workflows/windows-msi-sign.yml.example) | **No** — no live signtool job (**do not claim shipped**) |
| Publish digests runbook | **Yes** ([`PUBLISH-DIGESTS.md`](PUBLISH-DIGESTS.md) + `fill-manifest-sha256.ps1`) | **Docs/tool** — operator still uploads + pastes TBD |
| macOS menu bar LaunchAgent | Yes | Yes (dev/ops) |
| macOS unsigned menubar `.app` | Yes | **No** (Gatekeeper) |
| macOS **signed+notarized `.pkg`** script | Yes | **Near** — needs version sync, CI, clean-VM proof |
| macOS **standalone notarized `.app`** zip/DMG | **Partial** (app built inside pkg only) | **No** as primary download |
| Menubar notarize checklist | **Yes** (`NOTARIZE-CHECKLIST.md`) | Docs only — not automated |
| Menubar automated notarization | Docs + checklist only | **No** |
---
## References
- **[PACKAGING.md](PACKAGING.md)** — tray, menubar, unsigned `.app`, notarization steps (docs), CERT-01, statuslang, IPP split, smoke tests
- [`platform/windows/`](../platform/windows/) — NSIS + tray scripts
- [`platform/windows/wix/`](../platform/windows/wix/) + [`build-tray-msi.ps1`](../platform/windows/scripts/build-tray-msi.ps1) — per-user tray MSI skeleton; ProductVersion ← HelperVersion
- [`platform/windows/wix/Product-Full.wxs`](../platform/windows/wix/Product-Full.wxs) + [`build-full-msi.ps1`](../platform/windows/scripts/build-full-msi.ps1) — elevated full MSI StageFull + candle/light
- [`platform/windows/wix/DUAL-SHIP.md`](../platform/windows/wix/DUAL-SHIP.md) — NSIS + MSI side-by-side validation runbook
- [`platform/windows/wix/CI-SIGN-NOTARY.md`](../platform/windows/wix/CI-SIGN-NOTARY.md) — CI Authenticode + notarytool sketch (**draft only**)
- [`.github/workflows/windows-msi-sign.yml.example`](../.github/workflows/windows-msi-sign.yml.example) — GHA sign example (signtool + Trusted Signing; `if: false`; secret names only)
- [`docs/PUBLISH-DIGESTS.md`](PUBLISH-DIGESTS.md) — fill DOWNLOADS-MANIFEST after sign via `fill-manifest-sha256.ps1`
- [`platform/windows/scripts/sign-dry-run.ps1`](../platform/windows/scripts/sign-dry-run.ps1) — local `signtool` verify + print sign commands (no secrets; exit 0 if no signtool)
- [`platform/windows/wix/SILENT_PROPS.md`](../platform/windows/wix/SILENT_PROPS.md) — msiexec `/qn` property contract
- [`platform/macos/pkg/`](../platform/macos/pkg/) — commercial `.pkg` build
- [`platform/macos/menubar/`](../platform/macos/menubar/) — menu bar + unsigned `.app` + notarize docs/checklist
- [`platform/macos/menubar/NOTARIZE-CHECKLIST.md`](../platform/macos/menubar/NOTARIZE-CHECKLIST.md) — menubar notarytool step list
- [`platform/macos/menubar/ci-notarize.yml.example`](../platform/macos/menubar/ci-notarize.yml.example) — GHA/Forgejo menubar CI sketch (unsigned default; secret names only)
- [`platform/macos/menubar/notary-dry-run.sh`](../platform/macos/menubar/notary-dry-run.sh) — local notarytool dry-run (print recipe; `NOTARY_SUBMIT=1` to submit)
- [`docs/installer-registration-api.md`](installer-registration-api.md) — installer ↔ portal registration API
- [`README.md`](../README.md) — top-level packaging pointers
---
## Maintenance
When closing a row above, update the **Quick status matrix** and add a one-line note under the relevant “Exists” table (path + date). When shipping a new **scaffold** (authoring only), add/refresh the **Scaffold shipped** section rather than marking commercial-ready. Do not move day-to-day tray/menubar how-to out of [PACKAGING.md](PACKAGING.md) into this backlog.