# PrintIt packaging (tray, menubar, CERT) Desktop packaging for PrintToVeraeTimeStamp: version stamps, Windows tray / Start Menu presence (no elevated MSI), macOS menu bar / LaunchAgent / optional unsigned `.app`, CERT-01 digests, statuslang notify titles, and the split between pure GET status routes and the IPP POST print path. ## Version source of truth | Item | Value / location | |------|------------------| | Constant | `internal/job.HelperVersion` = **`0.3.6`** | | File | [`internal/job/job.go`](../internal/job/job.go) | | Sidecar field | `"helper_version"` on every `.verae.json` | All packaging metadata must resolve from that constant (or fall back to the same string when `job.go` is not on disk): - Windows: `platform/windows/scripts/common.ps1` → `Get-VeraePackageVersion` (fallback `"0.3.6"`). Used by `verae-tray.ps1`, `install-tray-startup.ps1`, `build-tray-startmenu.ps1`. - macOS: `platform/macos/menubar/build-menubar-app.sh` reads `job.go` when `VERSION` is unset; stamps `CFBundleShortVersionString` / `CFBundleVersion`, `Contents/Resources/VERSION`, and `packaging-metadata.txt`. - Binary build: `scripts/build-win-linux.sh` greps `HelperVersion` from `job.go`. - Linux deb: `platform/linux/deb/build-deb.sh` greps `HelperVersion` (fallback `"0.3.6"`). - Smoke: `platform/smoke-packaging-metadata.sh` fails if tray/menubar/deb scripts drift from `HelperVersion`. Override for a one-off stamp: `VERSION=x.y.z` (env) on the build/install scripts. Do not invent a second version constant. ### HelperVersion 0.3.6 audit (todo-list-3) Scan date: 2026-10-14. Source of truth: `internal/job.HelperVersion` = **0.3.6**. | Area | Status | |------|--------| | `job.go` / sidecar / certificate.html / site product string | **0.3.6** | | NSIS / WiX / common.ps1 / menubar / build-pkg / build-installer fallbacks | **0.3.6** | | `platform/linux/deb/build-deb.sh` fallback was **0.3.4** | **Fixed** → grep `HelperVersion` + fallback **0.3.6** | | Site deb blurb was **0.3.4** | **Fixed** → **0.3.6** + lag note | | CDN `DOWNLOADS-MANIFEST` checksums | Intentionally last-published **0.3.2** / **0.1.2**; 0.3.6 Windows rows remain **TBD** until upload | Re-run: `./platform/smoke-packaging-metadata.sh` ## Windows tray (no elevated MSI) User-level presence only. **No elevated MSI**, no admin rights, no WiX requirement for the tray stub. | Script | Role | |--------|------| | [`platform/windows/scripts/verae-tray.ps1`](../platform/windows/scripts/verae-tray.ps1) | STA NotifyIcon; polls **GET `/status`**; menu opens **GET `/queue`** | | [`platform/windows/scripts/install-tray-startup.ps1`](../platform/windows/scripts/install-tray-startup.ps1) | Startup-folder shortcut (`Verae Tray.lnk`) that runs `verae-tray.ps1` | | [`platform/windows/scripts/build-tray-startmenu.ps1`](../platform/windows/scripts/build-tray-startmenu.ps1) | WiX-free Start Menu folder stub; also installs Startup unless `-NoStartup` | CLI entry points (same behavior): ```bat verae-print menubar verae-print menubar --install verae-print menubar --uninstall ``` Or invoke the scripts directly: ```powershell powershell -NoProfile -ExecutionPolicy Bypass -STA -File platform\windows\scripts\verae-tray.ps1 powershell -NoProfile -ExecutionPolicy Bypass -File platform\windows\scripts\install-tray-startup.ps1 powershell -NoProfile -ExecutionPolicy Bypass -File platform\windows\scripts\build-tray-startmenu.ps1 ``` Notes: - Package version in shortcut description / tooltip = `HelperVersion`. - Full product installer (NSIS) is separate: see Windows README / `installer-windows.nsi` when present. Tray packaging does **not** depend on it. - Hold count in the tray is a **local queue handle**, never evidence (`statuslang.EvidenceNote`). More detail: [`platform/windows/README.txt`](../platform/windows/README.txt). ## macOS menu bar NSStatusItem that polls helper **GET `/status`** and opens **GET `/queue`**. Does not touch IPP POST. | Asset | Role | |-------|------| | `platform/macos/menubar/verae-menubar.swift` | NSStatusItem source | | `platform/macos/menubar/run-menubar.sh` | Compile-or-interpret entry | | `platform/macos/menubar/com.verae.timestamping.menubar.plist` | LaunchAgent template | | `platform/macos/menubar/build-menubar-app.sh` | Optional **unsigned** `.app` builder | | `platform/macos/menubar/README.txt` | Runbook + **notarization steps (docs only)** | ### Run / install ```sh verae-print menubar verae-print menubar --foreground verae-print menubar --install # LaunchAgent at login verae-print menubar --uninstall ./platform/macos/menubar/run-menubar.sh ``` `--install` copies durable assets to `~/Library/Application Support/Verae/menubar/` and writes `~/Library/LaunchAgents/com.verae.timestamping.menubar.plist` (`launchctl bootstrap`, fallback `load -w`). Prefer LaunchAgent over Login Items for headless reliability. ### Unsigned `.app` ```sh ./platform/macos/menubar/build-menubar-app.sh ./platform/macos/menubar/build-menubar-app.sh ~/Applications/Verae\ Menubar.app VERSION=0.3.6 ./platform/macos/menubar/build-menubar-app.sh ``` - `LSUIElement=true` (menu bar only, no Dock). - Bundle id: `ai.verae.timestamping.menubar`. - **Not codesigned or notarized by default** — local/dev use. - Builder copies `platform/macos/pkg/entitlements.plist` into `Contents/Resources/` for optional signing; it does not apply `codesign` and never embeds secrets. ### Codesign + notarization (document only) Requires Apple Developer Program + Developer ID Application cert in the keychain. This repo does **not** ship secrets or `signing.env` values. When certs are available (steps also in [`platform/macos/menubar/README.txt`](../platform/macos/menubar/README.txt)): 1. Build unsigned app: `./build-menubar-app.sh ~/Applications/Verae\ Menubar.app` 2. Codesign hardened runtime + entitlements: ```sh APP="$HOME/Applications/Verae Menubar.app" ENT="$APP/Contents/Resources/entitlements.plist" codesign --force --deep --options runtime --timestamp \ --entitlements "$ENT" \ --sign "Developer ID Application: YOUR ORG (TEAMID)" \ "$APP" codesign --verify --deep --strict --verbose=2 "$APP" ``` 3. Zip: `ditto -c -k --keepParent "$APP" /tmp/VeraeMenubar.zip` 4. Notarize: `xcrun notarytool submit /tmp/VeraeMenubar.zip --keychain-profile "verae-notary" --wait` 5. Staple: `xcrun stapler staple "$APP"` then `xcrun stapler validate "$APP"` 6. Optional signed `.pkg`: `platform/macos/pkg/build-pkg.sh` (Developer ID Installer + `productbuild`) — separate from the menu-bar stub. Gatekeeper: unsigned app needs right-click → Open once, or `xattr -dr com.apple.quarantine "Verae Menubar.app"`. Do not paste API keys, app-specific passwords, or `.p12` material into the repo. ## CLI surface: pending / tray-status / menubar | Command | Purpose | |---------|---------| | `verae-print serve` | IPP helper + loopback **GET `/queue`**, **GET `/status`**, **GET `/`** (health) | | `verae-print tray-status` | Offline/online held · key · helper snapshot (`statuslang` headlines) | | `verae-print tray-status --open` | Open live `http://127.0.0.1:8631/queue` (or temp HTML if helper down) | | `verae-print menubar` | PT-W3 / PT-06: Windows tray or macOS menu bar | | `verae-print menubar --install` / `--uninstall` | Login presence (Startup / LaunchAgent) | | `verae-print pending --html` | Offline queue page (same HTML family as live `/queue`) | Default loopback: `127.0.0.1:8631` (`VERAE_IPP_ADDR` overrides). ## CERT-01: multihash / blake3 when present Branded certificate path: `internal/pdfcert` (CERT-01 plum/gold, final-only wording). Job path always prefers local CERT-01 over upstream attach PDF. | Digest | When shown | Multihash form | |--------|------------|----------------| | SHA-256 | Always when stamp hash is known | `f1220` + 64 hex (`pdfcert.ContentMultihash`) | | BLAKE3 | **Only** when `Options.Blake3` / upstream provides it | `f1e20` + 64 hex; omit fields when empty | Rules: - **Never invent** digests. Empty blake3 → omit `blake3` / `blake3_multihash` on sidecar and CERT. - Job does **not** hash file bytes for blake3; it only passes through an upstream digest. - Sidecar always stamps `"helper_version": HelperVersion`. - **Hold ≠ evidence.** Local hold / pending ids are queue handles only. Certificates, “Download proof”, and legal claims are allowed only for final / already-registered states (`statuslang.IsEvidence`). Standing copy: `statuslang.EvidenceNote`. ### Web certificate viewer (printit.verae.ai) Site page: [`site/printit.verae.ai/certificate.html`](../site/printit.verae.ai/certificate.html) | Item | Status | |------|--------| | CERT-01 layout | **Shipped** — plum/gold double frame, Fraunces title (local woff2 else CDN), gold rule, final banner, mono digests, digests-only footer | | Brand kit | Uses `brand/tokens.css`, `verae-ui-kit.css`, `brand/logo/verae-mark.svg` | | Multihash / BLAKE3 | Shown **only when present** (query, sidecar, or sample); never invented | | Load paths | Query string (`?sha256=&receipt=&…`), optional `.verae.json` sidecar drop, “Sample data” button | | Print CSS | Letter page, toolbar hidden, frame colors preserved | | Live verify | Link to `explorer.verae.ai/time?receipt=&sha256=` (same as `pdfcert.LookupURL`) | | Live QR (web) | **Shipped** — final + receipt/digest paints QR via qrcode.js CDN → data-URL; offline/provisional keeps SVG placeholder (print-safe) | | Residual gap | Full **CERT-01.html** pixel match (self-hosted design-kit Fraunces/Cantarell, Substrate extrinsic polish) still **not** claimed — web title prefers local `fonts/*.woff2` else CDN Fraunces + Georgia/serif offline fallback; issued artifact remains PDF (`internal/pdfcert` + `platform/registration-pdf/build.mjs`) | | Self-host fonts plan | [`site/printit.verae.ai/fonts/README.md`](../site/printit.verae.ai/fonts/README.md) — drop-in woff2 dir (empty + `.gitkeep`); local-first probe in `certificate.html` | | Local-first fonts (Issue 7) | `certificate.html` probes planned `fonts/*.woff2` (magic `wOF2`); any hit → `@font-face` only; none → Google Fonts CDN. No proprietary binaries downloaded or committed | | Font fetch scaffold (TODO-7 A7) | [`site/printit.verae.ai/fonts/fetch-fonts.example.sh`](../site/printit.verae.ai/fonts/fetch-fonts.example.sh) — curl **placeholders only** (commented URLs, OFL reminder, `--check`); no licensed binaries downloaded or committed | | Layout spacing (TODO-7 A7) | `certificate.html` padding/margins nudged toward CERT-01 mock: sheet **56×64**, column **816px**, logo **40px**, title block **26/6/14**, gold rule **2×120**, banner **18** / check **34** / title **16px**, kv **20**, verify gap **22**, QR **130**; plum **1.5px** + gold **0.5px** inset frame. Still **not** full pixel match | Design source of truth for the paper mock: `verae-2026-design/ui-designs/src/html/CERT-01.html`. Cross-product siblings (not owned here): Sign `certificate.html` (SG-P1), Verify ZP-V5, MCP Mac export PDF. ### TODO-7 residual (Issue 7 — still left) | Done this slice | Still left | |-----------------|------------| | Local-first `@font-face` else CDN; fonts drop-in README | Real OFL woff2 + `OFL.txt` **committed** (operator drop-in only today) | | `fetch-fonts.example.sh` example + `--check` | Full CERT-01.html pixel match (Fraunces opsz, Cantarell body if required, Substrate extrinsic polish) | | CERT sheet spacing closer to CERT-01 | PDF Fraunces embed in `internal/pdfcert` / `registration-pdf/build.mjs` (system Times/Helvetica only today) | ## statuslang notify titles Source: [`internal/statuslang`](../internal/statuslang/status.go). Tray, menu bar, and helper balloons must use these short titles (not free-form strings): | State key | Notify title | |-----------|--------------| | `held` / `signin` | `Sign in to finish timestamp` | | `final` / `completed` / `ok` | `Final receipt ready` | | (ready path) | `Printer ready` | | `pending` | `Pending receipt` | | `queued` | `Queued offline` | | `hashing` | `Hashing on device…` | | `provisional` | `Stamped, awaiting blockchain` | | `failed` / `action` | failed / action chip strings | | `existing` | `Already registered` | Helpers: - `NotifyTitle(state)`, `NotifyBody(state, title, extra)` — for held/pending, `extra` is a **local hold id** (never a receipt). - `NotifyHeldJobs(n)` — cancel / still-waiting banner. - `TrayHeadline` / `TrayDetail` — persistent tray line (held count, key OK, helper up, package version on Windows scripts). Windows `verae-tray.ps1` mirrors the same brand strings inline; keep them in sync with `statuslang` when labels change. ## IPP: GET status pure; POST print separate Same loopback port hosts two concerns. They must not couple. | Method / path | Role | Consumers | |---------------|------|-----------| | **GET** `/status` | JSON helper snapshot (held, key, health) | tray, menubar, `tray-status` | | **GET** `/queue` | HTML job queue (PT-04/W4) | tray “Open queue”, browser | | **GET** `/` | Health | probes | | **POST** (IPP Print-Job / send-document) | Actual print capture → stamp pipeline | OS print spooler only | Constraints: - Tray / menubar / install scripts **GET only**. No IPP POST, no `Print-Job`, no document send from presence UI. - `serve` wires `ServeQueue` / `ServeStatus` as extra handlers beside the IPP server; **POST remains pure IPP** so Windows/macOS print clients are unaffected (`cmd/verae-print/serve.go`). - Smoke `platform/smoke-packaging-metadata.sh` checks tray scripts for accidental IPP POST coupling. ## Verification ```sh # From apps/PrintToVeraeTimeStamp ./platform/smoke-packaging-metadata.sh go test ./cmd/verae-print -count=1 -run 'Menubar|Packaging|TrayStartmenu|FindRepo' go test ./internal/job ./internal/pdfcert ./internal/statuslang -count=1 ``` Expect: HelperVersion match, no elevated MSI path required, IPP pure on tray/menubar assets. ### Commercial MSI / notary dry-runs (Issue 1 software) Offline helpers — no WiX, signtool, notary credentials, or upload required for the tip/print paths. Full inventory and still-left external work: [COMMERCIAL-INSTALLER-BACKLOG.md](COMMERCIAL-INSTALLER-BACKLOG.md). ```sh # macOS (bash) — exit 0 with tip if notarytool missing; never submits unless NOTARY_SUBMIT=1 ./platform/macos/menubar/notary-dry-run.sh # Windows (pwsh) — print-only / tip paths exit 0 without WiX or signtool powershell -NoProfile -ExecutionPolicy Bypass -File platform/windows/scripts/dual-ship-smoke.ps1 powershell -NoProfile -ExecutionPolicy Bypass -File platform/windows/scripts/sign-dry-run.ps1 # Hash a temp file (fill-manifest does not upload): powershell -NoProfile -ExecutionPolicy Bypass -File platform/windows/scripts/fill-manifest-sha256.ps1 \ -Path .\some-local.msi -AsName VeraePrintStamp-windows-amd64-0.3.6.msi -Format both ``` CI: unsigned full MSI [`.github/workflows/windows-msi.yml`](../.github/workflows/windows-msi.yml); 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 | Path | Notes | |------|--------| | `internal/job/job.go` | `HelperVersion`, sidecar digests | | `internal/pdfcert/` | CERT-01 build + multihash helpers | | `internal/statuslang/` | Brand notify titles + evidence rules | | `cmd/verae-print/serve.go` | IPP + GET `/queue` `/status` | | `cmd/verae-print/traystatus.go` | `tray-status` CLI + HTTP handlers | | `cmd/verae-print/menubar.go` | cross-platform menubar install | | `cmd/verae-print/pending.go` | pending queue HTML | | `platform/windows/scripts/` | tray / Startup / Start Menu | | `platform/macos/menubar/` | Swift menubar + LaunchAgent + `.app` | | `platform/macos/pkg/build-pkg.sh` | optional signed `.pkg` (certs required) | | `platform/smoke-packaging-metadata.sh` | A4 packaging smoke | | `site/printit.verae.ai/certificate.html` | CERT-01 web cert viewer (brand kit) |