@rel-time/mcp-server 1.2.7 → 1.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +11 -7
  2. package/index.js +1081 -1
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -6,7 +6,8 @@ Official Model Context Protocol (MCP) server for **[Rel-Time](https://rel-time.c
6
6
  3. **Anchor SHA-256 hashes to the Bitcoin Timechain & Rel-Time VDF** (`POST /v1/anchor`).
7
7
  4. **Generate & overlay 64-ray Sun-Stamps onto PDFs and Images** (`reltime-stamped-<name>.pdf` / `.png`).
8
8
  5. **Verify Bitcoin block settlement & Merkle proofs** (`GET /v1/proof/{hash}`).
9
- 6. **Self-provision or Reload (+500 Hash Credits / $50) an Autonomous Agent API key via Lightning** (`POST /v1/agent/invoice` & `POST /v1/agent/claim`).
9
+ 6. **Self-provision or Reload all 4 Rel-Time Tiers via Bitcoin Lightning or Stripe Card** (`Universal Anchor $3`, `Agent Launch Pack $5 / 50`, `Autonomous Agents $50 / 500`, and `Rel-Time Superhighway $600 / 5,000`).
10
+ 7. **Run a Superhighway Cryptographic Audit Trail & Local Folder Integrity Check** (`reltime_audit_trail`), generating an interactive HTML Audit Dashboard with Corporate Hardware Passkey (`signer_public_key`) attribution and Bitcoin block verification.
10
11
 
11
12
  ---
12
13
 
@@ -25,11 +26,13 @@ Add the following to your `.mcp.json`, `claude_desktop_config.json`, or `.cursor
25
26
  }
26
27
  ```
27
28
 
28
- When you start a fresh chat and ask to anchor a file or provision a key, `@rel-time/mcp-server` automatically prompts your agent to ask which pack you want first:
29
- 1. **Agent Launch Pack (`pack: "launch"`, `$5 / 50 Hashes`):** Priority 10-Minute Bitcoin Queue · Standard Mode only (Data Integrity + Biometric or Anonymous) · Includes 64-Ray Sun-Stamp, 1-Click Stamped Copy (`.PDF`/`.PNG`), and `.JSON` Doomsday Proof.
30
- 2. **Autonomous Agents (`pack: "standard"`, `$50 / 500 Hashes`):** Dynamic 6-Hour Batch Queue · Supports both Temporal Mode (`T_index` sub-second VDF sequencing) and Standard Mode · Reloadable anytime (`+500` credits / `$50`).
29
+ When you start a fresh chat and ask to anchor a file or provision a key, `@rel-time/mcp-server` automatically prompts your agent to ask which tier and payment method (**Bitcoin Lightning** or **Stripe Card**) you want first:
30
+ 1. **Universal Anchor (`pack: "universal"`, `$3 / anchor`):** Priority 10-Minute Bitcoin Queue · Standard Mode (Data Integrity + Biometric or Anonymous).
31
+ 2. **Agent Launch Pack (`pack: "launch"`, `$5 / 50 Hashes`):** Priority 10-Minute Bitcoin Queue · Standard Mode · Includes 64-Ray Sun-Stamp, 1-Click Stamped Copy (`.PDF`/`.PNG`), and `.JSON` Doomsday Proof.
32
+ 3. **Autonomous Agents (`pack: "standard"`, `$50 / 500 Hashes`):** Dynamic 6-Hour Batch Queue · Supports both Temporal Mode (`T_index` sub-second VDF sequencing) and Standard Mode · Reloadable anytime (`+500` credits / `$50`).
33
+ 4. **Rel-Time Superhighway (`pack: "enterprise"`, `$600 / 5,000 Hashes`):** Priority 10-Minute Bitcoin Queue · Temporal Mode (`T_index`) + Standard Mode + Bulk Folder & Superhighway Audit Dashboard.
31
34
 
32
- Once paid via Lightning (or if you pass an existing `rel_key_...` key), the MCP server automatically saves your API key to `~/.reltime/api_key.json` (`0600` permissions) — no app restart or `.zshrc` edits required.
35
+ Once paid via Lightning or Stripe (or if you pass an existing `rel_key_...` key), the MCP server automatically saves your API key to `~/.reltime/api_key.json` (`0600` permissions) — no app restart or `.zshrc` edits required.
33
36
 
34
37
  ---
35
38
 
@@ -37,11 +40,12 @@ Once paid via Lightning (or if you pass an existing `rel_key_...` key), the MCP
37
40
 
38
41
  | Tool Name | Requires Key? | What It Does |
39
42
  | :--- | :---: | :--- |
40
- | `reltime_anchor_file` | Yes | Computes the SHA-256 hash of a local file **on your machine** (0 bytes uploaded), supports `biometric: true` for **Biometric Anchors** (TouchID/FaceID/YubiKey), anchors via `POST /v1/anchor`, saves `reltime-stamp-<filename>.svg` and `reltime-doomsday-proof-<filename>.json`, and automatically outputs a **Stamped Copy** (`reltime-stamped-<name>.pdf` or `.png`). |
43
+ | `reltime_anchor_file` | Yes | Computes the SHA-256 hash of a local file **on your machine** (0 bytes uploaded), supports `biometric: true` for **Biometric Anchors** (TouchID/FaceID/YubiKey), anchors via `POST /v1/anchor`, saves `reltime-stamp-<filename>.svg` and `reltime-doomsday-proof-<filename>.json`, and automatically outputs a clickable **Stamped Copy** (`reltime-stamped-<name>.pdf` or `.png`). |
44
+ | `reltime_audit_trail` | Yes | Fetches the account's chronological anchor ledger (`GET /v1/account/history`), aggregates Bitcoin block settlements, VDF `T_index` ticks, and Corporate Hardware Passkey (`signer_public_key`) signatures, optionally verifies a local file or entire folder bit-for-bit (`verify_local_path`), and spins up a self-contained interactive HTML Audit Report (`reltime-audit-report-*.html`) in your browser. |
41
45
  | `reltime_link_passkey` | **No** | Opens `https://rel-time.com/mcp-passkey` in the system browser so the user can touch TouchID / FaceID / YubiKey once, saving `{ public_key_hex, passkey_signature }` to `~/.reltime/passkey.json` for seamless Biometric Anchors. |
42
46
  | `reltime_stamp_file` | **No** | Overlays the authentic 64-ray Sun-Stamp onto the bottom-right corner of any local PDF or Image (`.png`, `.jpg`, `.jpeg`, `.webp`, `.gif`) 100% locally. |
43
47
  | `reltime_anchor_hash` | Yes | Anchors any 64-char hex SHA-256 hash in `standard` (10-min Express / Integrity) or `temporal` (VDF sequence ordering) mode. |
44
48
  | `reltime_get_sun_stamp` | **No** | Fetches the deterministic 64-ray SVG Sun-Stamp (`GET /v1/stamp/{hash}`) and returns ready-to-paste Markdown/HTML badge snippets. |
45
49
  | `reltime_check_proof` | **No** | Queries `GET /v1/proof/{hash}` for Bitcoin TXID, Block Height, UTC Timestamp, and Merkle inclusion path, and optionally saves/updates the `.json` Doomsday Proof receipt on disk. |
46
50
  | `reltime_account_status` | Yes | Checks remaining Hash Credits and tier status (`GET /v1/account`). |
47
- | `reltime_provision_agent_key` | **No** | Generates a Lightning invoice (`$50` for 500 Hash Credits) and claims either a brand-new Tier 4 Autonomous Agent API key or **reloads `+500` Hash Credits into an existing key** (`reload_existing: true` or `api_key`). |
51
+ | `reltime_provision_agent_key` | **No** | Provisions or reloads any of the 4 Rel-Time Tiers (`universal` `$3`, `launch` `$5/50`, `standard` `$50/500`, `enterprise` `$600/5,000`) via **Bitcoin Lightning** or **Stripe Card**. |
package/index.js CHANGED
@@ -56,7 +56,7 @@ let LAST_INVOICE_PACK = null;
56
56
 
57
57
  const SERVER_INFO = {
58
58
  name: "rel-time-mcp-server",
59
- version: "1.2.7",
59
+ version: "1.2.8",
60
60
  };
61
61
 
62
62
  /**
@@ -477,6 +477,629 @@ async function stampLocalFileOptionA(filePath, svgContent, customOutputPath = nu
477
477
  }
478
478
  }
479
479
 
480
+ /**
481
+ * Extracts an embedded Rel-Time verification hash from a Stamped Copy (.png or .pdf) if present.
482
+ */
483
+ function extractStampedCopyEmbeddedHash(filePath, fileBuffer) {
484
+ try {
485
+ const ext = path.extname(filePath).toLowerCase();
486
+ if (ext === ".png" || ext === ".pdf" || ext === ".svg") {
487
+ const latin1 = fileBuffer.toString("latin1");
488
+ const m = latin1.match(/rel-time\.com\/verify\?t=([0-9a-fA-F]{64})/);
489
+ if (m && m[1]) {
490
+ return m[1].toLowerCase();
491
+ }
492
+ }
493
+ } catch (e) {
494
+ // Ignore extraction errors
495
+ }
496
+ return null;
497
+ }
498
+
499
+ function escapeHtml(str) {
500
+ return String(str == null ? "" : str)
501
+ .replace(/&/g, "&amp;")
502
+ .replace(/</g, "&lt;")
503
+ .replace(/>/g, "&gt;")
504
+ .replace(/"/g, "&quot;")
505
+ .replace(/'/g, "&#39;");
506
+ }
507
+
508
+ /**
509
+ * Generates a self-contained, interactive Rel-Time Superhighway Cryptographic Audit Trail HTML report.
510
+ */
511
+ function generateAuditReportHtml(audit) {
512
+ const generatedUtc = audit.generated_at_utc || new Date().toISOString();
513
+ const acct = audit.account_overview || {};
514
+ const sum = audit.audit_summary || {};
515
+ const modes = sum.mode_breakdown || {};
516
+ const btc = sum.bitcoin_settlement || {};
517
+ const signers = Array.isArray(sum.hardware_passkey_signers) ? sum.hardware_passkey_signers : [];
518
+ const localCheck = audit.local_path_verification || null;
519
+ const entries = Array.isArray(audit.audit_trail) ? audit.audit_trail : [];
520
+
521
+ const signerRowsHtml = signers.length
522
+ ? signers
523
+ .map(
524
+ (s, idx) => `
525
+ <tr>
526
+ <td class="mono">#${idx + 1}</td>
527
+ <td>
528
+ <span class="badge badge-passkey">FIDO2 / WebAuthn Hardware Key</span>
529
+ <div class="mono hash-full" style="margin-top:4px">${escapeHtml(s.signer_public_key)}</div>
530
+ </td>
531
+ <td class="mono" style="font-weight:700;color:#F5B041">${s.anchor_count}</td>
532
+ <td class="mono" style="color:#2ECC71">${s.settled_count}</td>
533
+ <td class="mono">${escapeHtml(s.latest_timestamp_utc || "—")}</td>
534
+ </tr>`
535
+ )
536
+ .join("")
537
+ : `<tr><td colspan="5" style="text-align:center;color:#8892b0;padding:18px">No hardware Passkey signatures recorded yet in this window. Link a passkey with <code>reltime_link_passkey</code> and anchor with <code>biometric: true</code>.</td></tr>`;
538
+
539
+ const localVerificationHtml = localCheck
540
+ ? `
541
+ <div class="section-card">
542
+ <div class="section-header">
543
+ <div>
544
+ <h2>Local File &amp; Folder Integrity Audit</h2>
545
+ <p class="section-sub">Target Path: <span class="mono">${escapeHtml(localCheck.target_path)}</span> · Zero-Knowledge Local SHA-256 Cross-Reference</p>
546
+ </div>
547
+ <span class="badge ${
548
+ localCheck.summary.unanchored_or_modified_count === 0 ? "badge-settled" : "badge-warn"
549
+ }">
550
+ ${
551
+ localCheck.summary.unanchored_or_modified_count === 0
552
+ ? `100% INTACT (${localCheck.summary.total_files_scanned} Verified)`
553
+ : `${localCheck.summary.unanchored_or_modified_count} UNANCHORED / MODIFIED`
554
+ }
555
+ </span>
556
+ </div>
557
+ <div class="table-wrap">
558
+ <table>
559
+ <thead>
560
+ <tr>
561
+ <th>File</th>
562
+ <th>Size</th>
563
+ <th>Local SHA-256 / Embedded Hash</th>
564
+ <th>Integrity Status</th>
565
+ <th>Mode / Signer</th>
566
+ <th>Bitcoin Settlement</th>
567
+ <th>Proof</th>
568
+ </tr>
569
+ </thead>
570
+ <tbody>
571
+ ${localCheck.files
572
+ .map((f) => {
573
+ const statusBadge =
574
+ f.integrity_status === "VERIFIED_SETTLED"
575
+ ? `<span class="badge badge-settled">✓ VERIFIED (BITCOIN SETTLED)</span>`
576
+ : f.integrity_status === "VERIFIED_PENDING"
577
+ ? `<span class="badge badge-pending">⏳ VERIFIED (PENDING BLOCK)</span>`
578
+ : `<span class="badge badge-warn">⚠ UNANCHORED OR MODIFIED</span>`;
579
+ const signerText = f.signer_public_key
580
+ ? `<span class="badge badge-passkey" title="${escapeHtml(f.signer_public_key)}">Passkey ${escapeHtml(
581
+ f.signer_public_key.slice(0, 10)
582
+ )}…</span>`
583
+ : `<span class="mono" style="color:#94a3b8">${escapeHtml(f.anchor_mode || "—")}</span>`;
584
+ const btcText = f.block_height
585
+ ? `<span class="mono" style="color:#2ECC71">Block #${escapeHtml(f.block_height)}</span>`
586
+ : f.integrity_status === "VERIFIED_PENDING"
587
+ ? `<span class="mono" style="color:#F5B041">Awaiting Next Block</span>`
588
+ : `—`;
589
+ const verifyLink = f.verify_url
590
+ ? `<a class="btn-link" href="${escapeHtml(f.verify_url)}" target="_blank" rel="noopener">Verify ↗</a>`
591
+ : `—`;
592
+ return `
593
+ <tr>
594
+ <td style="font-weight:600;color:#f8fafc">${escapeHtml(f.file_name)}${
595
+ f.is_stamped_copy ? ` <span class="badge badge-mode">Stamped Copy</span>` : ""
596
+ }</td>
597
+ <td class="mono">${escapeHtml(f.size_human)}</td>
598
+ <td class="mono hash-cell" title="${escapeHtml(f.local_sha256)}">${escapeHtml(
599
+ f.local_sha256.slice(0, 16)
600
+ )}…${escapeHtml(f.local_sha256.slice(-8))}</td>
601
+ <td>${statusBadge}</td>
602
+ <td>${signerText}</td>
603
+ <td>${btcText}</td>
604
+ <td>${verifyLink}</td>
605
+ </tr>`;
606
+ })
607
+ .join("")}
608
+ </tbody>
609
+ </table>
610
+ </div>
611
+ </div>`
612
+ : "";
613
+
614
+ const ledgerRowsHtml = entries.length
615
+ ? entries
616
+ .map((e, idx) => {
617
+ const isSettled = e.status === "settled";
618
+ const isBiometric = Boolean(e.signer_public_key);
619
+ const modeType = e.anchor_mode || "standard";
620
+ const filterClasses = [
621
+ isSettled ? "status-settled" : "status-pending",
622
+ `mode-${modeType}`,
623
+ isBiometric ? "mode-biometric" : "",
624
+ ]
625
+ .filter(Boolean)
626
+ .join(" ");
627
+
628
+ const statusHtml = isSettled
629
+ ? `<span class="badge badge-settled">✓ SETTLED</span>`
630
+ : `<span class="badge badge-pending">⏳ PENDING FINALITY</span>`;
631
+
632
+ const modeHtml = `
633
+ <span class="badge badge-mode">${escapeHtml(modeType.toUpperCase())}</span>
634
+ ${
635
+ isBiometric
636
+ ? `<span class="badge badge-passkey" title="Signed by Passkey: ${escapeHtml(
637
+ e.signer_public_key
638
+ )}">🔐 Passkey ${escapeHtml(e.signer_public_key.slice(0, 8))}…</span>`
639
+ : ""
640
+ }
641
+ `;
642
+
643
+ const btcHtml = isSettled
644
+ ? `
645
+ <div class="mono" style="color:#2ECC71;font-weight:600">Block #${escapeHtml(e.block_height || "—")}</div>
646
+ ${
647
+ e.txid
648
+ ? `<a class="mono tx-link" href="https://mempool.space/tx/${escapeHtml(
649
+ e.txid
650
+ )}" target="_blank" rel="noopener" title="${escapeHtml(e.txid)}">TX: ${escapeHtml(
651
+ e.txid.slice(0, 10)
652
+ )}…${escapeHtml(e.txid.slice(-6))} ↗</a>`
653
+ : ""
654
+ }
655
+ `
656
+ : `<span class="mono" style="color:#94a3b8">Queued in VDF Batch</span>`;
657
+
658
+ return `
659
+ <tr class="ledger-row ${filterClasses}" data-search="${escapeHtml(
660
+ `${e.client_hash} ${e.t_index} ${e.original_file_hash || ""} ${e.signer_public_key || ""} ${
661
+ e.txid || ""
662
+ } ${e.block_height || ""} ${modeType}`.toLowerCase()
663
+ )}">
664
+ <td class="mono" style="color:#64748b">#${idx + 1}</td>
665
+ <td class="mono">${escapeHtml(e.timestamp_utc || "—")}</td>
666
+ <td>${statusHtml}</td>
667
+ <td>${modeHtml}</td>
668
+ <td class="mono hash-cell" title="Client Hash: ${escapeHtml(e.client_hash)}${
669
+ e.original_file_hash ? ` | Original File SHA-256: ${escapeHtml(e.original_file_hash)}` : ""
670
+ }">
671
+ <div>${escapeHtml(e.client_hash.slice(0, 14))}…${escapeHtml(e.client_hash.slice(-8))}</div>
672
+ ${
673
+ e.original_file_hash
674
+ ? `<div style="font-size:11px;color:#94a3b8">File: ${escapeHtml(
675
+ e.original_file_hash.slice(0, 10)
676
+ )}…</div>`
677
+ : ""
678
+ }
679
+ </td>
680
+ <td class="mono hash-cell" title="T_index: ${escapeHtml(e.t_index)} | Solar Ticks: ${escapeHtml(
681
+ e.vdf_ticks
682
+ )}">
683
+ <div>${escapeHtml(e.t_index.slice(0, 12))}…${escapeHtml(e.t_index.slice(-6))}</div>
684
+ <div style="font-size:11px;color:#F5B041">Ticks: ${ Number(e.vdf_ticks || 0).toLocaleString() }</div>
685
+ </td>
686
+ <td>${btcHtml}</td>
687
+ <td>
688
+ <a class="btn-link" href="${escapeHtml(e.verify_url)}" target="_blank" rel="noopener">Verify ↗</a>
689
+ </td>
690
+ </tr>`;
691
+ })
692
+ .join("")
693
+ : `<tr><td colspan="8" style="text-align:center;color:#8892b0;padding:24px">No ledger entries matched the current filter.</td></tr>`;
694
+
695
+ return `<!DOCTYPE html>
696
+ <html lang="en">
697
+ <head>
698
+ <meta charset="UTF-8" />
699
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
700
+ <title>Rel-Time Superhighway — Cryptographic Audit Trail</title>
701
+ <style>
702
+ :root {
703
+ --bg: #07090e;
704
+ --card: #0f1420;
705
+ --card-border: #1e293b;
706
+ --gold: #f5b041;
707
+ --gold-dim: rgba(245, 176, 65, 0.14);
708
+ --green: #2ecc71;
709
+ --green-dim: rgba(46, 204, 113, 0.14);
710
+ --blue: #38bdf8;
711
+ --blue-dim: rgba(56, 189, 248, 0.14);
712
+ --purple: #c084fc;
713
+ --purple-dim: rgba(192, 132, 252, 0.15);
714
+ --red: #f87171;
715
+ --red-dim: rgba(248, 113, 113, 0.15);
716
+ --text: #f8fafc;
717
+ --muted: #94a3b8;
718
+ }
719
+ * { box-sizing: border-box; }
720
+ body {
721
+ margin: 0;
722
+ padding: 32px 24px 64px;
723
+ background: radial-gradient(circle at top right, #131c31 0%, var(--bg) 55%);
724
+ color: var(--text);
725
+ font-family: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", Roboto, sans-serif;
726
+ line-height: 1.5;
727
+ }
728
+ .container { max-width: 1340px; margin: 0 auto; }
729
+ .topbar {
730
+ display: flex;
731
+ flex-wrap: wrap;
732
+ justify-content: space-between;
733
+ align-items: center;
734
+ gap: 16px;
735
+ padding: 24px 28px;
736
+ background: linear-gradient(135deg, rgba(15,20,32,0.96), rgba(20,28,46,0.92));
737
+ border: 1px solid rgba(245, 176, 65, 0.35);
738
+ border-radius: 16px;
739
+ box-shadow: 0 18px 40px rgba(0,0,0,0.45);
740
+ margin-bottom: 24px;
741
+ }
742
+ .brand-eyebrow {
743
+ font-size: 11px;
744
+ letter-spacing: 0.18em;
745
+ text-transform: uppercase;
746
+ color: var(--gold);
747
+ font-weight: 700;
748
+ margin-bottom: 4px;
749
+ }
750
+ h1 {
751
+ margin: 0;
752
+ font-size: 26px;
753
+ font-weight: 800;
754
+ letter-spacing: -0.02em;
755
+ }
756
+ .meta-line {
757
+ font-size: 13px;
758
+ color: var(--muted);
759
+ margin-top: 4px;
760
+ }
761
+ .actions-bar {
762
+ display: flex;
763
+ gap: 10px;
764
+ align-items: center;
765
+ }
766
+ .btn {
767
+ background: var(--gold);
768
+ color: #07090e;
769
+ border: none;
770
+ padding: 10px 16px;
771
+ border-radius: 8px;
772
+ font-weight: 700;
773
+ font-size: 13px;
774
+ cursor: pointer;
775
+ text-decoration: none;
776
+ display: inline-flex;
777
+ align-items: center;
778
+ gap: 6px;
779
+ }
780
+ .btn-outline {
781
+ background: transparent;
782
+ color: var(--text);
783
+ border: 1px solid var(--card-border);
784
+ padding: 10px 16px;
785
+ border-radius: 8px;
786
+ font-weight: 600;
787
+ font-size: 13px;
788
+ cursor: pointer;
789
+ text-decoration: none;
790
+ }
791
+ .kpi-grid {
792
+ display: grid;
793
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
794
+ gap: 16px;
795
+ margin-bottom: 24px;
796
+ }
797
+ .kpi-card {
798
+ background: var(--card);
799
+ border: 1px solid var(--card-border);
800
+ border-radius: 14px;
801
+ padding: 18px 20px;
802
+ }
803
+ .kpi-label {
804
+ font-size: 11px;
805
+ text-transform: uppercase;
806
+ letter-spacing: 0.1em;
807
+ color: var(--muted);
808
+ margin-bottom: 6px;
809
+ }
810
+ .kpi-value {
811
+ font-size: 26px;
812
+ font-weight: 800;
813
+ font-family: "JetBrains Mono", "SF Mono", Menlo, monospace;
814
+ }
815
+ .kpi-sub {
816
+ font-size: 12px;
817
+ color: var(--muted);
818
+ margin-top: 4px;
819
+ }
820
+ .section-card {
821
+ background: var(--card);
822
+ border: 1px solid var(--card-border);
823
+ border-radius: 16px;
824
+ padding: 22px 24px;
825
+ margin-bottom: 24px;
826
+ }
827
+ .section-header {
828
+ display: flex;
829
+ flex-wrap: wrap;
830
+ justify-content: space-between;
831
+ align-items: center;
832
+ gap: 12px;
833
+ margin-bottom: 16px;
834
+ }
835
+ h2 {
836
+ margin: 0;
837
+ font-size: 18px;
838
+ font-weight: 700;
839
+ }
840
+ .section-sub {
841
+ margin: 3px 0 0;
842
+ font-size: 13px;
843
+ color: var(--muted);
844
+ }
845
+ .filter-bar {
846
+ display: flex;
847
+ flex-wrap: wrap;
848
+ gap: 8px;
849
+ align-items: center;
850
+ margin-bottom: 16px;
851
+ }
852
+ .search-input {
853
+ background: #07090e;
854
+ border: 1px solid var(--card-border);
855
+ color: var(--text);
856
+ padding: 8px 12px;
857
+ border-radius: 8px;
858
+ font-size: 13px;
859
+ min-width: 260px;
860
+ }
861
+ .filter-pill {
862
+ background: #07090e;
863
+ border: 1px solid var(--card-border);
864
+ color: var(--muted);
865
+ padding: 6px 12px;
866
+ border-radius: 999px;
867
+ font-size: 12px;
868
+ font-weight: 600;
869
+ cursor: pointer;
870
+ }
871
+ .filter-pill.active {
872
+ background: var(--gold-dim);
873
+ border-color: var(--gold);
874
+ color: var(--gold);
875
+ }
876
+ .table-wrap {
877
+ overflow-x: auto;
878
+ }
879
+ table {
880
+ width: 100%;
881
+ border-collapse: collapse;
882
+ font-size: 13px;
883
+ }
884
+ th {
885
+ text-align: left;
886
+ padding: 11px 12px;
887
+ font-size: 11px;
888
+ text-transform: uppercase;
889
+ letter-spacing: 0.08em;
890
+ color: var(--muted);
891
+ border-bottom: 1px solid var(--card-border);
892
+ white-space: nowrap;
893
+ }
894
+ td {
895
+ padding: 12px;
896
+ border-bottom: 1px solid rgba(30, 41, 59, 0.65);
897
+ vertical-align: middle;
898
+ }
899
+ tr:hover td {
900
+ background: rgba(255, 255, 255, 0.02);
901
+ }
902
+ .mono {
903
+ font-family: "JetBrains Mono", "SF Mono", Menlo, monospace;
904
+ font-size: 12px;
905
+ }
906
+ .hash-full {
907
+ word-break: break-all;
908
+ color: #cbd5e1;
909
+ font-size: 11.5px;
910
+ }
911
+ .badge {
912
+ display: inline-block;
913
+ padding: 3px 9px;
914
+ border-radius: 999px;
915
+ font-size: 11px;
916
+ font-weight: 700;
917
+ letter-spacing: 0.03em;
918
+ white-space: nowrap;
919
+ }
920
+ .badge-settled { background: var(--green-dim); color: var(--green); border: 1px solid rgba(46,204,113,0.35); }
921
+ .badge-pending { background: var(--gold-dim); color: var(--gold); border: 1px solid rgba(245,176,65,0.35); }
922
+ .badge-mode { background: var(--blue-dim); color: var(--blue); border: 1px solid rgba(56,189,248,0.3); }
923
+ .badge-passkey { background: var(--purple-dim); color: var(--purple); border: 1px solid rgba(192,132,252,0.35); }
924
+ .badge-warn { background: var(--red-dim); color: var(--red); border: 1px solid rgba(248,113,113,0.35); }
925
+ .btn-link {
926
+ color: var(--gold);
927
+ text-decoration: none;
928
+ font-weight: 700;
929
+ font-size: 12px;
930
+ padding: 5px 10px;
931
+ border: 1px solid rgba(245,176,65,0.35);
932
+ border-radius: 6px;
933
+ background: var(--gold-dim);
934
+ white-space: nowrap;
935
+ }
936
+ .tx-link {
937
+ color: #94a3b8;
938
+ text-decoration: none;
939
+ font-size: 11px;
940
+ display: inline-block;
941
+ margin-top: 2px;
942
+ }
943
+ .tx-link:hover { color: var(--gold); }
944
+ @media print {
945
+ body { background: #07090e !important; -webkit-print-color-adjust: exact; print-color-adjust: exact; padding: 12px; }
946
+ .no-print { display: none !important; }
947
+ }
948
+ </style>
949
+ </head>
950
+ <body>
951
+ <div class="container">
952
+ <div class="topbar">
953
+ <div>
954
+ <div class="brand-eyebrow">Rel-Time Sovereign Provenance Protocol · Audit Trail</div>
955
+ <h1>Cryptographic Data Integrity &amp; Timechain Audit Report</h1>
956
+ <div class="meta-line">
957
+ Generated: <span class="mono">${escapeHtml(generatedUtc)}</span> ·
958
+ Account Tier: <strong style="color:#F5B041">${escapeHtml(acct.tier || "Active")}</strong> ·
959
+ Key: <span class="mono">${escapeHtml(acct.masked_api_key || "rel_key_***")}</span>
960
+ </div>
961
+ </div>
962
+ <div class="actions-bar no-print">
963
+ <a class="btn-outline" href="https://rel-time.com/verify" target="_blank" rel="noopener">Open Verifier ↗</a>
964
+ <button class="btn" onclick="window.print()">Print / Save PDF Audit Certificate</button>
965
+ </div>
966
+ </div>
967
+
968
+ <div class="kpi-grid">
969
+ <div class="kpi-card">
970
+ <div class="kpi-label">Audited Anchors</div>
971
+ <div class="kpi-value" style="color:#f8fafc">${Number(sum.matched_entries_count || 0).toLocaleString()}</div>
972
+ <div class="kpi-sub">Lifetime Account Total: ${Number(acct.lifetime_anchors || 0).toLocaleString()}</div>
973
+ </div>
974
+ <div class="kpi-card">
975
+ <div class="kpi-label">Bitcoin Finality</div>
976
+ <div class="kpi-value" style="color:#2ECC71">${Number(sum.settled_on_bitcoin_count || 0).toLocaleString()}</div>
977
+ <div class="kpi-sub">${escapeHtml(sum.settlement_rate_pct || "0%")} Settled · ${Number(
978
+ sum.pending_finality_count || 0
979
+ )} Pending</div>
980
+ </div>
981
+ <div class="kpi-card">
982
+ <div class="kpi-label">Bitcoin Blocks &amp; TXs</div>
983
+ <div class="kpi-value" style="color:#F5B041">${Number(btc.unique_blocks_count || 0)}</div>
984
+ <div class="kpi-sub">${Number(btc.unique_txids_count || 0)} Unique OP_RETURN TXIDs${
985
+ btc.latest_settled_block ? ` · Latest #${escapeHtml(btc.latest_settled_block)}` : ""
986
+ }</div>
987
+ </div>
988
+ <div class="kpi-card">
989
+ <div class="kpi-label">Hashing Modes</div>
990
+ <div class="kpi-value" style="color:#38bdf8">${Number(modes.standard_count || 0)} / ${Number(
991
+ modes.temporal_vdf_count || 0
992
+ )}</div>
993
+ <div class="kpi-sub">Standard Integrity / Temporal VDF</div>
994
+ </div>
995
+ <div class="kpi-card">
996
+ <div class="kpi-label">Hardware Passkey Signers</div>
997
+ <div class="kpi-value" style="color:#c084fc">${signers.length}</div>
998
+ <div class="kpi-sub">${Number(modes.biometric_passkey_count || 0)} Biometric-Attested Anchors</div>
999
+ </div>
1000
+ <div class="kpi-card">
1001
+ <div class="kpi-label">Available Fuel</div>
1002
+ <div class="kpi-value" style="color:#F5B041">${Number(acct.hash_credits_remaining || 0).toLocaleString()}</div>
1003
+ <div class="kpi-sub">Remaining Hash Credits</div>
1004
+ </div>
1005
+ </div>
1006
+
1007
+ ${localVerificationHtml}
1008
+
1009
+ <div class="section-card">
1010
+ <div class="section-header">
1011
+ <div>
1012
+ <h2>Corporate Hardware Passkey Signers (WebAuthn / FIDO2 COSE Keys)</h2>
1013
+ <p class="section-sub">Hardware Secure Enclave &amp; YubiKey public keys that have attested files under this corporate account</p>
1014
+ </div>
1015
+ </div>
1016
+ <div class="table-wrap">
1017
+ <table>
1018
+ <thead>
1019
+ <tr>
1020
+ <th>#</th>
1021
+ <th>Hardware Signer Public Key (COSE Hex)</th>
1022
+ <th>Signed Anchors</th>
1023
+ <th>Bitcoin Settled</th>
1024
+ <th>Latest Signature (UTC)</th>
1025
+ </tr>
1026
+ </thead>
1027
+ <tbody>
1028
+ ${signerRowsHtml}
1029
+ </tbody>
1030
+ </table>
1031
+ </div>
1032
+ </div>
1033
+
1034
+ <div class="section-card">
1035
+ <div class="section-header">
1036
+ <div>
1037
+ <h2>Chronological Timechain Audit Ledger</h2>
1038
+ <p class="section-sub">Complete record of SHA-256 data integrity hashes, VDF thermodynamic proofs (T_index), and Bitcoin Merkle settlements</p>
1039
+ </div>
1040
+ </div>
1041
+ <div class="filter-bar no-print">
1042
+ <input id="searchInput" class="search-input" type="text" placeholder="Filter by SHA-256 hash, T_index, Passkey, TXID, or Block #..." oninput="filterLedger()" />
1043
+ <button class="filter-pill active" onclick="setPill(this, 'all')">All (${entries.length})</button>
1044
+ <button class="filter-pill" onclick="setPill(this, 'status-settled')">Settled (${Number(
1045
+ sum.settled_on_bitcoin_count || 0
1046
+ )})</button>
1047
+ <button class="filter-pill" onclick="setPill(this, 'status-pending')">Pending (${Number(
1048
+ sum.pending_finality_count || 0
1049
+ )})</button>
1050
+ <button class="filter-pill" onclick="setPill(this, 'mode-biometric')">Biometric Passkey (${Number(
1051
+ modes.biometric_passkey_count || 0
1052
+ )})</button>
1053
+ <button class="filter-pill" onclick="setPill(this, 'mode-temporal')">Temporal VDF (${Number(
1054
+ modes.temporal_vdf_count || 0
1055
+ )})</button>
1056
+ <button class="filter-pill" onclick="setPill(this, 'mode-standard')">Standard (${Number(
1057
+ modes.standard_count || 0
1058
+ )})</button>
1059
+ </div>
1060
+ <div class="table-wrap">
1061
+ <table>
1062
+ <thead>
1063
+ <tr>
1064
+ <th>#</th>
1065
+ <th>Timestamp (UTC)</th>
1066
+ <th>Status</th>
1067
+ <th>Mode &amp; Signer</th>
1068
+ <th>Client / File SHA-256</th>
1069
+ <th>Thermodynamic Proof (T_index)</th>
1070
+ <th>Bitcoin Settlement</th>
1071
+ <th>Verify</th>
1072
+ </tr>
1073
+ </thead>
1074
+ <tbody id="ledgerBody">
1075
+ ${ledgerRowsHtml}
1076
+ </tbody>
1077
+ </table>
1078
+ </div>
1079
+ </div>
1080
+ </div>
1081
+
1082
+ <script>
1083
+ let currentFilter = 'all';
1084
+ function setPill(btn, filter) {
1085
+ currentFilter = filter;
1086
+ document.querySelectorAll('.filter-pill').forEach(el => el.classList.remove('active'));
1087
+ btn.classList.add('active');
1088
+ filterLedger();
1089
+ }
1090
+ function filterLedger() {
1091
+ const q = (document.getElementById('searchInput').value || '').trim().toLowerCase();
1092
+ document.querySelectorAll('.ledger-row').forEach(row => {
1093
+ const matchesFilter = currentFilter === 'all' || row.classList.contains(currentFilter);
1094
+ const matchesQuery = !q || (row.getAttribute('data-search') || '').includes(q);
1095
+ row.style.display = (matchesFilter && matchesQuery) ? '' : 'none';
1096
+ });
1097
+ }
1098
+ </script>
1099
+ </body>
1100
+ </html>`;
1101
+ }
1102
+
480
1103
  const TOOLS = [
481
1104
  {
482
1105
  name: "reltime_anchor_file",
@@ -685,6 +1308,64 @@ const TOOLS = [
685
1308
  required: ["action"],
686
1309
  },
687
1310
  },
1311
+ {
1312
+ name: "reltime_audit_trail",
1313
+ description:
1314
+ "Superhighway Cryptographic Audit Trail & Local Folder Integrity Verifier (`GET /api/v1/account` + `GET /api/v1/account/history` + local SHA-256 cross-reference). Fetches the account's chronological anchor ledger, aggregates Bitcoin block settlements, VDF `T_index` ticks, and Corporate Hardware Passkey (`signer_public_key`) signatures, optionally verifies a local file or entire folder bit-for-bit against the ledger (`verify_local_path`), and spins up a self-contained interactive HTML Audit Report (`reltime-audit-report-*.html`) in the user's browser.",
1315
+ inputSchema: {
1316
+ type: "object",
1317
+ properties: {
1318
+ verify_local_path: {
1319
+ type: "string",
1320
+ description:
1321
+ "Optional local file path or folder/directory path to audit bit-for-bit (Zero-Knowledge local SHA-256 + Passkey attestation check + Stamped Copy check) against the Rel-Time Bitcoin ledger.",
1322
+ },
1323
+ limit: {
1324
+ type: "number",
1325
+ description: "Maximum number of audit ledger entries to retrieve (default 100, max 500).",
1326
+ },
1327
+ status_filter: {
1328
+ type: "string",
1329
+ enum: ["all", "settled", "pending"],
1330
+ description: "Filter ledger entries by settlement status: 'all' (default), 'settled', or 'pending'.",
1331
+ },
1332
+ mode_filter: {
1333
+ type: "string",
1334
+ enum: ["all", "standard", "temporal", "biometric"],
1335
+ description:
1336
+ "Filter ledger entries by mode: 'all' (default), 'standard', 'temporal' (VDF T_index), or 'biometric' (Hardware Passkey attested).",
1337
+ },
1338
+ signer_filter: {
1339
+ type: "string",
1340
+ description:
1341
+ "Optional full or partial Hardware Passkey `public_key_hex` to filter anchors signed by a specific employee or corporate YubiKey.",
1342
+ },
1343
+ save_html_report: {
1344
+ type: "boolean",
1345
+ description:
1346
+ "If true (default), saves an interactive dark-mode HTML Superhighway Audit Trail dashboard (`reltime-audit-report-<date>.html`).",
1347
+ },
1348
+ open_in_browser: {
1349
+ type: "boolean",
1350
+ description:
1351
+ "If true (default), automatically opens the generated HTML Audit Trail dashboard in the user's browser.",
1352
+ },
1353
+ export_csv: {
1354
+ type: "boolean",
1355
+ description: "If true, also exports the audit ledger to a `.csv` spreadsheet file.",
1356
+ },
1357
+ output_dir: {
1358
+ type: "string",
1359
+ description:
1360
+ "Optional directory where the generated `.html` / `.csv` audit reports should be saved (defaults to `verify_local_path` folder or `~/.reltime/audits`).",
1361
+ },
1362
+ api_key: {
1363
+ type: "string",
1364
+ description: "Optional override for RELTIME_API_KEY.",
1365
+ },
1366
+ },
1367
+ },
1368
+ },
688
1369
  ];
689
1370
 
690
1371
  async function executeTool(name, args = {}) {
@@ -984,6 +1665,405 @@ async function executeTool(name, args = {}) {
984
1665
  return data;
985
1666
  }
986
1667
 
1668
+ if (name === "reltime_audit_trail") {
1669
+ if (!key) {
1670
+ return buildPackSelectionPrompt(
1671
+ "To run a Superhighway Audit Trail, a Rel-Time API key is required. Ask the user which tier and payment method they would like to provision:"
1672
+ );
1673
+ }
1674
+
1675
+ const acctRes = await fetch(`${API_BASE}/account`, {
1676
+ headers: { Authorization: `Bearer ${key}` },
1677
+ });
1678
+ if (!acctRes.ok) {
1679
+ if (acctRes.status === 401 || acctRes.status === 403) {
1680
+ return buildPackSelectionPrompt(
1681
+ "The configured API key was not recognized. Ask the user which tier and payment method they would like to provision:"
1682
+ );
1683
+ }
1684
+ throw new Error(`Rel-Time Account error (${acctRes.status}): ${await acctRes.text()}`);
1685
+ }
1686
+ const acctData = await acctRes.json();
1687
+
1688
+ const maxEntries = Math.min(500, Math.max(1, Number(args.limit) || 100));
1689
+ const pageSize = Math.min(100, maxEntries);
1690
+ const pagesToFetch = Math.ceil(maxEntries / pageSize);
1691
+ let rawHistory = [];
1692
+ let totalInLedger = 0;
1693
+
1694
+ for (let p = 1; p <= pagesToFetch; p++) {
1695
+ const histRes = await fetch(`${API_BASE}/account/history?page=${p}&limit=${pageSize}`, {
1696
+ headers: { Authorization: `Bearer ${key}` },
1697
+ });
1698
+ if (!histRes.ok) break;
1699
+ const histJson = await histRes.json();
1700
+ totalInLedger = Number(histJson.total || 0);
1701
+ const batch = Array.isArray(histJson.history) ? histJson.history : [];
1702
+ rawHistory.push(...batch);
1703
+ if (batch.length < pageSize || rawHistory.length >= maxEntries || rawHistory.length >= totalInLedger) {
1704
+ break;
1705
+ }
1706
+ }
1707
+ rawHistory = rawHistory.slice(0, maxEntries);
1708
+
1709
+ const statusFilter = (args.status_filter || "all").toLowerCase();
1710
+ const modeFilter = (args.mode_filter || "all").toLowerCase();
1711
+ const signerFilter = (args.signer_filter || "").trim().toLowerCase();
1712
+
1713
+ const normalizedEntries = rawHistory.map((item) => {
1714
+ const clientHash = (item.client_hash || "").toLowerCase();
1715
+ const tIndex = (item.t_index || "").toLowerCase();
1716
+ const origHash = item.original_file_hash ? item.original_file_hash.toLowerCase() : null;
1717
+ const mode = (item.anchor_mode || "standard").toLowerCase();
1718
+ const lookupHash = mode === "temporal" ? tIndex || clientHash : clientHash;
1719
+ const tsSec = Number(item.timestamp || 0);
1720
+ const tsUtc = tsSec > 0 ? new Date(tsSec * 1000).toISOString() : null;
1721
+ return {
1722
+ timestamp: tsSec,
1723
+ timestamp_utc: tsUtc,
1724
+ status: item.status || "pending_finality",
1725
+ anchor_mode: mode,
1726
+ identity_mode: item.signer_public_key ? "biometric_passkey" : "anonymous",
1727
+ client_hash: clientHash,
1728
+ original_file_hash: origHash,
1729
+ t_index: tIndex,
1730
+ vdf_ticks: Number(item.vdf_ticks || 0),
1731
+ signer_public_key: item.signer_public_key || null,
1732
+ block_height: item.block_height || null,
1733
+ block_time: item.block_time || null,
1734
+ block_time_utc: item.block_time ? new Date(Number(item.block_time) * 1000).toISOString() : null,
1735
+ txid: item.txid || null,
1736
+ merkle_root: item.merkle_root || null,
1737
+ verify_url: `https://rel-time.com/verify?t=${lookupHash}`,
1738
+ sun_stamp_svg_url: `${API_BASE}/stamp/${lookupHash}`,
1739
+ };
1740
+ });
1741
+
1742
+ const filteredEntries = normalizedEntries.filter((e) => {
1743
+ if (statusFilter === "settled" && e.status !== "settled") return false;
1744
+ if (statusFilter === "pending" && e.status === "settled") return false;
1745
+ if (modeFilter === "standard" && e.anchor_mode !== "standard") return false;
1746
+ if (modeFilter === "temporal" && e.anchor_mode !== "temporal") return false;
1747
+ if (modeFilter === "biometric" && !e.signer_public_key) return false;
1748
+ if (signerFilter && !(e.signer_public_key || "").toLowerCase().includes(signerFilter)) return false;
1749
+ return true;
1750
+ });
1751
+
1752
+ // Aggregate KPI & Hardware Passkey Signer statistics
1753
+ let settledCount = 0;
1754
+ let pendingCount = 0;
1755
+ let standardCount = 0;
1756
+ let temporalCount = 0;
1757
+ let biometricCount = 0;
1758
+ const uniqueBlocks = new Set();
1759
+ const uniqueTxids = new Set();
1760
+ let latestBlock = null;
1761
+ let latestTxid = null;
1762
+ const signerMap = new Map();
1763
+
1764
+ for (const e of filteredEntries) {
1765
+ if (e.status === "settled") {
1766
+ settledCount++;
1767
+ if (e.block_height) {
1768
+ uniqueBlocks.add(e.block_height);
1769
+ if (!latestBlock || Number(e.block_height) > Number(latestBlock)) {
1770
+ latestBlock = e.block_height;
1771
+ latestTxid = e.txid || latestTxid;
1772
+ }
1773
+ }
1774
+ if (e.txid) uniqueTxids.add(e.txid);
1775
+ } else {
1776
+ pendingCount++;
1777
+ }
1778
+
1779
+ if (e.anchor_mode === "temporal") temporalCount++;
1780
+ else standardCount++;
1781
+
1782
+ if (e.signer_public_key) {
1783
+ biometricCount++;
1784
+ const pk = e.signer_public_key;
1785
+ const cur = signerMap.get(pk) || {
1786
+ signer_public_key: pk,
1787
+ short_key: `${pk.slice(0, 12)}...${pk.slice(-8)}`,
1788
+ anchor_count: 0,
1789
+ settled_count: 0,
1790
+ latest_timestamp_utc: e.timestamp_utc,
1791
+ };
1792
+ cur.anchor_count++;
1793
+ if (e.status === "settled") cur.settled_count++;
1794
+ if (e.timestamp_utc && (!cur.latest_timestamp_utc || e.timestamp_utc > cur.latest_timestamp_utc)) {
1795
+ cur.latest_timestamp_utc = e.timestamp_utc;
1796
+ }
1797
+ signerMap.set(pk, cur);
1798
+ }
1799
+ }
1800
+
1801
+ // Build fast lookup index of all known hashes in account history + known passkeys
1802
+ const ledgerByHash = new Map();
1803
+ const knownPasskeys = new Set();
1804
+ const localPasskey = loadSavedPasskey();
1805
+ if (localPasskey && localPasskey.public_key_hex) {
1806
+ knownPasskeys.add(localPasskey.public_key_hex);
1807
+ }
1808
+ for (const e of normalizedEntries) {
1809
+ if (e.client_hash) ledgerByHash.set(e.client_hash, e);
1810
+ if (e.t_index) ledgerByHash.set(e.t_index, e);
1811
+ if (e.original_file_hash) ledgerByHash.set(e.original_file_hash, e);
1812
+ if (e.signer_public_key) knownPasskeys.add(e.signer_public_key);
1813
+ }
1814
+
1815
+ // Optional Zero-Knowledge Local File / Folder Integrity Verification
1816
+ let localPathVerification = null;
1817
+ if (args.verify_local_path) {
1818
+ const targetPath = resolveLocalFilePath(args.verify_local_path);
1819
+ if (!fs.existsSync(targetPath)) {
1820
+ throw new Error(`verify_local_path not found: ${targetPath}`);
1821
+ }
1822
+ const stat = fs.statSync(targetPath);
1823
+ let candidateFiles = [];
1824
+ if (stat.isFile()) {
1825
+ candidateFiles.push(targetPath);
1826
+ } else if (stat.isDirectory()) {
1827
+ const dirItems = fs.readdirSync(targetPath);
1828
+ for (const item of dirItems) {
1829
+ if (item.startsWith(".")) continue;
1830
+ if (
1831
+ item.startsWith("reltime-stamp-") ||
1832
+ item.startsWith("reltime-doomsday-proof-") ||
1833
+ item.startsWith("reltime-proof-") ||
1834
+ item.startsWith("reltime-audit-report-")
1835
+ ) {
1836
+ continue;
1837
+ }
1838
+ const fullItemPath = path.join(targetPath, item);
1839
+ try {
1840
+ if (fs.statSync(fullItemPath).isFile()) {
1841
+ candidateFiles.push(fullItemPath);
1842
+ }
1843
+ } catch (e) {
1844
+ // Skip unreadable entry
1845
+ }
1846
+ }
1847
+ }
1848
+
1849
+ const fileAuditResults = [];
1850
+ let vSettled = 0;
1851
+ let vPending = 0;
1852
+ let vUnanchored = 0;
1853
+
1854
+ for (const fPath of candidateFiles) {
1855
+ const buf = fs.readFileSync(fPath);
1856
+ const fName = path.basename(fPath);
1857
+ const rawSha256 = crypto.createHash("sha256").update(buf).digest("hex");
1858
+ const embeddedHash = extractStampedCopyEmbeddedHash(fPath, buf);
1859
+ const isStampedCopy = Boolean(embeddedHash) || fName.startsWith("reltime-stamped-");
1860
+
1861
+ let matchedEntry =
1862
+ ledgerByHash.get(rawSha256) || (embeddedHash ? ledgerByHash.get(embeddedHash) : null) || null;
1863
+
1864
+ // Also check biometric attested hashes SHA-256(rawSha256 + passkey_hex)
1865
+ if (!matchedEntry) {
1866
+ for (const pkHex of knownPasskeys) {
1867
+ const attestedCandidate = crypto
1868
+ .createHash("sha256")
1869
+ .update(`${rawSha256}${pkHex}`, "utf8")
1870
+ .digest("hex");
1871
+ if (ledgerByHash.has(attestedCandidate)) {
1872
+ matchedEntry = ledgerByHash.get(attestedCandidate);
1873
+ break;
1874
+ }
1875
+ }
1876
+ }
1877
+
1878
+ // Fallback: query live GET /v1/proof/{hash} in case it was anchored outside the recent history window
1879
+ const probeHash = embeddedHash || rawSha256;
1880
+ if (!matchedEntry) {
1881
+ try {
1882
+ const proofRes = await fetch(`${API_BASE}/proof/${probeHash}`);
1883
+ if (proofRes.ok) {
1884
+ const proofData = await proofRes.json();
1885
+ const s = proofData.settlement || {};
1886
+ matchedEntry = {
1887
+ status: proofData.status || "settled",
1888
+ anchor_mode: proofData.anchor_mode || "standard",
1889
+ client_hash: proofData.client_hash || probeHash,
1890
+ t_index: proofData.t_index || probeHash,
1891
+ signer_public_key: proofData.signer_public_key || null,
1892
+ block_height: s.bitcoin_block_height || s.block_height || null,
1893
+ txid: s.bitcoin_txid || null,
1894
+ merkle_root: s.merkle_root || null,
1895
+ verify_url: `https://rel-time.com/verify?t=${probeHash}`,
1896
+ };
1897
+ }
1898
+ } catch (e) {
1899
+ // Ignore network error during fallback lookup
1900
+ }
1901
+ }
1902
+
1903
+ let integrityStatus = "UNANCHORED_OR_MODIFIED";
1904
+ if (matchedEntry) {
1905
+ if (matchedEntry.status === "settled") {
1906
+ integrityStatus = "VERIFIED_SETTLED";
1907
+ vSettled++;
1908
+ } else {
1909
+ integrityStatus = "VERIFIED_PENDING";
1910
+ vPending++;
1911
+ }
1912
+ } else {
1913
+ vUnanchored++;
1914
+ }
1915
+
1916
+ const bytes = buf.length;
1917
+ const sizeHuman =
1918
+ bytes >= 1048576
1919
+ ? `${(bytes / 1048576).toFixed(2)} MB`
1920
+ : bytes >= 1024
1921
+ ? `${(bytes / 1024).toFixed(1)} KB`
1922
+ : `${bytes} B`;
1923
+
1924
+ fileAuditResults.push({
1925
+ file_name: fName,
1926
+ file_path: fPath,
1927
+ size_bytes: bytes,
1928
+ size_human: sizeHuman,
1929
+ local_sha256: embeddedHash || rawSha256,
1930
+ raw_file_sha256: rawSha256,
1931
+ embedded_stamp_hash: embeddedHash,
1932
+ is_stamped_copy: isStampedCopy,
1933
+ integrity_status: integrityStatus,
1934
+ anchor_mode: matchedEntry ? matchedEntry.anchor_mode : null,
1935
+ signer_public_key: matchedEntry ? matchedEntry.signer_public_key : null,
1936
+ t_index: matchedEntry ? matchedEntry.t_index : null,
1937
+ block_height: matchedEntry ? matchedEntry.block_height : null,
1938
+ txid: matchedEntry ? matchedEntry.txid : null,
1939
+ verify_url: matchedEntry ? matchedEntry.verify_url : null,
1940
+ });
1941
+ }
1942
+
1943
+ localPathVerification = {
1944
+ target_path: targetPath,
1945
+ summary: {
1946
+ total_files_scanned: fileAuditResults.length,
1947
+ verified_settled_count: vSettled,
1948
+ verified_pending_count: vPending,
1949
+ unanchored_or_modified_count: vUnanchored,
1950
+ overall_integrity: vUnanchored === 0 ? "ALL_FILES_VERIFIED" : "ATTENTION_UNANCHORED_OR_MODIFIED_FILES",
1951
+ },
1952
+ files: fileAuditResults,
1953
+ };
1954
+ }
1955
+
1956
+ const maskedKey = key.length > 16 ? `${key.slice(0, 14)}…${key.slice(-4)}` : "rel_key_***";
1957
+ const settlementRatePct =
1958
+ filteredEntries.length > 0 ? `${Math.round((settledCount / filteredEntries.length) * 100)}%` : "0%";
1959
+
1960
+ const auditPayload = {
1961
+ status: "audit_complete",
1962
+ generated_at_utc: new Date().toISOString(),
1963
+ account_overview: {
1964
+ tier: acctData.tier || "Active",
1965
+ hash_credits_remaining: Number(acctData.hash_credits || 0),
1966
+ lifetime_anchors: Number(acctData.total_usage || totalInLedger || 0),
1967
+ masked_api_key: maskedKey,
1968
+ },
1969
+ audit_summary: {
1970
+ total_ledger_entries: totalInLedger,
1971
+ matched_entries_count: filteredEntries.length,
1972
+ settled_on_bitcoin_count: settledCount,
1973
+ pending_finality_count: pendingCount,
1974
+ settlement_rate_pct: settlementRatePct,
1975
+ mode_breakdown: {
1976
+ standard_count: standardCount,
1977
+ temporal_vdf_count: temporalCount,
1978
+ biometric_passkey_count: biometricCount,
1979
+ },
1980
+ bitcoin_settlement: {
1981
+ unique_blocks_count: uniqueBlocks.size,
1982
+ unique_txids_count: uniqueTxids.size,
1983
+ latest_settled_block: latestBlock,
1984
+ latest_txid: latestTxid,
1985
+ },
1986
+ hardware_passkey_signers: Array.from(signerMap.values()),
1987
+ },
1988
+ local_path_verification: localPathVerification,
1989
+ audit_trail: filteredEntries,
1990
+ };
1991
+
1992
+ // Determine output directory for reports
1993
+ let reportDir = path.join(PASSKEY_CONFIG_DIR, "audits");
1994
+ if (args.output_dir) {
1995
+ reportDir = resolveLocalFilePath(args.output_dir);
1996
+ } else if (localPathVerification) {
1997
+ const tp = localPathVerification.target_path;
1998
+ reportDir = fs.statSync(tp).isDirectory() ? tp : path.dirname(tp);
1999
+ }
2000
+ if (!fs.existsSync(reportDir)) {
2001
+ fs.mkdirSync(reportDir, { recursive: true });
2002
+ }
2003
+
2004
+ const dateStamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
2005
+ const savedReports = {};
2006
+
2007
+ if (args.save_html_report !== false) {
2008
+ const htmlPath = path.join(reportDir, `reltime-audit-report-${dateStamp}.html`);
2009
+ const htmlContent = generateAuditReportHtml(auditPayload);
2010
+ fs.writeFileSync(htmlPath, htmlContent, "utf8");
2011
+ savedReports.html_report_path = htmlPath;
2012
+
2013
+ if (args.open_in_browser !== false) {
2014
+ try {
2015
+ openSystemBrowser(htmlPath);
2016
+ savedReports.opened_in_browser = true;
2017
+ } catch (e) {
2018
+ savedReports.opened_in_browser = false;
2019
+ }
2020
+ }
2021
+ }
2022
+
2023
+ if (args.export_csv) {
2024
+ const csvPath = path.join(reportDir, `reltime-audit-trail-${dateStamp}.csv`);
2025
+ const csvHeader = [
2026
+ "timestamp_utc",
2027
+ "status",
2028
+ "anchor_mode",
2029
+ "identity_mode",
2030
+ "client_hash",
2031
+ "original_file_hash",
2032
+ "t_index",
2033
+ "vdf_ticks",
2034
+ "signer_public_key",
2035
+ "bitcoin_block_height",
2036
+ "bitcoin_txid",
2037
+ "merkle_root",
2038
+ "verify_url",
2039
+ ].join(",");
2040
+ const csvRows = filteredEntries.map((e) =>
2041
+ [
2042
+ e.timestamp_utc || "",
2043
+ e.status || "",
2044
+ e.anchor_mode || "",
2045
+ e.identity_mode || "",
2046
+ e.client_hash || "",
2047
+ e.original_file_hash || "",
2048
+ e.t_index || "",
2049
+ e.vdf_ticks || 0,
2050
+ e.signer_public_key || "",
2051
+ e.block_height || "",
2052
+ e.txid || "",
2053
+ e.merkle_root || "",
2054
+ e.verify_url || "",
2055
+ ]
2056
+ .map((v) => `"${String(v).replace(/"/g, '""')}"`)
2057
+ .join(",")
2058
+ );
2059
+ fs.writeFileSync(csvPath, [csvHeader, ...csvRows].join("\n"), "utf8");
2060
+ savedReports.csv_report_path = csvPath;
2061
+ }
2062
+
2063
+ auditPayload.saved_reports = savedReports;
2064
+ return auditPayload;
2065
+ }
2066
+
987
2067
  if (name === "reltime_provision_agent_key") {
988
2068
  if (args.action === "create_invoice") {
989
2069
  const validPacks = ["universal", "launch", "standard", "enterprise", "superhighway"];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rel-time/mcp-server",
3
- "version": "1.2.7",
3
+ "version": "1.2.8",
4
4
  "description": "Official Model Context Protocol (MCP) server for Rel-Time (rel-time.com) — Zero-Knowledge Bitcoin & VDF Provenance, Local File Hashing, and Sun-Stamp Generation for Claude, Cursor, Windsurf, and Antigravity.",
5
5
  "main": "index.js",
6
6
  "bin": {