diff --git a/docs/COMMERCIAL-INSTALLER-BACKLOG.md b/docs/COMMERCIAL-INSTALLER-BACKLOG.md index 09c0d5a..ddfcdcd 100644 --- a/docs/COMMERCIAL-INSTALLER-BACKLOG.md +++ b/docs/COMMERCIAL-INSTALLER-BACKLOG.md @@ -16,6 +16,19 @@ Repo now includes **authoring/docs scaffolds** for the two commercial gaps that |----------|------|------------|----------------| | **WiX tray presence MSI skeleton** | [`platform/windows/wix/`](../platform/windows/wix/) — `VeraeTrayShortcuts.wxs`, `VeraeTrayShortcutsFragment.wxs`, [`README.md`](../platform/windows/wix/README.md); driver [`scripts/build-tray-msi.ps1`](../platform/windows/scripts/build-tray-msi.ps1) | Non-elevated **per-user** WiX v3 product + shortcut fragment: stages `verae-tray.ps1`, Start Menu `Programs\Verae\Verae Tray.lnk`, Startup `Verae Tray.lnk`. **ProductVersion** wired from `Get-VeraePackageVersion` → `internal/job.HelperVersion` (or `VERSION=` / `-Version`). `build-tray-msi.ps1` runs **candle/light** when present; otherwise prints WiX install tip (exit 0). | Full elevated commercial MSI (printers, Program Files, Authenticode, NSIS parity, CI-signed MSI in `dist/`). No MSI binary without the WiX toolset on the build host. | | **Menubar notarize checklist** | [`platform/macos/menubar/NOTARIZE-CHECKLIST.md`](../platform/macos/menubar/NOTARIZE-CHECKLIST.md) | Operator step list: unsigned build → codesign hardened runtime → `ditto` zip → `notarytool` (keychain profile **or** API key placeholders) → staple → `spctl`. No secrets. | Automated sign/notarize in `build-menubar-app.sh`, stapled `.app.zip`/DMG release, or CI keychain import. | +| **Full elevated MSI stub (TODO-7 A1)** | [`platform/windows/wix/Product-Full.wxs`](../platform/windows/wix/Product-Full.wxs) (+ outline, [`SILENT_PROPS.md`](../platform/windows/wix/SILENT_PROPS.md), [`CI-SIGN-NOTARY.md`](../platform/windows/wix/CI-SIGN-NOTARY.md)) | Commented WiX v3 **Product** starter: perMachine/elevated, Feature/ComponentGroup placeholders (bin/scripts/docs/Start Menu), silent public props, distinct UpgradeCode from tray. **candle** should parse; **light** needs `StageFull` binaries. CI sketch: Authenticode + notarytool env names only. | Shippable MSI artifact, deferred printer CAs scheduled, Authenticode in CI, Intune-proven silent install, NSIS replacement. | + +### TODO-7 (A1) — what landed vs still open + +| Done in TODO-7 A1 | Still left (Issue 1 commercial) | +|-------------------|----------------------------------| +| `Product-Full.wxs` candle-parseable stub from outline | `build-full-msi.ps1` staging + light → real `VeraePrintStamp-windows-*.msi` in dist | +| `CI-SIGN-NOTARY.md` GHA/Forgejo draft (secret **names** only) | Live CI: signtool / Trusted Signing + notarytool staple on runners | +| `SILENT_PROPS.md` msiexec one-liner + expanded log example | Properties honored by scheduled CAs (config write, skip printers/key) | +| This backlog row | Printer deferred CA parity with NSIS; dual-ship validation; thin NSIS | +| | macOS menubar automated notarize (checklist exists; script default still unsigned) | +| | Version-default drift fix across build-windows / build-pkg / NSIS | +| | Clean-VM Gatekeeper + Intune smoke proof | **Still use for day-to-day presence (no WiX / no Apple certs):** Windows `scripts/build-tray-startmenu.ps1` + `install-tray-startup.ps1`; macOS `verae-print menubar --install` / unsigned `build-menubar-app.sh`. Full product install remains NSIS (`installer-windows.nsi`) and macOS `pkg/build-pkg.sh`. Tray MSI skeleton build: `scripts/build-tray-msi.ps1` (optional WiX). @@ -147,8 +160,8 @@ NSIS product already covers much of the *install experience* (files under Progra | Windows tray / Startup / Start Menu (user) | Yes | Yes for **presence**; not enterprise MSI | | Windows NSIS `setup.exe` | Yes | **Partial** — full GUI install; needs Authenticode + version align; not MSI | | Windows **MSI** (WiX) tray presence skeleton | **Yes** (`.wxs` + `build-tray-msi.ps1`; ProductVersion ← HelperVersion) | **No** — scaffold only; MSI only when candle/light installed; not full product MSI | -| Windows **MSI** full product (NSIS parity) | **No** | **No** | -| Windows Authenticode pipeline | **No** (in-repo) | **No** | +| Windows **MSI** full product (NSIS parity) | **Stub** (`Product-Full.wxs` TODO-7 A1; candle-oriented) | **No** — no StageFull light artifact / CA / sign | +| Windows Authenticode pipeline | **Draft doc** (`wix/CI-SIGN-NOTARY.md`) | **No** — no live signtool job | | macOS menu bar LaunchAgent | Yes | Yes (dev/ops) | | macOS unsigned menubar `.app` | Yes | **No** (Gatekeeper) | | macOS **signed+notarized `.pkg`** script | Yes | **Near** — needs version sync, CI, clean-VM proof | @@ -163,6 +176,9 @@ NSIS product already covers much of the *install experience* (files under Progra - **[PACKAGING.md](PACKAGING.md)** — tray, menubar, unsigned `.app`, notarization steps (docs), CERT-01, statuslang, IPP split, smoke tests - [`platform/windows/`](../platform/windows/) — NSIS + tray scripts - [`platform/windows/wix/`](../platform/windows/wix/) + [`build-tray-msi.ps1`](../platform/windows/scripts/build-tray-msi.ps1) — per-user tray MSI skeleton; ProductVersion ← HelperVersion +- [`platform/windows/wix/Product-Full.wxs`](../platform/windows/wix/Product-Full.wxs) — elevated full-product MSI stub (TODO-7 A1) +- [`platform/windows/wix/CI-SIGN-NOTARY.md`](../platform/windows/wix/CI-SIGN-NOTARY.md) — CI Authenticode + notarytool sketch +- [`platform/windows/wix/SILENT_PROPS.md`](../platform/windows/wix/SILENT_PROPS.md) — msiexec `/qn` property contract - [`platform/macos/pkg/`](../platform/macos/pkg/) — commercial `.pkg` build - [`platform/macos/menubar/`](../platform/macos/menubar/) — menu bar + unsigned `.app` + notarize docs/checklist - [`platform/macos/menubar/NOTARIZE-CHECKLIST.md`](../platform/macos/menubar/NOTARIZE-CHECKLIST.md) — menubar notarytool step list diff --git a/docs/PACKAGING.md b/docs/PACKAGING.md index 3408323..70ca7cc 100644 --- a/docs/PACKAGING.md +++ b/docs/PACKAGING.md @@ -191,10 +191,20 @@ Site page: [`site/printit.verae.ai/certificate.html`](../site/printit.verae.ai/c | 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 | +| 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 | +|-----------------|------------| +| `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) | + ## statuslang notify titles Source: [`internal/statuslang`](../internal/statuslang/status.go). Tray, diff --git a/platform/windows/wix/CI-SIGN-NOTARY.md b/platform/windows/wix/CI-SIGN-NOTARY.md new file mode 100644 index 0000000..84fed6f --- /dev/null +++ b/platform/windows/wix/CI-SIGN-NOTARY.md @@ -0,0 +1,206 @@ +# CI — Authenticode (Windows) + notarytool (macOS) + +**Status:** draft sketch only. No live pipeline, no secrets in git. +**Audience:** release engineers wiring GitHub Actions or Forgejo Actions. +**Related:** [`Product-Full.wxs`](Product-Full.wxs), [`SILENT_PROPS.md`](SILENT_PROPS.md), +[`docs/COMMERCIAL-INSTALLER-BACKLOG.md`](../../../docs/COMMERCIAL-INSTALLER-BACKLOG.md), +[`platform/macos/menubar/NOTARIZE-CHECKLIST.md`](../../macos/menubar/NOTARIZE-CHECKLIST.md), +[`platform/macos/pkg/build-pkg.sh`](../../macos/pkg/build-pkg.sh). + +Do **not** commit PFX, passwords, App Store Connect `.p8`, or real Team IDs used as secrets. +All credential names below are **environment / secret store keys** only. + +--- + +## Goals + +| Artifact | Sign | Notarize / trust | +|----------|------|------------------| +| `verae-print.exe`, `verae-printhelper.exe` | Authenticode (`signtool`) | SmartScreen reputation over time | +| Full / tray **MSI** | Authenticode on the `.msi` | Same | +| macOS `.pkg` / menubar `.app` | Developer ID | `notarytool` + `stapler` | + +Primary product install remains **NSIS** until `Product-Full.wxs` lights + signs cleanly. +macOS **pkg** path already supports notary when operator `SIGNING_ENV` is set; CI should mirror that with secret injection. + +--- + +## Secret / env names (placeholders) + +### Windows Authenticode + +| Env name | Purpose | +|----------|---------| +| `WINDOWS_CERT_PFX_BASE64` | Base64 of code-signing PFX (CI import only) | +| `WINDOWS_CERT_PFX_PASSWORD` | PFX password | +| `WINDOWS_CERT_THUMBPRINT` | Optional: thumbprint when cert is pre-installed on runner | +| `WINDOWS_SIGN_TIMESTAMP_URL` | RFC 3161 TSA URL (e.g. DigiCert / Sectigo public TSA) | +| `WINDOWS_TRUSTED_SIGNING_*` | Optional Azure Trusted Signing — if used, prefer over PFX | + +Never echo these values in logs. Prefer OIDC → cloud KMS/Trusted Signing when available. + +### Apple notary + Developer ID + +| Env name | Purpose | +|----------|---------| +| `APPLE_API_KEY_P8_BASE64` | App Store Connect API key `.p8` (base64) | +| `APPLE_API_KEY_ID` | Key ID | +| `APPLE_API_ISSUER_ID` | Issuer UUID | +| `APPLE_TEAM_ID` | Team ID for codesign identity selection | +| `APPLE_CODESIGN_IDENTITY` | e.g. `Developer ID Application: … (TEAMID)` | +| `APPLE_INSTALLER_IDENTITY` | e.g. `Developer ID Installer: … (TEAMID)` | +| `NOTARY_KEYCHAIN_PROFILE` | Optional laptop path name (`verae-notary`); CI usually uses API key trio above | + +Match names already used in operator `signing.env` where possible; map in the workflow, do not rename product scripts casually. + +--- + +## GitHub Actions sketch (Windows MSI + sign) + +```yaml +# .github/workflows/windows-msi-sign.yml — DRAFT, not enabled +name: windows-msi-sign +on: + workflow_dispatch: + # push: + # tags: ["printstamp-v*"] + +jobs: + msi: + runs-on: windows-latest + permissions: + contents: read + # id-token: write # if using Azure OIDC Trusted Signing + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version-file: apps/PrintToVeraeTimeStamp/go.mod + # adjust path if workflow lives inside the app repo root + + - name: Build Windows binaries + shell: pwsh + working-directory: apps/PrintToVeraeTimeStamp # or repo root + run: | + # Prefer existing build-windows.ps1 / cross-build; stage under platform/windows/wix/StageFull + # go build → StageFull\bin\verae-print.exe + verae-printhelper.exe + # copy scripts + LICENSE + README-WINDOWS.txt into StageFull + Write-Host "TODO: wire build-windows.ps1 staging for Product-Full.wxs" + + - name: Install WiX toolset + shell: pwsh + run: | + # choco install wixtoolset --version 3.14.x OR use preinstalled image + Write-Host "TODO: ensure candle/light on PATH" + + - name: candle + light Product-Full + shell: pwsh + run: | + # candle -dProductVersion=… -dStageDir=… Product-Full.wxs + # light → out\VeraePrintStamp-windows-amd64-.msi + # Expect failure until StageFull complete — see Product-Full.wxs header + Write-Host "TODO: invoke candle/light" + + - name: Import code-signing cert + shell: pwsh + env: + WINDOWS_CERT_PFX_BASE64: ${{ secrets.WINDOWS_CERT_PFX_BASE64 }} + WINDOWS_CERT_PFX_PASSWORD: ${{ secrets.WINDOWS_CERT_PFX_PASSWORD }} + run: | + # $bytes = [Convert]::FromBase64String($env:WINDOWS_CERT_PFX_BASE64) + # [IO.File]::WriteAllBytes("$env:RUNNER_TEMP\codesign.pfx", $bytes) + # Import-PfxCertificate -FilePath ... -CertStoreLocation Cert:\CurrentUser\My -Password ... + Write-Host "TODO: import PFX; never Upload-Artifact the PFX" + + - name: signtool Authenticode + shell: cmd + env: + WINDOWS_SIGN_TIMESTAMP_URL: ${{ vars.WINDOWS_SIGN_TIMESTAMP_URL }} + run: | + REM signtool sign /fd SHA256 /tr %WINDOWS_SIGN_TIMESTAMP_URL% /td SHA256 /a ^ + REM path\to\verae-print.exe path\to\verae-printhelper.exe path\to\*.msi + echo TODO: signtool after binaries + MSI exist + + - name: Checksums + shell: pwsh + run: | + # Get-FileHash … SHA256 | Out-File SHA256SUMS + Write-Host "TODO: publish MSI + SHA256SUMS as release assets" +``` + +**Forgejo Actions:** same steps; replace `${{ secrets.* }}` with Forgejo secret syntax (`secrets.WINDOWS_CERT_PFX_BASE64`, etc.) and `runs-on` with your Windows label. Keep secret **names** identical so docs stay portable. + +--- + +## GitHub Actions sketch (macOS notary) + +```yaml +# .github/workflows/macos-notarize.yml — DRAFT, not enabled +name: macos-notarize +on: + workflow_dispatch: + +jobs: + notarize: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + + - name: Materialize notary API key + env: + APPLE_API_KEY_P8_BASE64: ${{ secrets.APPLE_API_KEY_P8_BASE64 }} + APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} + run: | + # mkdir -p "$RUNNER_TEMP/private_keys" + # echo "$APPLE_API_KEY_P8_BASE64" | base64 -d > "$RUNNER_TEMP/private_keys/AuthKey_${APPLE_API_KEY_ID}.p8" + # chmod 600 ... + echo "TODO: write .p8 outside workspace; delete in always() step" + + - name: Codesign + pkgbuild path + env: + APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} + APPLE_API_ISSUER_ID: ${{ secrets.APPLE_API_ISSUER_ID }} + APPLE_CODESIGN_IDENTITY: ${{ secrets.APPLE_CODESIGN_IDENTITY }} + APPLE_INSTALLER_IDENTITY: ${{ secrets.APPLE_INSTALLER_IDENTITY }} + # NOTARY_KEY_PATH / KEY_ID / ISSUER_ID as expected by build-pkg.sh + run: | + # Prefer platform/macos/pkg/build-pkg.sh with SIGNING_ENV generated from secrets + # or menubar NOTARIZE-CHECKLIST.md steps for accessory .app + echo "TODO: call build-pkg.sh; notarytool submit --wait; stapler staple" + + - name: Gatekeeper smoke (signed host only) + run: | + # spctl --assess --type install -vv path/to.pkg || true + # xcrun stapler validate path/to.pkg + echo "TODO: fail job on assess failure once pipeline is real" +``` + +Menubar-only accessory: follow [`NOTARIZE-CHECKLIST.md`](../../macos/menubar/NOTARIZE-CHECKLIST.md) (`ditto` zip → `notarytool` → staple app). + +--- + +## Ordering (recommended) + +1. **Build unsigned** artifacts on CI (MSI light needs StageFull; pkg can already build unsigned). +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. +6. Never upload raw PFX / `.p8` as workflow artifacts. + +--- + +## What this draft does **not** do + +- Enable a workflow file in-repo (operators copy/adapt). +- Provide real certs, Team IDs, or TSA URLs. +- Replace NSIS as primary Windows download. +- Automate menubar notarization inside `build-menubar-app.sh` (still checklist + backlog). +- Claim SmartScreen reputation or Gatekeeper green without clean-VM proof. + +--- + +## License + +UNLICENSED © Verae — CI sketch only; not a redistributable binary. diff --git a/platform/windows/wix/Product-Full.wxs b/platform/windows/wix/Product-Full.wxs new file mode 100644 index 0000000..1931ccd --- /dev/null +++ b/platform/windows/wix/Product-Full.wxs @@ -0,0 +1,287 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platform/windows/wix/Product-Full.wxs.outline b/platform/windows/wix/Product-Full.wxs.outline index e17dc06..1cc78ca 100644 --- a/platform/windows/wix/Product-Full.wxs.outline +++ b/platform/windows/wix/Product-Full.wxs.outline @@ -1,6 +1,6 @@ # Product-Full.wxs.outline — full elevated Print Stamp MSI (scaffold) -**Status:** authoring outline only. Not a buildable `.wxs`. Does not produce MSI. +**Status:** outline + **TODO-7 A1 stub** [`Product-Full.wxs`](Product-Full.wxs) (candle-oriented; light needs StageFull). Does not produce a shipping MSI yet. **Parity target:** [`../installer-windows.nsi`](../installer-windows.nsi) (elevated full product). **Not this package:** tray-only skeleton [`VeraeTrayShortcuts.wxs`](VeraeTrayShortcuts.wxs) (per-user, non-elevated). **Version:** `ProductVersion` ← `Get-VeraePackageVersion` → `internal/job.HelperVersion` (or `VERSION=`). @@ -204,7 +204,7 @@ Same sources as [`../build-windows.ps1`](../build-windows.ps1) stage (Go build + | Per-user tray-only MSI | `VeraeTrayShortcuts.wxs` + `build-tray-msi.ps1` | | Authenticode / signtool | CI secrets; backlog | | NSIS GUI key wizard pages | Interactive NSIS until MSI UI or always-silent + first-run | -| Real `.wxs` Product element / GUIDs locked | Future `Product-Full.wxs` (not this outline) | +| Real `.wxs` Product element / GUIDs locked | Starter: [`Product-Full.wxs`](Product-Full.wxs) (TODO-7 A1); lock GUIDs after first light success | | MSIX / Print Support App | Deferred | --- diff --git a/platform/windows/wix/SILENT_PROPS.md b/platform/windows/wix/SILENT_PROPS.md index 521715d..889d3a8 100644 --- a/platform/windows/wix/SILENT_PROPS.md +++ b/platform/windows/wix/SILENT_PROPS.md @@ -9,18 +9,28 @@ ## msiexec pattern +**One-liner (fleet default — skip key UI, PrintIt/zapier channel):** + +```bat +msiexec /i VeraePrintStamp-windows-amd64-0.3.6.msi /qn /norestart VERAE_SKIP_KEY_SETUP=1 VERAE_CHANNEL=zapier VERAE_INSTALL_API_URL=https://customer.verae.ai +``` + +**Expanded (same props + IPP loopback + verbose log):** + ```bat msiexec /i VeraePrintStamp-windows-amd64-0.3.6.msi /qn /norestart ^ VERAE_SKIP_KEY_SETUP=1 ^ VERAE_CHANNEL=zapier ^ VERAE_INSTALL_API_URL=https://customer.verae.ai ^ - VERAE_IPP_ADDR=127.0.0.1:8631 + VERAE_IPP_ADDR=127.0.0.1:8631 ^ + /L*v %TEMP%\verae-msi.log ``` - `/qn` — no UI (required for “silent”). - `/norestart` — printers/helper must not force reboot; log if reboot pending. - Log (ops): `msiexec /i … /qn /L*v %TEMP%\verae-msi.log`. - Uninstall: `msiexec /x {ProductCode} /qn` or ARP. +- Full MSI stub authoring: [`Product-Full.wxs`](Product-Full.wxs) (candle OK; light needs StageFull). All names below are **public** Windows Installer properties (uppercase) unless marked private. @@ -143,7 +153,7 @@ msiexec /i VeraeTray-VERSION-peruser.msi /qn VERAE_IPP_ADDR=127.0.0.1:8631 ## Implementation checklist (Wave 2+ / I1-adjacent) -- [ ] Declare properties in `Product-Full.wxs` with the defaults above. +- [x] Declare properties in `Product-Full.wxs` with the defaults above. (TODO-7 A1 stub) - [ ] Condition key-related CAs on `VERAE_SKIP_KEY_SETUP<>"1"`. - [ ] Condition printer CA on `VERAE_SKIP_PRINTERS<>"1"`. - [ ] Persist channel + portal URL for helper/CLI (config file or HKLM non-secret values only). diff --git a/site/printit.verae.ai/certificate.html b/site/printit.verae.ai/certificate.html index 35be43f..babd59f 100644 --- a/site/printit.verae.ai/certificate.html +++ b/site/printit.verae.ai/certificate.html @@ -21,54 +21,58 @@ .btn.secondary{background:var(--card,#fff);color:var(--plum,#531B59);border-color:#CDBFD4} .btn.primary{background:var(--plum,#531B59);color:#fff} .btn.tonal{background:var(--plum-100,#EBD9EE);color:var(--plum-d,#3E1343);border:0} - .wrap{max-width:900px;margin:0 auto;padding:24px 16px 48px} - /* CERT-01 paper sheet — plum outer + gold inner frame */ + /* CERT-01 paper ~816px content column (design mock) */ + .wrap{max-width:816px;margin:0 auto;padding:24px 16px 48px} + /* CERT-01 paper sheet — plum 1.5px outer + gold 0.5px inner (inset 22/28 mock) */ .sheet{ background:#fff;position:relative;border-radius:4px; - box-shadow:0 12px 40px rgba(30,10,40,.18),0 2px 8px rgba(30,10,40,.08); - padding:48px 56px 56px;min-height:720px; + /* content pad matches design 56×64 inside double frame */ + padding:56px 64px;min-height:720px; box-shadow: - 0 12px 40px rgba(30,10,40,.18), + 0 12px 40px rgba(30,10,40,.25), + 0 2px 8px rgba(30,10,40,.12), inset 0 0 0 1.5px var(--plum,#531B59), - inset 0 0 0 6px #fff, - inset 0 0 0 6.5px var(--gold,#C4A85A); + inset 0 0 0 7.5px #fff, + inset 0 0 0 8px var(--gold,#C4A85A); } .head{display:flex;justify-content:space-between;align-items:flex-start;gap:16px} .logo-row{display:flex;align-items:center;gap:10px} - .logo-row img{height:36px;width:auto;display:block} - .mark{width:36px;height:36px;border-radius:9px;background:linear-gradient(140deg,var(--plum,#531B59),var(--violet,#6B3FD6));display:grid;place-items:center;color:#fff;font-weight:800;font-size:13px;flex:none} + .logo-row img{height:40px;width:auto;display:block} + .mark{width:40px;height:40px;border-radius:9px;background:linear-gradient(140deg,var(--plum,#531B59),var(--violet,#6B3FD6));display:grid;place-items:center;color:#fff;font-weight:800;font-size:13px;flex:none} .cert-meta{text-align:right;font-size:11px;color:var(--muted,#5E5470);line-height:1.45} /* Title: Fraunces via Google Fonts CDN; Georgia/Times offline-ish fallback */ + /* Design: font 700 26px serif; title block margin 26px 0 18px */ .title{ - text-align:center;margin:28px 0 0; + text-align:center;margin:26px 0 0; font-family:'Fraunces',var(--font-serif,Georgia,serif),Georgia,'Times New Roman',Times,serif; font-weight:700;font-size:clamp(22px,3.2vw,26px);line-height:1.25;letter-spacing:-0.01em;color:var(--ink,#1D1534); } - .subtitle{text-align:center;font-size:12.5px;color:var(--muted,#5E5470);margin:8px auto 0;max-width:34rem;line-height:1.45} - .rule{height:2px;background:var(--gold,#C4A85A);width:120px;margin:14px auto 0;border:0} + .subtitle{text-align:center;font-size:12.5px;color:var(--muted,#5E5470);margin:6px auto 0;max-width:34rem;line-height:1.45} + /* Gold rule under title — CERT-01 weight 2px × 120px */ + .rule{height:2px;background:var(--gold,#C4A85A);width:120px;margin:14px auto 0;border:0;border-radius:1px} .banner{ - margin-top:20px;border:2px solid var(--ok,#0E6B3A);background:var(--ok-bg,#E3F5EA); + margin-top:18px;border:2px solid var(--ok,#0E6B3A);background:var(--ok-bg,#E3F5EA); border-radius:10px;padding:12px 16px;display:flex;gap:12px;align-items:center; } .banner.provisional{border-color:var(--pend-bd,#D5DAE1);background:var(--pend-bg,#EEF0F3)} - .banner .check{font-size:28px;line-height:1;color:var(--ok,#0E6B3A);font-weight:700} + .banner .check{font-size:34px;line-height:1;color:var(--ok,#0E6B3A);font-weight:700} .banner.provisional .check{color:var(--pend,#475467)} - .banner .bt{font:700 15px/1.3 var(--font-sans),system-ui;color:var(--ok,#0E6B3A)} + .banner .bt{font:700 16px/1.3 var(--font-sans),system-ui;color:var(--ok,#0E6B3A)} .banner.provisional .bt{color:var(--pend,#475467)} .banner .bs{font-size:12px;color:#1D4D33;margin-top:2px} .banner.provisional .bs{color:var(--muted,#5E5470)} - .kv{display:grid;grid-template-columns:150px 1fr;gap:10px 16px;margin-top:22px;font-size:13px} + .kv{display:grid;grid-template-columns:150px 1fr;gap:10px 16px;margin-top:20px;font-size:13px} .kv dt{color:var(--muted,#5E5470);font-weight:600;font-size:12px;padding-top:2px} .kv dd{margin:0;word-break:break-all;line-height:1.45} .mono{font-family:var(--font-mono),'JetBrains Mono',ui-monospace,SF Mono,Menlo,monospace;font-size:11px;letter-spacing:-0.01em} .mh{display:inline-flex;align-items:flex-start;gap:4px;flex-wrap:wrap;background:var(--surface-2,#F4F1F8);border:1px solid var(--line,#E3DEF0);border-radius:6px;padding:4px 8px} .mh .pfx{color:var(--plum,#531B59);font-weight:600} .verify-box{ - display:flex;gap:18px;align-items:flex-start;margin-top:24px;padding:16px; + display:flex;gap:22px;align-items:flex-start;margin-top:22px;padding:16px; border:1px solid var(--line,#E3DEF0);border-radius:10px;background:var(--bg,#FBFAFD); } .qr{ - width:112px;height:112px;flex:none;border:2px solid var(--ink,#1D1534);border-radius:8px; + width:130px;height:130px;flex:none;border:1px solid var(--ink,#1D1534);border-radius:6px;padding:4px;box-sizing:border-box; /* Offline / provisional placeholder — print-safe without CDN */ background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 29 29'%3E%3Cpath fill='%231D1534' d='M1 1h7v7H1zm2 2v3h3V3zm10 0h7v7h-7zm2 2v3h3V3zM1 13h7v7H1zm2 2v3h3v-3zm18-14h7v7h-7zm2 2v3h3V3zm0 10h3v2h-3zm-4 0h2v2h-2zm4 4h3v3h-3zm-4 2h2v3h-2zm-2-4h2v2h-2zm6 6h3v2h-3zm-8 0h2v2h-2zm4-2h2v4h-2zm-6 0h2v2h-2z'/%3E%3C/svg%3E") center/72% no-repeat; position:relative;overflow:hidden; @@ -94,9 +98,9 @@ .wrap{padding:0;max-width:none} .sheet{ box-shadow:none!important; - border:2px solid #531B59; - outline:1px solid #C4A85A;outline-offset:-5px; - margin:0;border-radius:0;min-height:0;padding:28px 36px 36px; + border:1.5px solid #531B59; + outline:0.5px solid #C4A85A;outline-offset:-6px; + margin:0;border-radius:0;min-height:0;padding:40px 48px 48px; -webkit-print-color-adjust:exact;print-color-adjust:exact; } /* CDN may be absent offline; keep plum/gold frame + system serif title */ @@ -104,8 +108,8 @@ .rule,.banner,.mh,.qr,.qr img.qr-bitmap,.qr canvas.qr-bitmap{ -webkit-print-color-adjust:exact;print-color-adjust:exact } - .rule{background:#C4A85A!important} - .qr{width:96px;height:96px;border-color:#1D1534!important} + .rule{background:#C4A85A!important;height:2px!important} + .qr{width:110px;height:110px;border-color:#1D1534!important;padding:3px} a{color:inherit;text-decoration:none} } @page{size:letter;margin:12mm} @@ -126,7 +130,7 @@
- Verae Verae diff --git a/site/printit.verae.ai/fonts/README.md b/site/printit.verae.ai/fonts/README.md index 5b65bb2..c50d003 100644 --- a/site/printit.verae.ai/fonts/README.md +++ b/site/printit.verae.ai/fonts/README.md @@ -32,12 +32,24 @@ print/PDF-from-browser paths stay consistent offline. fonts/ README.md (this file) .gitkeep + fetch-fonts.example.sh # curl placeholders; no download until uncommented fraunces-opsz-wght-600.woff2 # variable or static; opsz + weight 600 fraunces-opsz-wght-700.woff2 # weight 700 (title) jetbrains-mono-500.woff2 # optional; digests already have mono stack OFL.txt / LICENSE # required license text beside binaries ``` +### Fetch scaffold (TODO-7 A7) + +[`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: + +```bash +./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. @@ -82,6 +94,8 @@ wired so deploys and cache busting stay simple. | Step | State | |------|-------| | Placeholder dir + this plan | **Done** (L7) | +| `fetch-fonts.example.sh` placeholders | **Done** (TODO-7 A7) — no binaries fetched | +| Web cert spacing toward CERT-01 | **Partial** (TODO-7 A7) — padding/rule/QR; 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 | diff --git a/site/printit.verae.ai/fonts/fetch-fonts.example.sh b/site/printit.verae.ai/fonts/fetch-fonts.example.sh new file mode 100755 index 0000000..3319047 --- /dev/null +++ b/site/printit.verae.ai/fonts/fetch-fonts.example.sh @@ -0,0 +1,97 @@ +#!/usr/bin/env bash +# fetch-fonts.example.sh — placeholder download steps for CERT self-host fonts +# +# Does NOT download by default. Review licenses, then uncomment ONE source +# 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. +# +# License reminder: +# Fraunces — SIL Open Font License 1.1 (OFL) +# JetBrains Mono — SIL Open Font License 1.1 (OFL) +# Commit OFL.txt / LICENSE next to any binary. Do not strip copyright notices. + +set -euo pipefail + +DIR="$(cd "$(dirname "$0")" && pwd)" +cd "$DIR" + +echo "CERT fonts dir: $DIR" +echo "This is an EXAMPLE script — no network fetch runs unless you uncomment." +echo + +# --------------------------------------------------------------------------- +# Planned local names (keep stable once wired — see README.md) +# --------------------------------------------------------------------------- +# fraunces-opsz-wght-600.woff2 +# fraunces-opsz-wght-700.woff2 +# jetbrains-mono-500.woff2 +# OFL.txt + +# --------------------------------------------------------------------------- +# Option A — google/fonts GitHub release trees (OFL sources; pick a tag/commit) +# Comment-only reference URLs (do not curl until you pin a commit + verify OFL): +# +# # Fraunces variable / static under apache/ofl: +# # https://github.com/google/fonts/tree/main/ofl/fraunces +# # raw example (PIN A COMMIT — main moves): +# # https://raw.githubusercontent.com/google/fonts//ofl/fraunces/Fraunces%5Bopsz%2CSOFT%2CWONK%2Cwght%5D.ttf +# # +# # JetBrains Mono: +# # https://github.com/google/fonts/tree/main/ofl/jetbrainsmono +# # https://raw.githubusercontent.com/google/fonts//ofl/jetbrainsmono/JetBrainsMono%5Bwght%5D.ttf +# # +# # Upstream project pages (also OFL): +# # https://github.com/underscript-type/fraunces +# # https://github.com/JetBrains/JetBrainsMono +# +# Example curl skeleton (still commented — replace COMMIT and convert TTF→woff2 +# with fonttools/woff2_compress; do not commit unlicensed or renamed dumps): +# +# # COMMIT=________________________________ +# # curl -fsSL -o /tmp/fraunces.ttf \ +# # "https://raw.githubusercontent.com/google/fonts/${COMMIT}/ofl/fraunces/Fraunces%5Bopsz%2CSOFT%2CWONK%2Cwght%5D.ttf" +# # # subset + woff2, then: +# # # cp fraunces-opsz-wght-700.woff2 "$DIR/" +# # curl -fsSL -o "$DIR/OFL.txt" \ +# # "https://raw.githubusercontent.com/google/fonts/${COMMIT}/ofl/fraunces/OFL.txt" +# +# --------------------------------------------------------------------------- +# Option B — fonts.google.com CSS API (CDN helper; not for production self-host +# of binaries in-repo — use only to discover gstatic file URLs you already +# have rights to cache under OFL): +# +# # curl -fsSL -A "Mozilla/5.0" \ +# # "https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,600;9..144,700&family=JetBrains+Mono:wght@500&display=swap" +# # # Then curl each https://fonts.gstatic.com/...woff2 URL into this dir +# # # with the planned filenames above. Still need OFL.txt from Option A. +# +# --------------------------------------------------------------------------- + +if [[ "${1:-}" == "--check" ]]; then + missing=0 + for f in fraunces-opsz-wght-600.woff2 fraunces-opsz-wght-700.woff2 OFL.txt; do + if [[ -f "$f" ]]; then + echo "OK $f" + else + echo "MISS $f" + missing=1 + fi + done + if [[ -f jetbrains-mono-500.woff2 ]]; then + echo "OK jetbrains-mono-500.woff2 (optional)" + else + echo "skip jetbrains-mono-500.woff2 (optional)" + fi + exit "$missing" +fi + +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 tags." +echo +echo "Status: placeholder only — binaries not started (MULTI-AGENT-TODO-7 A7)."