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.
15 KiB
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 |
| 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 byverae-tray.ps1,install-tray-startup.ps1,build-tray-startmenu.ps1. - macOS:
platform/macos/menubar/build-menubar-app.shreadsjob.gowhenVERSIONis unset; stampsCFBundleShortVersionString/CFBundleVersion,Contents/Resources/VERSION, andpackaging-metadata.txt. - Binary build:
scripts/build-win-linux.shgrepsHelperVersionfromjob.go. - Smoke:
platform/smoke-packaging-metadata.shfails if tray/menubar scripts drift fromHelperVersion.
Override for a one-off stamp: VERSION=x.y.z (env) on the build/install
scripts. Do not invent a second version constant.
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 |
STA NotifyIcon; polls GET /status; menu opens GET /queue |
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 |
WiX-free Start Menu folder stub; also installs Startup unless -NoStartup |
CLI entry points (same behavior):
verae-print menubar
verae-print menubar --install
verae-print menubar --uninstall
Or invoke the scripts directly:
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.nsiwhen 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.
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
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
./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.plistintoContents/Resources/for optional signing; it does not applycodesignand 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):
- Build unsigned app:
./build-menubar-app.sh ~/Applications/Verae\ Menubar.app - Codesign hardened runtime + entitlements:
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" - Zip:
ditto -c -k --keepParent "$APP" /tmp/VeraeMenubar.zip - Notarize:
xcrun notarytool submit /tmp/VeraeMenubar.zip --keychain-profile "verae-notary" --wait - Staple:
xcrun stapler staple "$APP"thenxcrun stapler validate "$APP" - 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_multihashon 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
| 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 — 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 — 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. 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,extrais 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. servewiresServeQueue/ServeStatusas 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.shchecks tray scripts for accidental IPP POST coupling.
Verification
# 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.
# 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;
sign example (secret names only)
.github/workflows/windows-msi-sign.yml.example.
Digest paste after upload: PUBLISH-DIGESTS.md.
Operator enable package (secrets/VM only left):
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) |