docs: PACKAGING.md for tray, menubar, CERT, and statuslang

Document HelperVersion packaging paths and evidence rules.
This commit is contained in:
George Lambert 2026-10-13 21:46:33 -04:00
commit c2c87274be
2 changed files with 262 additions and 0 deletions

View file

@ -9,6 +9,7 @@ PrintIt desktop/CLI path: stamp documents via Verae edge billing (xVerae keys).
- [Background](#background)
- [Install](#install)
- [Usage](#usage)
- [Packaging](#packaging)
- [Contributing](#contributing)
- [License](#license)
@ -48,6 +49,12 @@ verae-print pending --html # offline queue page
# Direct chain only with VERAE_ALLOW_DIRECT_VERAE=1
```
## Packaging
Tray, menu bar, CERT-01 digests, version stamps (`HelperVersion` **0.3.6**), and IPP GET vs POST split:
- [docs/PACKAGING.md](docs/PACKAGING.md) — Windows tray (no elevated MSI), macOS LaunchAgent / unsigned `.app` / notarization (docs only), `pending` / `tray-status` / `menubar`, statuslang notify titles, hold ≠ evidence.
## Contributing
Questions: open an issue on the project Forgejo/GitHub remote, or contact Verae engineering.

255
docs/PACKAGING.md Normal file
View file

@ -0,0 +1,255 @@
# 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`.
- Smoke: `platform/smoke-packaging-metadata.sh` fails if tray/menubar 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.
## 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`.
## 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.
## 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 |