PrintToVeraeTimeStamp/internal/job/job.go
George Lambert 38b902d5e7 fix(pdfcert): branded Registration Document without mcp.verae.ai
Match the official Verae Timestamp Registration Document layout (logo,
frames, field list, QR). Disclaimer: only the digest was submitted.
Lookup/footer use explorer.verae.ai — no mcp.verae.ai or “via MCP”.
2026-10-03 04:15:28 -04:00

181 lines
4.7 KiB
Go

package job
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/VeraeLLC/PrintToVeraeTimeStamp/internal/auth"
"github.com/VeraeLLC/PrintToVeraeTimeStamp/internal/hash"
"github.com/VeraeLLC/PrintToVeraeTimeStamp/internal/pdfcert"
"github.com/VeraeLLC/PrintToVeraeTimeStamp/internal/veraeapi"
)
const HelperVersion = "0.1.0"
// Result paths written for one print job.
type Result struct {
OriginalPDF string
CertificatePDF string
ReceiptJSON string
ReceiptID string
SHA256 string
Channel auth.Channel
}
// Options for processing a captured print PDF.
type Options struct {
SourcePDF string // temp PDF from macOS PDF Services
OutDir string
DocumentName string // optional override for stem; default from source basename
Channel auth.Channel
Client *veraeapi.Client
}
var unsafeName = regexp.MustCompile(`[^A-Za-z0-9._-]+`)
// SanitizeReceiptID makes a receipt id safe for filenames.
func SanitizeReceiptID(id string) string {
id = strings.TrimSpace(id)
id = unsafeName.ReplaceAllString(id, "-")
id = strings.Trim(id, "-.")
if id == "" {
id = "unknown"
}
// Keep under common NAME_MAX budget with prefix
const max = 180
if len(id) > max {
id = id[:max]
}
return id
}
// StemFromPath returns the document stem without .pdf.
func StemFromPath(path, override string) string {
if override != "" {
s := strings.TrimSuffix(override, filepath.Ext(override))
return unsafeName.ReplaceAllString(s, "-")
}
base := filepath.Base(path)
stem := strings.TrimSuffix(base, filepath.Ext(base))
stem = unsafeName.ReplaceAllString(stem, "-")
stem = strings.Trim(stem, "-.")
if stem == "" {
stem = "document"
}
return stem
}
// DefaultOutDir is ~/Documents/Verae Timestamps.
func DefaultOutDir() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(home, "Documents", "Verae Timestamps"), nil
}
// Run copies the source PDF, stamps digests-only, writes certificate + JSON.
func Run(opt Options) (Result, error) {
if opt.SourcePDF == "" {
return Result{}, fmt.Errorf("source PDF required")
}
if opt.Client == nil {
return Result{}, fmt.Errorf("client required")
}
outDir := opt.OutDir
if outDir == "" {
d, err := DefaultOutDir()
if err != nil {
return Result{}, err
}
outDir = d
}
if err := os.MkdirAll(outDir, 0o700); err != nil {
return Result{}, err
}
stem := StemFromPath(opt.SourcePDF, opt.DocumentName)
origPath := filepath.Join(outDir, stem+".pdf")
srcBytes, err := os.ReadFile(opt.SourcePDF)
if err != nil {
return Result{}, err
}
if err := os.WriteFile(origPath, srcBytes, 0o600); err != nil {
return Result{}, err
}
digest, err := hash.FileSHA256(origPath)
if err != nil {
return Result{}, err
}
client := *opt.Client
client.Channel = opt.Channel
rec, err := client.Stamp(digest, stem+".pdf")
if err != nil {
return Result{}, err
}
if rec.ReceiptID == "" {
return Result{}, fmt.Errorf("stamp returned empty receipt id")
}
safeID := SanitizeReceiptID(rec.ReceiptID)
certPath := filepath.Join(outDir, fmt.Sprintf("%s-verae-timestamp-%s.pdf", stem, safeID))
// Always prefer the branded Registration Document (digests-only wording,
// no mcp.verae.ai). Edge attachPdf is optional and may still say MCP.
pdfBytes, err := pdfcert.Build(pdfcert.Input{
DocumentName: stem + ".pdf",
SHA256: digest,
ReceiptID: rec.ReceiptID,
CreatedAt: rec.CreatedAt,
Merkle: rec.Merkle,
Scope: "global",
Channel: string(opt.Channel),
ByteLength: int64(len(srcBytes)),
})
if err != nil {
return Result{}, err
}
_ = rec.PDF // upstream PDF ignored in favor of branded local certificate
if err := os.WriteFile(certPath, pdfBytes, 0o600); err != nil {
return Result{}, err
}
sidecar := map[string]any{
"helper_version": HelperVersion,
"document_name": stem + ".pdf",
"sha256": digest,
"receipt_id": rec.ReceiptID,
"created_at": rec.CreatedAt,
"merkle": rec.Merkle,
"channel": string(opt.Channel),
"base_url": rec.BaseURL,
"original_pdf": filepath.Base(origPath),
"certificate_pdf": filepath.Base(certPath),
"byte_length": len(srcBytes),
"saved_at": time.Now().UTC().Format(time.RFC3339),
}
jsonPath := filepath.Join(outDir, stem+".verae.json")
jb, err := json.MarshalIndent(sidecar, "", " ")
if err != nil {
return Result{}, err
}
if err := os.WriteFile(jsonPath, jb, 0o600); err != nil {
return Result{}, err
}
return Result{
OriginalPDF: origPath,
CertificatePDF: certPath,
ReceiptJSON: jsonPath,
ReceiptID: rec.ReceiptID,
SHA256: digest,
Channel: opt.Channel,
}, nil
}