feat: fill-manifest-sha256.ps1 + CERT local font-face first
Some checks failed
windows-msi / full-msi-unsigned (push) Has been cancelled

Hash helper for DOWNLOADS-MANIFEST; certificate.html prefers fonts/*.woff2 when present.
This commit is contained in:
George Lambert 2026-10-14 00:27:11 -04:00
commit da4a0dc4e2
12 changed files with 366 additions and 82 deletions

View file

@ -35,6 +35,7 @@ Repo now includes **authoring/docs scaffolds** for the two commercial gaps that
| [`.github/workflows/windows-msi.yml`](../.github/workflows/windows-msi.yml) unsigned MSI on `windows-latest` + artifact upload | Wire org secrets and enable sign step; publish signed assets to site |
| [`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) local hash → DOWNLOADS-MANIFEST rows (linked from DUAL-SHIP + manifest.md; **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) |
**Still use for day-to-day presence (no WiX / no Apple certs):**
@ -73,6 +74,7 @@ Related but out of scope for “done” above (still backlog-adjacent): Linux `.
| **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 |
| 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 |
@ -86,7 +88,7 @@ NSIS product already covers much of the *install experience* (files under Progra
| 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; no SHA256 release job |
| **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 | `signtool` (or Azure Trusted Signing) for EXE/DLL **and** MSI; store certs in CI secrets only — never in git. **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 |
@ -104,7 +106,7 @@ NSIS product already covers much of the *install experience* (files under Progra
4. **Uncomment** + test printer/helper deferred CAs on clean VM (DUAL-SHIP VM C); keep NSIS primary until parity.
5. Authenticode on binaries + MSI (**not shipped** — enable commented sign step; secret names in workflow + `CI-SIGN-NOTARY.md`).
6. Honor silent props in live CAs + Intune sample command line proof.
7. Publish 0.3.6 artifacts; refresh site `DOWNLOADS-MANIFEST` checksums; keep smoke on HelperVersion drift.
7. Publish 0.3.6 artifacts; refresh site `DOWNLOADS-MANIFEST` checksums via `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).
---

View file

@ -182,15 +182,16 @@ Site page: [`site/printit.verae.ai/certificate.html`](../site/printit.verae.ai/c
| Item | Status |
|------|--------|
| CERT-01 layout | **Shipped** — plum/gold double frame, Fraunces title via Google Fonts, gold rule, final banner, mono digests, digests-only footer |
| 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 ships 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) — woff2 placeholder dir (empty + `.gitkeep`); binaries/CDN cutover not started |
| 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 |
@ -201,9 +202,9 @@ Cross-product siblings (not owned here): Sign `certificate.html` (SG-P1), Verify
| Done this slice | Still left |
|-----------------|------------|
| `fetch-fonts.example.sh` example + `--check` | Real woff2 + `OFL.txt` committed; `@font-face` cutover; drop Google Fonts CDN links |
| CERT sheet spacing closer to CERT-01 | Full CERT-01.html pixel match (Fraunces opsz, Cantarell body if required, Substrate extrinsic polish) |
| PACKAGING rows above | PDF Fraunces embed in `internal/pdfcert` / `registration-pdf/build.mjs` (system Times/Helvetica only today) |
| 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

View file

@ -0,0 +1,152 @@
#Requires -Version 5.1
<#
.SYNOPSIS
Compute SHA-256 of built MSI/NSIS (or companion EXE) and print DOWNLOADS-MANIFEST rows.
.DESCRIPTION
Given one or more paths to release artifacts (setup.exe, .msi, bare helper .exe),
prints SHA256SUMS-style lines matching site/printit.verae.ai/DOWNLOADS-MANIFEST.txt:
<lowercase-hex> <leaf-name>
Optionally prints a Markdown table row for DOWNLOADS-MANIFEST.md.
Does NOT upload, rewrite the site manifests, or sign. Operator order remains:
build → sign (when required) → upload → hash → paste digests over TBD.
.EXITCODES
0 At least one path hashed successfully.
1 No paths, missing file, or terminating error.
.PARAMETER Path
One or more artifact paths (MSI, NSIS setup.exe, optional bare helper).
Also accepts leftover positional args and pipeline input (strings or FileInfo).
.PARAMETER Format
txt — SHA256SUMS lines only (default; paste into DOWNLOADS-MANIFEST.txt).
md — Markdown table rows for DOWNLOADS-MANIFEST.md.
both — txt then md (md after a blank line).
.PARAMETER AsName
Override the leaf name written in the row (single Path only). Use when the
local build name differs from the CDN filename reserved in the manifest.
.EXAMPLE
powershell -NoProfile -ExecutionPolicy Bypass -File 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
.EXAMPLE
.\fill-manifest-sha256.ps1 -Path .\out\product.msi -AsName VeraePrintStamp-windows-amd64-0.3.6.msi -Format both
#>
param(
[Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[Alias("FullName", "PSPath")]
[string[]]$Path = @(),
[ValidateSet("txt", "md", "both")]
[string]$Format = "txt",
[string]$AsName = ""
)
begin {
$ErrorActionPreference = "Stop"
$collected = New-Object System.Collections.Generic.List[string]
}
process {
foreach ($p in @($Path)) {
if ($null -eq $p) { continue }
$s = "$p".Trim()
if (-not $s) { continue }
# Pipeline FileInfo often arrives as Microsoft.PowerShell.Core\FileSystem::path
if ($s -match '^Microsoft\.PowerShell\.Core\\FileSystem::(.+)$') {
$s = $Matches[1]
}
[void]$collected.Add($s)
}
}
end {
# Leftover unbound args (powershell -File script.ps1 a.msi b.exe)
if ($args -and $args.Count -gt 0) {
foreach ($a in $args) {
$s = "$a".Trim()
if ($s) { [void]$collected.Add($s) }
}
}
if ($collected.Count -eq 0) {
Write-Error "No artifact paths given. Pass -Path <msi|setup.exe|...> (does not upload)."
}
if ($AsName -and $collected.Count -gt 1) {
Write-Error "-AsName applies to a single -Path only."
}
$rows = New-Object System.Collections.Generic.List[object]
foreach ($raw in $collected) {
if (-not (Test-Path -LiteralPath $raw)) {
Write-Error "Artifact not found: $raw"
}
$item = Get-Item -LiteralPath $raw
if ($item.PSIsContainer) {
Write-Error "Path is a directory (need file): $raw"
}
$hash = (Get-FileHash -LiteralPath $item.FullName -Algorithm SHA256).Hash.ToLowerInvariant()
$leaf = if ($AsName) { $AsName.Trim() } else { $item.Name }
if (-not $leaf) {
Write-Error "Empty leaf name for $raw"
}
[void]$rows.Add([pscustomobject]@{
Hash = $hash
Name = $leaf
FullName = $item.FullName
Length = $item.Length
})
}
function Write-TxtRows {
param($Items)
foreach ($r in $Items) {
# Two spaces between digest and name — same as DOWNLOADS-MANIFEST.txt / shasum -a 256
Write-Output ("{0} {1}" -f $r.Hash, $r.Name)
}
}
function Write-MdRows {
param($Items)
foreach ($r in $Items) {
$role = "artifact"
$channel = "file"
$n = $r.Name
if ($n -match '\.msi$') {
$channel = "Full MSI"
$role = "Dual-ship / fleet path"
} elseif ($n -match '-setup\.exe$') {
$channel = "NSIS"
$role = "Windows setup"
} elseif ($n -match '^verae-print-windows-.*\.exe$') {
$channel = "Bare helper"
$role = "Optional companion EXE"
}
Write-Output ("| `{0}` | {1} | {2} | `{3}` | hashed locally (not uploaded by this script) |" -f $n, $channel, $role, $r.Hash)
}
}
Write-Host "# fill-manifest-sha256: local hash only — does not upload or edit site manifests." -ForegroundColor DarkGray
Write-Host "# Paste over TBD in DOWNLOADS-MANIFEST.txt only after the blob is on the CDN." -ForegroundColor DarkGray
switch ($Format) {
"txt" { Write-TxtRows $rows }
"md" { Write-MdRows $rows }
"both" {
Write-TxtRows $rows
Write-Output ""
Write-MdRows $rows
}
}
exit 0
}

View file

@ -189,7 +189,7 @@ GHA/Forgejo copy-paste sketch (unsigned default, notary job gated off): [`ci-not
2. **Sign** Windows PE + MSI with `signtool` (or Trusted Signing).
3. **Notarize** macOS zip/pkg with `notarytool --wait`.
4. **Staple** macOS artifacts; `stapler validate`.
5. **Checksum** + upload release assets; update `site/printit.verae.ai/DOWNLOADS-MANIFEST.txt` out-of-band or via a docs job.
5. **Checksum** + upload release assets; update `site/printit.verae.ai/DOWNLOADS-MANIFEST.txt` out-of-band (`platform/windows/scripts/fill-manifest-sha256.ps1` prints rows only — does not upload) or via a docs job.
6. Never upload raw PFX / `.p8` as workflow artifacts.
---

View file

@ -4,7 +4,7 @@
**Do not** thin or deprecate NSIS until the **thin-NSIS exit criteria checklist** (below) is fully checked — clean-VM gates, live Authenticode, silent/fleet proof, and site cutover (see [COMMERCIAL-INSTALLER-BACKLOG.md](../../../docs/COMMERCIAL-INSTALLER-BACKLOG.md), [CI-SIGN-NOTARY.md](CI-SIGN-NOTARY.md)).
**Related:** [Product-Full.wxs](Product-Full.wxs) · [SILENT_PROPS.md](SILENT_PROPS.md) · [VM-DUAL-SHIP-CHECKLIST.md](VM-DUAL-SHIP-CHECKLIST.md) (printable A/B/C) · [../installer-windows.nsi](../installer-windows.nsi) · [PACKAGING.md](../../../docs/PACKAGING.md) · site [DOWNLOADS-MANIFEST.md](../../../site/printit.verae.ai/DOWNLOADS-MANIFEST.md) (0.3.6 TBD rows).
**Related:** [Product-Full.wxs](Product-Full.wxs) · [SILENT_PROPS.md](SILENT_PROPS.md) · [VM-DUAL-SHIP-CHECKLIST.md](VM-DUAL-SHIP-CHECKLIST.md) (printable A/B/C) · [../installer-windows.nsi](../installer-windows.nsi) · [PACKAGING.md](../../../docs/PACKAGING.md) · site [DOWNLOADS-MANIFEST.md](../../../site/printit.verae.ai/DOWNLOADS-MANIFEST.md) (0.3.6 TBD rows) · [`fill-manifest-sha256.ps1`](../scripts/fill-manifest-sha256.ps1) (hash built MSI/NSIS → manifest row; no upload).
---
@ -47,10 +47,32 @@ Expected outputs (names vary by arch):
| NSIS setup | `dist/` or build output `VeraePrintStamp-windows-amd64-<ver>-setup.exe` |
| Full MSI | `platform/windows/wix/out/VeraePrintStamp-windows-amd64-<ver>.msi` |
Local SHA-256 for site manifest rows (**no upload**):
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File platform\windows\scripts\fill-manifest-sha256.ps1 `
-Path .\dist\VeraePrintStamp-windows-amd64-<ver>-setup.exe,
.\platform\windows\wix\out\VeraePrintStamp-windows-amd64-<ver>.msi
```
Paste printed digests over **TBD** in [DOWNLOADS-MANIFEST.txt](../../../site/printit.verae.ai/DOWNLOADS-MANIFEST.txt) only after the blob is on the CDN. See [DOWNLOADS-MANIFEST.md](../../../site/printit.verae.ai/DOWNLOADS-MANIFEST.md).
If WiX is missing, `build-full-msi.ps1` **exits 1** with an install tip — dual-ship **fails closed** for the MSI leg (do not claim MSI ready).
Cross-host NSIS-only: `platform/windows/build-installer.sh` (needs prebuilt Windows binaries).
### Manifest checksum rows (no upload)
After artifacts exist locally (and only after they are on the CDN when rewriting site TBD):
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File platform\windows\scripts\fill-manifest-sha256.ps1 `
-Path .\dist\VeraePrintStamp-windows-amd64-<ver>-setup.exe,
.\platform\windows\wix\out\VeraePrintStamp-windows-amd64-<ver>.msi
```
Prints `DOWNLOADS-MANIFEST.txt` lines (`<sha256> <leaf>`). `-Format md` prints Markdown table rows for [DOWNLOADS-MANIFEST.md](../../../site/printit.verae.ai/DOWNLOADS-MANIFEST.md). **Does not upload** or edit the site files.
---
## Clean-VM matrix (minimum)
@ -195,7 +217,7 @@ Thin or demote NSIS to “key-wizard offline only” (or drop from primary downl
- [ ] Site **primary** Windows download points at signed full MSI (`VeraePrintStamp-windows-*-<ver>.msi`).
- [ ] NSIS `setup.exe` linked as **alternate** only (or removed from primary CTA) — not the default button.
- [ ] `DOWNLOADS-MANIFEST.txt` checksums refreshed for the promoted MSI (and remaining NSIS if kept).
- [ ] `DOWNLOADS-MANIFEST.txt` checksums refreshed for the promoted MSI (and remaining NSIS if kept) — use [`fill-manifest-sha256.ps1`](../scripts/fill-manifest-sha256.ps1) after upload (script does **not** upload).
- [ ] Release notes / PACKAGING state MSI as primary full-product Windows install.
- [ ] OPEN-ISSUES Issue 1 **Still left** no longer lists dual-ship gate / thin-NSIS / “NSIS still primary”.
@ -254,4 +276,5 @@ Do **not** put PFX bytes or passwords in git. Do not upload the PFX as a workflo
| Sign/notary draft | [CI-SIGN-NOTARY.md](CI-SIGN-NOTARY.md) |
| Menubar CI notary example | [../../macos/menubar/ci-notarize.yml.example](../../macos/menubar/ci-notarize.yml.example) |
| Site 0.3.6 placeholders | [DOWNLOADS-MANIFEST.md](../../../site/printit.verae.ai/DOWNLOADS-MANIFEST.md) · [DOWNLOADS-MANIFEST.txt](../../../site/printit.verae.ai/DOWNLOADS-MANIFEST.txt) |
| Local SHA-256 → manifest rows | [../scripts/fill-manifest-sha256.ps1](../scripts/fill-manifest-sha256.ps1) (hash only; no upload) |
| Commercial backlog | [docs/COMMERCIAL-INSTALLER-BACKLOG.md](../../../docs/COMMERCIAL-INSTALLER-BACKLOG.md) |

View file

@ -172,6 +172,7 @@ day-to-day full product. **Not** Authenticode-signed; printer CAs remain
| `README.md` | This file |
| `../scripts/build-tray-msi.ps1` | Tray candle/light driver; WiX tip exit 0 if missing |
| `../scripts/build-full-msi.ps1` | Full StageFull + candle/light; WiX missing → exit 1 |
| `../scripts/fill-manifest-sha256.ps1` | Hash built MSI/NSIS → DOWNLOADS-MANIFEST row (no upload) |
### Relation to other Windows paths
@ -182,6 +183,7 @@ day-to-day full product. **Not** Authenticode-signed; printer CAs remain
| `../scripts/build-tray-startmenu.ps1` | WiX-free Start Menu + Startup |
| `../scripts/build-tray-msi.ps1` | Builds tray presence MSI when WiX is installed |
| `../scripts/build-full-msi.ps1` | Stages StageFull + builds full MSI when WiX is installed |
| `../scripts/fill-manifest-sha256.ps1` | Local SHA-256 rows for site DOWNLOADS-MANIFEST (no upload) |
| `../installer-windows.nsi` | Elevated **full product** setup.exe (primary until MSI validated) |
| **`wix/` (this folder)** | Tray skeleton + full Product-Full authoring |

View file

@ -118,7 +118,7 @@ UI install is fine for smoke; `/qn` proves fleet syntax.
|------|---|
| Record logs + exit codes in release notes / dual-ship folder | ☐ |
| Authenticode still left → SmartScreen warn is **not** promote-ready | ☐ |
| Update site `DOWNLOADS-MANIFEST` digests only after upload (0.3.6 rows are TBD until then) | ☐ |
| Update site `DOWNLOADS-MANIFEST` digests only after upload (0.3.6 rows are TBD until then); hash with `fill-manifest-sha256.ps1` (no upload) | ☐ |
| Thin-NSIS only when DUAL-SHIP exit checklist **A–D** all checked | ☐ |
**Overall dual-ship gate:** open / closed · notes: _______________________________

View file

@ -6,7 +6,7 @@ Human-readable companion to [`DOWNLOADS-MANIFEST.txt`](DOWNLOADS-MANIFEST.txt) (
**Last-published CDN blobs:** mostly **0.3.2** (Windows amd64 NSIS / macOS / Linux) or **0.1.2** (Windows arm64, `.deb`, bare `.zip`).
**Rule:** do not replace a checksum line until the matching artifact is built, signed (when required), uploaded, and the digest verified. `TBD` is intentional — not a shipping claim.
Related: [DUAL-SHIP.md](../../platform/windows/wix/DUAL-SHIP.md) · [VM-DUAL-SHIP-CHECKLIST.md](../../platform/windows/wix/VM-DUAL-SHIP-CHECKLIST.md) · [COMMERCIAL-INSTALLER-BACKLOG.md](../../docs/COMMERCIAL-INSTALLER-BACKLOG.md).
Related: [DUAL-SHIP.md](../../platform/windows/wix/DUAL-SHIP.md) · [VM-DUAL-SHIP-CHECKLIST.md](../../platform/windows/wix/VM-DUAL-SHIP-CHECKLIST.md) · [COMMERCIAL-INSTALLER-BACKLOG.md](../../docs/COMMERCIAL-INSTALLER-BACKLOG.md) · [`fill-manifest-sha256.ps1`](../../platform/windows/scripts/fill-manifest-sha256.ps1) (local hash → manifest row; **does not upload**).
---
@ -32,15 +32,26 @@ TBD VeraePrintStamp-windows-arm64-0.3.6.msi
TBD verae-print-windows-amd64-0.3.6.exe
```
After upload:
After upload (hash only — this helper does **not** upload):
```powershell
# Windows (preferred): SHA256SUMS-style rows for DOWNLOADS-MANIFEST.txt
powershell -NoProfile -ExecutionPolicy Bypass -File platform\windows\scripts\fill-manifest-sha256.ps1 `
-Path .\VeraePrintStamp-windows-amd64-0.3.6-setup.exe,
.\VeraePrintStamp-windows-amd64-0.3.6.msi
# -Format both → also print Markdown table rows for this file
# -AsName <cdn-leaf> when local build name ≠ reserved CDN filename
```
```bash
# From the directory that holds the release blobs:
# From the directory that holds the release blobs (POSIX equivalent):
shasum -a 256 VeraePrintStamp-windows-amd64-0.3.6-setup.exe \
VeraePrintStamp-windows-amd64-0.3.6.msi
# Paste hex digests over TBD in DOWNLOADS-MANIFEST.txt (and this table).
```
Helper: [`fill-manifest-sha256.ps1`](../../platform/windows/scripts/fill-manifest-sha256.ps1) · dual-ship runbook: [DUAL-SHIP.md](../../platform/windows/wix/DUAL-SHIP.md).
---
## Last-published (live checksums in `.txt`)
@ -60,5 +71,5 @@ Exact digests: see [`DOWNLOADS-MANIFEST.txt`](DOWNLOADS-MANIFEST.txt).
## Operator notes
1. Site product string may say **0.3.6** while CDN still serves **0.3.2** — that lag is documented on `index.html` and here.
2. Filling TBD without uploading the blob breaks mirror checks; always upload first, hash second, rewrite third.
2. Filling TBD without uploading the blob breaks mirror checks; always upload first, hash second (`fill-manifest-sha256.ps1`), rewrite third.
3. Dual-ship print checklist for clean VMs: [VM-DUAL-SHIP-CHECKLIST.md](../../platform/windows/wix/VM-DUAL-SHIP-CHECKLIST.md).

View file

@ -46,6 +46,7 @@ TBD VeraePrintStamp-windows-arm64-0.3.6.msi
# Optional companion EXE (parity with prior 0.3.x bare helper uploads):
TBD verae-print-windows-amd64-0.3.6.exe
# See also DOWNLOADS-MANIFEST.md (human table) and VM-DUAL-SHIP-CHECKLIST.md.
# Fill digests: platform/windows/scripts/fill-manifest-sha256.ps1 -Path <msi|setup.exe> (prints rows; does not upload).
56cb72d8d65c5d2e1292b0a05f235ded0a267e66530229a53980887fc5de1b72 verae-print-windows-amd64-0.3.1.exe
aa2e7f1843ccbd4d466c66e92a5460f0e3d499e6af0dcba2f93cbd49bc2012e5 verae-print-windows-amd64-0.3.2.exe
079482b38ae3b49b268863209e309bb35072a3ec136f076f0ec5062d67a7c26e verae-print_0.1.2_amd64.deb

View file

@ -8,9 +8,57 @@
<link rel="canonical" href="https://printit.verae.ai/certificate.html"/>
<link rel="stylesheet" href="brand/tokens.css"/>
<link rel="stylesheet" href="verae-ui-kit.css"/>
<link rel="preconnect" href="https://fonts.googleapis.com"/>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,600;9..144,700&amp;family=JetBrains+Mono:wght@500&amp;display=swap" rel="stylesheet"/>
<!-- Fonts: local fonts/*.woff2 first when present; else Google Fonts CDN Fraunces.
No binaries committed — drop OFL woff2 into fonts/ (see fonts/README.md). -->
<script>
(function () {
var CDN = 'https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,600;9..144,700&family=JetBrains+Mono:wght@500&display=swap';
var CANDIDATES = [
{ file: 'fonts/fraunces-opsz-wght-700.woff2', family: 'Fraunces', weight: '700' },
{ file: 'fonts/fraunces-opsz-wght-600.woff2', family: 'Fraunces', weight: '600' },
{ file: 'fonts/jetbrains-mono-500.woff2', family: 'JetBrains Mono', weight: '500' }
];
function isWoff2(buf) {
var u = new Uint8Array(buf);
return u.length >= 4 && u[0] === 0x77 && u[1] === 0x4F && u[2] === 0x46 && u[3] === 0x32;
}
function addCdn() {
var p1 = document.createElement('link');
p1.rel = 'preconnect'; p1.href = 'https://fonts.googleapis.com';
var p2 = document.createElement('link');
p2.rel = 'preconnect'; p2.href = 'https://fonts.gstatic.com'; p2.crossOrigin = '';
var l = document.createElement('link');
l.rel = 'stylesheet'; l.href = CDN;
document.head.appendChild(p1);
document.head.appendChild(p2);
document.head.appendChild(l);
}
function addLocal(faces) {
var css = faces.map(function (f) {
return '@font-face{font-family:\'' + f.family + '\';src:url(\'' + f.file + '\') format(\'woff2\');' +
'font-weight:' + f.weight + ';font-style:normal;font-display:swap}';
}).join('\n');
var s = document.createElement('style');
s.setAttribute('data-cert-fonts', 'local');
s.textContent = css;
document.head.appendChild(s);
}
Promise.all(CANDIDATES.map(function (c) {
return fetch(c.file, { headers: { Range: 'bytes=0-3' }, cache: 'force-cache' })
.then(function (r) {
if (!r.ok && r.status !== 206) return null;
return r.arrayBuffer().then(function (buf) {
return isWoff2(buf) ? c : null;
});
})
.catch(function () { return null; });
})).then(function (results) {
var found = results.filter(Boolean);
if (found.length) addLocal(found);
else addCdn();
}).catch(addCdn);
})();
</script>
<style>
body{margin:0;background:var(--canvas,#E9E6EE);color:var(--ink,#1D1534);font-family:var(--font-sans),system-ui,-apple-system,Segoe UI,sans-serif}
.toolbar{padding:12px 20px;background:var(--card,#fff);border-bottom:1px solid var(--line,#E3DEF0);display:flex;gap:10px;align-items:center;flex-wrap:wrap}
@ -208,9 +256,9 @@
Query params also work:
<span class="mono">?document=&amp;sha256=&amp;receipt=&amp;created_at=&amp;merkle=&amp;blake3=&amp;block=&amp;helper_version=</span>
Multihash / BLAKE3 lines appear <b>only when data is present</b> — never invented.
Title uses Fraunces (Google Fonts CDN) with Georgia/serif offline fallback; plum/gold frame kept.
Title uses Fraunces: local <code>fonts/*.woff2</code> via @font-face when present, else Google Fonts CDN; Georgia/serif offline fallback. Plum/gold frame kept.
Final certificates paint a live QR (qrcode.js CDN → data-URL) from the explorer lookup; offline / provisional keeps the SVG placeholder so print still works.
Residual: self-hosted Fraunces/JetBrains woff2 + full CERT-01.html pixel / PDF embed.
Residual: commit OFL woff2 binaries; full CERT-01.html pixel match; PDF Fraunces embed.
</p>
</div>
</div>

View file

@ -1,32 +1,88 @@
# CERT self-host fonts (printit.verae.ai)
Placeholder directory for **self-hosted woff2** used by the CERT-01 web
viewer (`../certificate.html`). Binaries are **not** committed yet — only
`.gitkeep` keeps the path in git.
[![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme)
## Current state
Drop-in woff2 directory for the CERT-01 web viewer (`../certificate.html`).
`certificate.html` probes this folder at load time. If any planned `.woff2`
validates (woff2 magic `wOF2`), it injects local `@font-face` and **skips**
the Google Fonts CDN. If none are present, it loads CDN Fraunces + JetBrains
Mono. Binaries are **not** committed — only `.gitkeep` keeps the path in git.
Do **not** download or commit proprietary fonts.
## Table of Contents
- [Install](#install)
- [Usage](#usage)
- [Background](#background)
- [Planned filenames](#planned-filenames)
- [Fetch scaffold](#fetch-scaffold)
- [Out of scope](#out-of-scope)
- [Status](#status)
- [Contributing](#contributing)
- [License](#license)
## Install
Place OFL-licensed files next to this README (no proprietary fonts):
```sh
# From this directory (site/printit.verae.ai/fonts/)
cp /path/to/fraunces-opsz-wght-700.woff2 .
cp /path/to/fraunces-opsz-wght-600.woff2 . # optional weight 600
cp /path/to/jetbrains-mono-500.woff2 . # optional mono
cp /path/to/OFL.txt . # required beside binaries
./fetch-fonts.example.sh --check # exit 0 when required set present
```
Deploy with the static site (`rsync` to `/var/www/printit.verae.ai/` — see
parent `README.md`). No HTML edit is required: the viewer auto-selects local
vs CDN.
## Usage
```sh
# No woff2 yet → certificate.html uses Google Fonts CDN Fraunces
open ../certificate.html
# After drop-in → local @font-face only (CDN not requested)
./fetch-fonts.example.sh --check && open ../certificate.html
# Smoke offline: block fonts.googleapis.com / fonts.gstatic.com;
# title must still render Fraunces from local woff2 (or Georgia if missing).
```
How drop-in works:
1. Obtain **OFL-licensed** Fraunces (and optional JetBrains Mono) as woff2
from upstream or a subsetted build (title glyphs only is enough for CERT).
2. Name files exactly as in [Planned filenames](#planned-filenames).
3. Commit `OFL.txt` / `LICENSE` next to the binaries (keep copyright notices).
4. Open `certificate.html` — head script HEADs each candidate, checks woff2
magic (avoids Caddy `try_files` HTML false-positives), injects
`@font-face` for hits, otherwise appends the CDN `<link>`.
5. Keep the Georgia / Times New Roman / serif stack as final fallback in CSS.
Do **not** uncomment network fetches in `fetch-fonts.example.sh` until license
review. This tree must not ship proprietary font files.
## Background
| Surface | Fonts today | Offline fallback |
|---------|-------------|------------------|
| `certificate.html` title | **Fraunces** 600/700 via Google Fonts CDN | Georgia / Times New Roman / serif |
| Mono digests | **JetBrains Mono** 500 via same CDN | ui-monospace / SF Mono / Menlo |
| `certificate.html` title | Local `fonts/*.woff2` if present, else **Fraunces** CDN | Georgia / Times New Roman / serif |
| Mono digests | Local mono woff2 if present, else **JetBrains Mono** CDN | ui-monospace / SF Mono / Menlo |
| UI kit (`verae-ui-kit.css`) | Inter + Material Symbols Rounded CDN | system stacks in `brand/tokens.css` |
| Issued PDF (`internal/pdfcert`) | System / library serif — **not** Fraunces embed | N/A (separate from web) |
Design source of truth: `verae-2026-design/ui-designs/src/html/CERT-01.html`
(tokens `--font-serif: 'Fraunces'`; design-kit `src/fonts/` currently ships
Cantarell + Material Symbols TTF only — **no Fraunces binary in-repo yet**).
(tokens `--font-serif: 'Fraunces'`; design-kit `src/fonts/` ships Cantarell +
Material Symbols TTF only — **no Fraunces binary in-repo yet**).
**Not claimed:** full CERT-01.html pixel match, PDF Fraunces embed, or
self-hosted production fonts.
production binaries in git.
## Goal
Serve brand serif (and optional mono) from this origin so the certificate
viewer works without `fonts.googleapis.com` / `fonts.gstatic.com`, and
print/PDF-from-browser paths stay consistent offline.
## Planned files (when licensed binaries land)
## Planned filenames
```
fonts/
@ -39,53 +95,25 @@ fonts/
OFL.txt / LICENSE # required license text beside binaries
```
### Fetch scaffold (TODO-7 A7)
Exact names must stay stable once wired so deploys and the head probe match.
[`fetch-fonts.example.sh`](./fetch-fonts.example.sh) — **example only**. Comment-only
upstream URLs (google/fonts OFL trees, gstatic discovery). Does not curl until
an operator uncomments a pinned-commit block after license review. Smoke:
## Fetch scaffold
```bash
[`fetch-fonts.example.sh`](./fetch-fonts.example.sh) — **example only**.
Comment-only upstream URLs (google/fonts OFL trees). Does not curl until an
operator uncomments a pinned-commit block after license review.
```sh
./fetch-fonts.example.sh # prints instructions; no network
./fetch-fonts.example.sh --check # exit 1 until woff2 + OFL.txt present
```
Exact filenames may follow upstream release naming; keep them stable once
wired so deploys and cache busting stay simple.
## How to wire (later — do not claim done until files exist)
1. Obtain **OFL-licensed** Fraunces (and optional JetBrains Mono) woff2 from
the upstream release or a subsetted build (title glyphs only is enough
for CERT).
2. Drop files into this directory; commit license text next to them.
3. Add local `@font-face` in `certificate.html` (or a small
`fonts/cert-fonts.css`):
```css
@font-face {
font-family: 'Fraunces';
src: url('/fonts/fraunces-opsz-wght-700.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
}
```
4. Remove the three Google Fonts `<link>` tags from `certificate.html`
head once local faces load cleanly.
5. Keep the Georgia/serif stack as final fallback.
6. Deploy with the rest of the static site (`rsync` to
`/var/www/printit.verae.ai/` — see parent `README.md`).
7. Smoke: open `/certificate.html` with network blocked; title must still
render serif (local woff2 or Georgia).
## Out of scope here
## Out of scope
| Item | Owner / later |
|------|----------------|
| PDF CERT Fraunces embed | **I7 done:** pdfcert/build.mjs note + test — Times/Helvetica only until embed lands |
| Inter / Material Symbols self-host for whole site | optional; CERT title is the residual gap called out in OPEN-ISSUES #7 |
| Inter / Material Symbols self-host for whole site | optional; CERT title is OPEN-ISSUES #7 residual |
| Cantarell from design-kit TTFs | GNOME mock only; not required for CERT-01 web title |
| Pixel-perfect CERT-01.html match | Still later (OPEN-ISSUES Advanced) |
@ -95,7 +123,23 @@ wired so deploys and cache busting stay simple.
|------|-------|
| Placeholder dir + this plan | **Done** (L7) |
| `fetch-fonts.example.sh` placeholders | **Done** (TODO-7 A7) — no binaries fetched |
| Web cert spacing toward CERT-01 | **Closer** (title-block 26/18, pad 56×64, rule 2×120, mh 2×6, verify center) — not full pixel |
| woff2 binaries + LICENSE | Not started |
| `@font-face` + drop CDN links | Not started |
| Pixel match / PDF embed claim | Not started — do not claim |
| Web cert spacing toward CERT-01 | **Closer** — not full pixel |
| Local-first `@font-face` else CDN | **Done** — `certificate.html` probes `fonts/*.woff2` |
| Drop-in docs (this README) | **Done** |
| woff2 binaries + LICENSE in git | **Not started** — operator drop-in only |
| Pixel match / PDF embed claim | **Not started** — do not claim |
## Contributing
Questions: Verae eng / printit site owners. PRs that add **OFL** woff2 +
`OFL.txt` with stable planned names are welcome. PRs that vendor proprietary
fonts or strip copyright notices will be rejected. Do not enable the example
fetch script in CI.
## License
Documentation in this directory: UNLICENSED © Verae LLC.
Font binaries, when added, remain under their upstream licenses (typically
SIL Open Font License 1.1 for Fraunces and JetBrains Mono). Ship `OFL.txt`
beside any binary. This directory does not re-license font files.

View file

@ -5,8 +5,8 @@
# block and re-run. Binaries stay uncommitted until OFL/LICENSE is beside them.
#
# Target dir: this script's directory (site/printit.verae.ai/fonts/).
# Wire-up: see README.md in this folder; certificate.html still uses CDN until
# local @font-face + woff2 land.
# Wire-up: see README.md. certificate.html probes this dir at load time —
# any valid planned woff2 → local @font-face; none → Google Fonts CDN.
#
# License reminder:
# Fraunces — SIL Open Font License 1.1 (OFL)
@ -92,6 +92,6 @@ echo "No downloads performed."
echo "1. Pin a google/fonts commit or upstream release."
echo "2. Uncomment a curl block after license review."
echo "3. Place woff2 + OFL.txt here; run: $0 --check"
echo "4. Add @font-face in certificate.html; drop Google Fonts <link> tags."
echo "4. Reload certificate.html — local @font-face auto-selects; CDN skipped."
echo
echo "Status: placeholder only — binaries not started (MULTI-AGENT-TODO-7 A7)."
echo "Status: drop-in ready — binaries not committed (OPEN-ISSUES #7 still left)."