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.2" // 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) // Preserve original basename (e.g. .html) for the proven artifact + certificate label. origBase := filepath.Base(opt.SourcePDF) if opt.DocumentName != "" { origBase = filepath.Base(opt.DocumentName) if filepath.Ext(origBase) == "" { origBase = origBase + filepath.Ext(opt.SourcePDF) } } if origBase == "" || origBase == "." { origBase = stem + ".pdf" } origPath := filepath.Join(outDir, origBase) 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, origBase) 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: origBase, 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": origBase, "sha256": digest, "receipt_id": rec.ReceiptID, "created_at": rec.CreatedAt, "merkle": rec.Merkle, "channel": string(opt.Channel), "base_url": rec.BaseURL, "original_file": filepath.Base(origPath), "certificate_pdf": filepath.Base(certPath), "byte_length": len(srcBytes), "status": "ok", "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 }