docs: OPERATOR-READY package for MSI/notary external legs
Some checks failed
windows-msi / full-msi-unsigned (push) Has been cancelled

Consolidate Authenticode, Apple notary, VM C, and digest-paste into
platform/windows/wix/OPERATOR-READY.md (runbook + commands + secret
names only). Cross-link from CI-SIGN-NOTARY, VM-C-ONE-PAGER, PACKAGING,
and COMMERCIAL-INSTALLER-BACKLOG. Live enable remains blocked only on
org secret store / clean VM — no secret values invented.
This commit is contained in:
George Lambert 2026-10-14 04:17:07 -04:00
commit 778763737e
5 changed files with 232 additions and 2 deletions

View file

@ -22,6 +22,7 @@ Repo now includes **authoring/docs scaffolds** for the two commercial gaps that
| **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. |
| **OPERATOR-READY package** | [`wix/OPERATOR-READY.md`](../platform/windows/wix/OPERATOR-READY.md) | Single runbook: secret **names** + copy-paste commands for Authenticode, notary, VM C, digest paste. | Live values / clean VM still org-side; **READY: blocked only on org secret/VM**. |
### Issue 1 — what landed vs still open

View file

@ -290,6 +290,8 @@ CI: unsigned full MSI [`.github/workflows/windows-msi.yml`](../.github/workflows
sign example (secret **names** only)
[`.github/workflows/windows-msi-sign.yml.example`](../.github/workflows/windows-msi-sign.yml.example).
Digest paste after upload: [PUBLISH-DIGESTS.md](PUBLISH-DIGESTS.md).
Operator enable package (secrets/VM only left):
[platform/windows/wix/OPERATOR-READY.md](../platform/windows/wix/OPERATOR-READY.md).
## Related paths

View file

@ -1,9 +1,10 @@
# CI — Authenticode (Windows) + notarytool (macOS)
**Status:** unsigned MSI CI is live (`.github/workflows/windows-msi.yml`); **sign/notary steps remain draft** (commented secret **names** + example workflow). No secrets in git.
**Operator package:** [`OPERATOR-READY.md`](OPERATOR-READY.md) — runbook + commands + secret names; live path **READY: blocked only on org secret/VM**.
**Audience:** release engineers wiring GitHub Actions or Forgejo Actions.
**Related:** [`Product-Full.wxs`](Product-Full.wxs), [`SILENT_PROPS.md`](SILENT_PROPS.md),
[`DUAL-SHIP.md`](DUAL-SHIP.md) § CI artifact,
[`DUAL-SHIP.md`](DUAL-SHIP.md) § CI artifact, [`VM-C-ONE-PAGER.md`](VM-C-ONE-PAGER.md),
[`.github/workflows/windows-msi.yml`](../../../.github/workflows/windows-msi.yml) — unsigned build + commented signtool / Trusted Signing,
[`.github/workflows/windows-msi-sign.yml.example`](../../../.github/workflows/windows-msi-sign.yml.example) — full sign sketch (`if: false`; secret **names** only),
[`docs/PUBLISH-DIGESTS.md`](../../../docs/PUBLISH-DIGESTS.md) — fill DOWNLOADS-MANIFEST after sign via `fill-manifest-sha256.ps1`,

View file

@ -0,0 +1,224 @@
# OPERATOR-READY — commercial MSI / notary / dual-ship
**Status:** software + dry-runs complete. Live sign / notary / VM C / CDN digests are
**READY: blocked only on org secret/VM** (no missing in-repo steps).
**Audience:** release operator with org secret-store + clean Windows VM access.
**Does not invent secrets.** Names only. Values stay in org CI / keychain / operator home.
| Item | Blocker | In-repo ready |
|------|---------|---------------|
| Org Authenticode / Trusted Signing | Org secret store values | Yes — names + example workflow + dry-run |
| Live Apple notary submit/staple | Apple API key / keychain profile | Yes — names + checklist + dry-run |
| Clean VM C after `enable-printer-cas` | Physical/clean VM sign-off | Yes — one-pager + dual-ship smoke |
| Real sha256 in DOWNLOADS-MANIFEST | Signed blobs on CDN first | Yes — hash helper + paste runbook |
**Canonical siblings:** [CI-SIGN-NOTARY.md](CI-SIGN-NOTARY.md) · [VM-C-ONE-PAGER.md](VM-C-ONE-PAGER.md) ·
[DUAL-SHIP.md](DUAL-SHIP.md) · [VM-DUAL-SHIP-CHECKLIST.md](VM-DUAL-SHIP-CHECKLIST.md) ·
[docs/PUBLISH-DIGESTS.md](../../../docs/PUBLISH-DIGESTS.md) ·
[platform/macos/menubar/NOTARIZE-CHECKLIST.md](../../macos/menubar/NOTARIZE-CHECKLIST.md) ·
[`.github/workflows/windows-msi-sign.yml.example`](../../../.github/workflows/windows-msi-sign.yml.example) ·
[`platform/macos/menubar/ci-notarize.yml.example`](../../macos/menubar/ci-notarize.yml.example)
---
## 0) Preconditions (already in repo)
```text
# From PrintToVeraeTimeStamp repo root
go test ./internal/pdfcert ./internal/statuslang -count=1
./platform/smoke-packaging-metadata.sh
./platform/macos/menubar/notary-dry-run.sh # exit 0; no submit
# Windows builder:
powershell -NoProfile -ExecutionPolicy Bypass -File platform\windows\scripts\sign-dry-run.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File platform\windows\scripts\dual-ship-smoke.ps1
```
Unsigned MSI CI is live: `.github/workflows/windows-msi.yml`.
Sign/notary jobs stay gated (`if: false`) until secrets exist.
---
## 1) Org CI secrets — Authenticode / Trusted Signing
**READY: blocked only on org secret/VM**
### Secret / var **names** (never commit values)
| Name | Kind | Purpose |
|------|------|---------|
| `WINDOWS_CERT_PFX_BASE64` | secret | Base64 PFX for Path A (`signtool`) |
| `WINDOWS_CERT_PFX_PASSWORD` | secret | PFX password |
| `WINDOWS_CERT_THUMBPRINT` | secret | Optional `/sha1` after import |
| `WINDOWS_SIGN_TIMESTAMP_URL` | var | RFC 3161 TSA (e.g. `https://timestamp.digicert.com`) |
| `WINDOWS_TRUSTED_SIGNING_TENANT_ID` | secret | Path B Azure Trusted Signing |
| `WINDOWS_TRUSTED_SIGNING_CLIENT_ID` | secret | Path B |
| `WINDOWS_TRUSTED_SIGNING_CLIENT_SECRET` | secret | Omit when OIDC `id-token: write` |
| `WINDOWS_TRUSTED_SIGNING_ENDPOINT` | var | e.g. `https://cus.codesigning.azure.net` |
| `WINDOWS_TRUSTED_SIGNING_ACCOUNT` | var | Trusted Signing account name |
| `WINDOWS_TRUSTED_SIGNING_CERTIFICATE_PROFILE` | var | Certificate profile name |
### Operator commands
```text
# A) Add the names above to org GitHub/Forgejo secret store (values out-of-band).
# B) Copy example → live workflow (same secret names on Forgejo):
cp .github/workflows/windows-msi-sign.yml.example .github/workflows/windows-msi-sign.yml
# — or .forgejo/workflows/windows-msi-sign.yml with your Windows runs-on label
# C) Flip job gate: if: false → if: true (only after secrets exist)
# D) workflow_dispatch; download signed MSI artifact
# E) Local verify (Windows SDK):
powershell -NoProfile -ExecutionPolicy Bypass -File platform\windows\scripts\sign-dry-run.ps1 `
-Path .\path\to\signed.msi
```
**Do not** claim SmartScreen reputation until clean-VM install proof.
**Do not** upload PFX as a workflow artifact.
---
## 2) Live notary submit / staple (macOS)
**READY: blocked only on org secret/VM**
### Secret / env **names**
| Name | Purpose |
|------|---------|
| `APPLE_API_KEY_P8_BASE64` | App Store Connect API key `.p8` (base64; CI materialize only) |
| `APPLE_API_KEY_ID` | Key ID |
| `APPLE_API_ISSUER_ID` | Issuer UUID |
| `APPLE_TEAM_ID` | Team ID for identity selection |
| `APPLE_CODESIGN_IDENTITY` | `Developer ID Application: … (TEAMID)` |
| `APPLE_INSTALLER_IDENTITY` | `Developer ID Installer: … (TEAMID)` (product `.pkg`) |
| `NOTARY_KEYCHAIN_PROFILE` | Laptop profile (default print: `verae-notary`) |
| `NOTARY_KEY_PATH` / `NOTARY_KEY_ID` / `NOTARY_ISSUER_ID` | Checklist aliases after materialize |
### Operator commands (menubar accessory)
```sh
# Dry-run first (always safe; no submit):
./platform/macos/menubar/notary-dry-run.sh
# Build unsigned → codesign → ditto → notarytool → staple
# (full steps: platform/macos/menubar/NOTARIZE-CHECKLIST.md)
./platform/macos/menubar/build-menubar-app.sh "$HOME/Applications/Verae Menubar.app"
# codesign with $APPLE_CODESIGN_IDENTITY + entitlements (see checklist)
ditto -c -k --keepParent "$APP" /tmp/VeraeMenubar.zip
xcrun notarytool submit /tmp/VeraeMenubar.zip \
--keychain-profile "${NOTARY_KEYCHAIN_PROFILE:-verae-notary}" --wait
# or API key: --key "$NOTARY_KEY_PATH" --key-id "$APPLE_API_KEY_ID" --issuer "$APPLE_API_ISSUER_ID"
xcrun stapler staple "$APP" && xcrun stapler validate "$APP"
spctl --assess --type execute -vv "$APP"
```
### Product `.pkg` (already scripted)
```sh
# When operator signing.env / CI secrets present:
# platform/macos/pkg/build-pkg.sh (codesign + notarytool --wait + stapler)
```
### CI enable
```text
cp platform/macos/menubar/ci-notarize.yml.example .github/workflows/macos-menubar-notarize.yml
# Flip menubar-notarize job if: false → true only after Apple secrets exist.
```
**Never** set `NOTARY_SUBMIT=1` in shared CI without secrets + intentional release.
---
## 3) Clean VM C sign-off (`enable-printer-cas`)
**READY: blocked only on org secret/VM**
### Host (Windows builder) — unlock + rebuild
```powershell
# From repo root
powershell -NoProfile -ExecutionPolicy Bypass -File platform\windows\scripts\enable-printer-cas.ps1 -Status
powershell -NoProfile -ExecutionPolicy Bypass -File platform\windows\scripts\enable-printer-cas.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File platform\windows\scripts\build-full-msi.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File platform\windows\scripts\dual-ship-smoke.ps1 -VerifyArtifacts
powershell -NoProfile -ExecutionPolicy Bypass -File platform\windows\scripts\sign-dry-run.ps1
# Re-disable files-only tree when done authoring:
# enable-printer-cas.ps1 -Disable
```
### Clean VM C (operator physical/VM)
Printable ticks: [VM-DUAL-SHIP-CHECKLIST.md](VM-DUAL-SHIP-CHECKLIST.md) § VM C. Narrative: [DUAL-SHIP.md](DUAL-SHIP.md). One-pager: [VM-C-ONE-PAGER.md](VM-C-ONE-PAGER.md).
1. Install **elevated** full MSI (default or `VERAE_SKIP_PRINTERS=0`).
2. Confirm files under Program Files + IPP/`127.0.0.1:8631` + **Verae Print Helper** task.
3. One-page PDF stamp → receipt/CID ≠ jobId (hold is never evidence).
4. Uninstall clean; parity with VM A (NSIS).
5. Sign off checklist; keep NSIS primary until thin-NSIS exit A–D.
`dual-ship-smoke.ps1` prints steps offline — it does **not** replace VM C sign-off.
---
## 4) Upload signed blobs → real sha256 in DOWNLOADS-MANIFEST
**READY: blocked only on org secret/VM**
Order (fail closed) — [docs/PUBLISH-DIGESTS.md](../../../docs/PUBLISH-DIGESTS.md):
1. **Build** NSIS +/or full MSI (`build-full-msi.ps1` / `windows-msi.yml`).
2. **Sign** (section 1) when shipping Windows trust.
3. **Upload** the **same bytes** to CDN under reserved 0.3.6 leaf names.
4. **Hash** (no upload in script):
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File platform\windows\scripts\fill-manifest-sha256.ps1 `
-Path .\dist\VeraePrintStamp-windows-amd64-0.3.6-setup.exe,
.\platform\windows\wix\out\VeraePrintStamp-windows-amd64-0.3.6.msi `
-Format both
```
POSIX shape if no pwsh:
```bash
shasum -a 256 VeraePrintStamp-windows-amd64-0.3.6-setup.exe \
VeraePrintStamp-windows-amd64-0.3.6.msi
# lowercase hex; two spaces; leaf name
```
5. **Paste** over `TBD` in `site/printit.verae.ai/DOWNLOADS-MANIFEST.txt` + `.md`.
6. **Verify** re-download from public URL matches pasted digest.
Do **not** paste digests for unsigned-only CI smoke MSIs into production rows.
Reserved 0.3.6 leaves:
| Leaf | Channel |
|------|---------|
| `VeraePrintStamp-windows-amd64-0.3.6-setup.exe` | NSIS primary |
| `VeraePrintStamp-windows-arm64-0.3.6-setup.exe` | NSIS arm64 |
| `VeraePrintStamp-windows-amd64-0.3.6.msi` | Full MSI dual-ship |
| `VeraePrintStamp-windows-arm64-0.3.6.msi` | Full MSI arm64 |
| `verae-print-windows-amd64-0.3.6.exe` | Optional bare helper |
---
## Quick enable checklist (operator)
- [ ] Org secrets set (names in §1 / §2) — **values never in git**
- [ ] Copy `windows-msi-sign.yml.example` → live; flip `if: false`
- [ ] Copy `ci-notarize.yml.example` → live when Apple secrets exist; flip gate
- [ ] `enable-printer-cas.ps1` + `build-full-msi.ps1` on Windows builder
- [ ] Clean VM C ticks (checklist) signed off
- [ ] CDN upload of signed 0.3.6 blobs
- [ ] `fill-manifest-sha256.ps1` → paste TBD → re-download verify
Until every box above is done by a human with secrets/VM, commercial claims stay:
software-complete, **READY: blocked only on org secret/VM**.
---
## License
UNLICENSED © Verae — operator runbook; not a redistributable binary.

View file

@ -42,4 +42,6 @@ powershell -NoProfile -ExecutionPolicy Bypass -File platform\windows\scripts\fil
- Hold / jobId is never evidence.
- Re-disable CAs for files-only tree: `enable-printer-cas.ps1 -Disable`.
**Still left (Issue 1):** org secrets for live CI sign; live notary (macOS); clean-VM **C** sign-off; paste real digests after upload — see [OPEN-ISSUES](../../../../verae-2026-screens/OPEN-ISSUES.md) row 1 · [PUBLISH-DIGESTS.md](../../../docs/PUBLISH-DIGESTS.md) · [CI-SIGN-NOTARY.md](CI-SIGN-NOTARY.md).
**Operator package:** [OPERATOR-READY.md](OPERATOR-READY.md) — all four external legs (sign / notary / VM C / digests) with commands + secret **names**. Status: **READY: blocked only on org secret/VM**.
**Still left (org-side only):** org secrets for live CI sign; live notary (macOS); clean-VM **C** sign-off; paste real digests after upload — see [OPEN-ISSUES](../../../../verae-2026-screens/OPEN-ISSUES.md) row 1 · [PUBLISH-DIGESTS.md](../../../docs/PUBLISH-DIGESTS.md) · [CI-SIGN-NOTARY.md](CI-SIGN-NOTARY.md).