From 46fa1aa0d4e08883d608b92cbc1fd2dd1a8bbd37 Mon Sep 17 00:00:00 2001 From: George Lambert Date: Tue, 13 Oct 2026 23:23:45 -0400 Subject: [PATCH] feat: MSI full outline, silent props, HelperVersion NSIS sync, CERT fonts plan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TODO-6 L1/I1/L7/I7 — commercial + CERT next-level scaffolds. --- docs/PACKAGING.md | 1 + internal/pdfcert/fallback.go | 8 +- internal/pdfcert/pdfcert.go | 4 + internal/pdfcert/pdfcert_test.go | 66 +++++ platform/registration-pdf/build.mjs | 5 +- platform/windows/build-windows.ps1 | 26 +- platform/windows/installer-windows.nsi | 6 +- platform/windows/scripts/build-tray-msi.ps1 | 5 + platform/windows/wix/Product-Full.wxs.outline | 232 ++++++++++++++++++ platform/windows/wix/README.md | 9 + platform/windows/wix/SILENT_PROPS.md | 157 ++++++++++++ site/printit.verae.ai/fonts/.gitkeep | 0 site/printit.verae.ai/fonts/README.md | 87 +++++++ 13 files changed, 600 insertions(+), 6 deletions(-) create mode 100644 platform/windows/wix/Product-Full.wxs.outline create mode 100644 platform/windows/wix/SILENT_PROPS.md create mode 100644 site/printit.verae.ai/fonts/.gitkeep create mode 100644 site/printit.verae.ai/fonts/README.md diff --git a/docs/PACKAGING.md b/docs/PACKAGING.md index 77f6dab..3408323 100644 --- a/docs/PACKAGING.md +++ b/docs/PACKAGING.md @@ -190,6 +190,7 @@ Site page: [`site/printit.verae.ai/certificate.html`](../site/printit.verae.ai/c | 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 | 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. diff --git a/internal/pdfcert/fallback.go b/internal/pdfcert/fallback.go index 5e28f38..5d49ca7 100644 --- a/internal/pdfcert/fallback.go +++ b/internal/pdfcert/fallback.go @@ -27,6 +27,10 @@ const ( ) // Minimal fallback when Node/jsPDF is unavailable. +// +// Fonts: PDF core / library faces only (Times title, Helvetica body). +// Do not claim Fraunces embed — no AddFont/TTF path unless a licensed +// Fraunces file is wired in later (OPEN-ISSUES #7 / TODO-6 I7). func buildFallbackGofpdf(in Input, docName, created, scope, merkle, lookup string) ([]byte, error) { pdf := gofpdf.New("P", "pt", "Letter", "") pdf.SetCompression(false) @@ -51,13 +55,13 @@ func buildFallbackGofpdf(in Input, docName, created, scope, merkle, lookup strin } } - // Gold-d eyebrow (BrandingBook §12) + // Gold-d eyebrow (BrandingBook §12) — Helvetica core font (not Fraunces) pdf.SetTextColor(goldDR, goldDG, goldDB) pdf.SetFont("Helvetica", "B", 9) pdf.SetXY(margin, 92) pdf.CellFormat(pageW-margin*2, 11, "TIMESTAMP CERTIFICATE", "", 0, "C", false, 0, "") - // Title — CERT-01 serif / ink + // Title — Times core serif stand-in for CERT-01 Fraunces (no embed unless present) pdf.SetTextColor(inkR, inkG, inkB) pdf.SetFont("Times", "B", 22) pdf.SetXY(margin, 106) diff --git a/internal/pdfcert/pdfcert.go b/internal/pdfcert/pdfcert.go index 6385e6f..d9a2e72 100644 --- a/internal/pdfcert/pdfcert.go +++ b/internal/pdfcert/pdfcert.go @@ -103,6 +103,10 @@ func LookupURL(sha256, receiptID string) string { // Build returns the branded CERT-01 certificate PDF (plum/gold, final-only // evidence copy). Prefers platform/registration-pdf/build.mjs when Node is // available; otherwise gofpdf fallback. +// +// Typography: both paths use PDF standard fonts (Times title, Helvetica body). +// Fraunces is the brand serif on web CERT shells only — the issued PDF does +// not embed Fraunces unless a licensed face is explicitly added later. func Build(in Input) ([]byte, error) { created := in.CreatedAt if created == "" { diff --git a/internal/pdfcert/pdfcert_test.go b/internal/pdfcert/pdfcert_test.go index 442b701..8c4ce68 100644 --- a/internal/pdfcert/pdfcert_test.go +++ b/internal/pdfcert/pdfcert_test.go @@ -259,3 +259,69 @@ func TestFindBuildScriptNoResearchHardcode(t *testing.T) { t.Fatal("build.mjs must keep hold≠evidence disclaimer") } } + +// TestBuildUsesSystemFontsNotFraunces: issued PDF uses core Times/Helvetica; +// do not claim Fraunces embed until a licensed face is explicitly added (I7). +func TestBuildUsesSystemFontsNotFraunces(t *testing.T) { + sha := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + in := Input{ + DocumentName: "FontCheck.pdf", + SHA256: sha, + ReceiptID: "QmFontCheck", + CreatedAt: "2026-10-03T12:00:00Z", + Merkle: "99", + } + // gofpdf path — deterministic, no Node. + pdf, err := buildFallbackGofpdf(in, "FontCheck.pdf", in.CreatedAt, "GLOBAL", in.Merkle, LookupURL(sha, in.ReceiptID)) + if err != nil { + t.Fatal(err) + } + s := string(pdf) + if strings.Contains(s, "Fraunces") || strings.Contains(s, "fraunces") { + t.Fatal("PDF must not embed or name Fraunces unless a licensed face is wired") + } + // Standard 14 base fonts (gofpdf): Times title + Helvetica body. + if !strings.Contains(s, "/BaseFont /Times") && !strings.Contains(s, "/BaseFont/Times") { + if !strings.Contains(s, "Times-Bold") && !strings.Contains(s, "Times-Roman") { + t.Fatal("expected PDF core Times base font for title serif stand-in") + } + } + if !strings.Contains(s, "/BaseFont /Helvetica") && !strings.Contains(s, "/BaseFont/Helvetica") { + if !strings.Contains(s, "Helvetica") { + t.Fatal("expected PDF core Helvetica for body text") + } + } + // Full Build() path must also stay honest (node or gofpdf). + b, err := Build(in) + if err != nil { + t.Fatal(err) + } + full := string(b) + if strings.Contains(full, "Fraunces") || strings.Contains(full, "fraunces") { + t.Fatal("Build() PDF must not claim Fraunces embed") + } + // build.mjs must keep standard faces; no real addFont(...) call for Fraunces. + script := findBuildScript() + if script == "" { + t.Fatal("expected build.mjs") + } + src, err := os.ReadFile(script) + if err != nil { + t.Fatal(err) + } + body := string(src) + lower := strings.ToLower(body) + // Call-site only: addFont(…fraunces…) — prose notes may mention both words. + if strings.Contains(lower, `addfont(`) && strings.Contains(lower, "fraunces") { + // Narrow: same line or adjacent token would be an embed; bare prose is OK. + for _, line := range strings.Split(body, "\n") { + ll := strings.ToLower(line) + if strings.Contains(ll, "addfont(") && strings.Contains(ll, "fraunces") { + t.Fatal("build.mjs must not addFont(Fraunces) until licensed binary lands") + } + } + } + if !strings.Contains(body, `setFont("times"`) || !strings.Contains(body, `setFont("helvetica"`) { + t.Fatal("build.mjs should keep times/helvetica standard faces") + } +} diff --git a/platform/registration-pdf/build.mjs b/platform/registration-pdf/build.mjs index 2062c96..97be81d 100644 --- a/platform/registration-pdf/build.mjs +++ b/platform/registration-pdf/build.mjs @@ -9,6 +9,9 @@ * - no mcp.verae.ai URLs * - footer: Verae LLC · verae.ai · explorer.verae.ai * + * Fonts: jsPDF standard faces only (times title, helvetica body). Do not claim + * Fraunces embed — no addFont/TTF unless a licensed Fraunces file is wired later. + * * Usage: node build.mjs * input.json: { file, size_bytes, sha256, multihash?, blake3?, blake3_multihash?, * scope, receipt_id, created_at, merkle, lookup_url? } @@ -143,7 +146,7 @@ async function buildPdf(row) { doc.setTextColor(...goldD); doc.text("TIMESTAMP CERTIFICATE", pageW / 2, 98, { align: "center" }); - // Title — CERT-01 serif / ink + // Title — Times core serif stand-in for CERT-01 Fraunces (no embed unless present) doc.setFont("times", "bold"); doc.setFontSize(22); doc.setTextColor(...ink); diff --git a/platform/windows/build-windows.ps1 b/platform/windows/build-windows.ps1 index 41b68ca..3fd583f 100644 --- a/platform/windows/build-windows.ps1 +++ b/platform/windows/build-windows.ps1 @@ -1,12 +1,34 @@ # Build the Windows installer natively on Windows (Go + NSIS). -# Usage: powershell -ExecutionPolicy Bypass -File platform\windows\build-windows.ps1 [-Arch amd64|arm64] -param([string]$Arch = "amd64", [string]$Version = "0.3.2") +# Usage: powershell -ExecutionPolicy Bypass -File platform\windows\build-windows.ps1 [-Arch amd64|arm64] [-Version x.y.z] +# Version default: Get-VeraePackageVersion → internal/job.HelperVersion (or $env:VERSION). +param([string]$Arch = "amd64", [string]$Version = "") $ErrorActionPreference = "Stop" $Root = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).Path $Out = Join-Path $Root "dist\windows" $Stage = Join-Path $Out "stage-$Arch" $Go = (Get-Command go -ErrorAction SilentlyContinue).Source; if (-not $Go) { $Go = "C:\Program Files\Go\bin\go.exe" } $Nsis = (Get-Command makensis -ErrorAction SilentlyContinue).Source; if (-not $Nsis) { $Nsis = "${env:ProgramFiles(x86)}\NSIS\makensis.exe" } + +# Package version = internal/job.HelperVersion (single source of truth). +$common = Join-Path $PSScriptRoot "scripts\common.ps1" +if (Test-Path -LiteralPath $common) { . $common } +if (-not $Version) { + if (Get-Command Get-VeraePackageVersion -ErrorAction SilentlyContinue) { + $Version = Get-VeraePackageVersion + } elseif ($env:VERSION -and $env:VERSION.Trim()) { + $Version = $env:VERSION.Trim() + } else { + $jobGo = Join-Path $Root "internal\job\job.go" + if (Test-Path -LiteralPath $jobGo) { + $line = Select-String -Path $jobGo -Pattern 'HelperVersion\s*=\s*"([^"]+)"' -ErrorAction SilentlyContinue | + Select-Object -First 1 + if ($line -and $line.Matches.Count -gt 0) { + $Version = $line.Matches[0].Groups[1].Value + } + } + } + if (-not $Version) { $Version = "0.3.6" } # must match HelperVersion fallback +} Remove-Item -Recurse -Force $Stage -ErrorAction SilentlyContinue New-Item -ItemType Directory -Force -Path $Stage, (Join-Path $Stage "scripts") | Out-Null Push-Location $Root diff --git a/platform/windows/installer-windows.nsi b/platform/windows/installer-windows.nsi index d4196b9..44cd936 100644 --- a/platform/windows/installer-windows.nsi +++ b/platform/windows/installer-windows.nsi @@ -6,8 +6,12 @@ ; only through files in $PLUGINSDIR that are deleted immediately and are never logged. ; Logging: one concise %ProgramData%\Verae\logs\installer-