@rel-time/mcp-server 1.2.7 → 1.2.9
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.
- package/README.md +11 -7
- package/index.js +1508 -1
- package/package.json +1 -1
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.
|
|
59
|
+
version: "1.2.9",
|
|
60
60
|
};
|
|
61
61
|
|
|
62
62
|
/**
|
|
@@ -477,6 +477,1056 @@ 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, "&")
|
|
502
|
+
.replace(/</g, "<")
|
|
503
|
+
.replace(/>/g, ">")
|
|
504
|
+
.replace(/"/g, """)
|
|
505
|
+
.replace(/'/g, "'");
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Generates a self-contained, institutional-grade 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 totalMatched = Math.max(1, Number(sum.matched_entries_count || entries.length || 1));
|
|
522
|
+
const settledCount = Number(sum.settled_on_bitcoin_count || 0);
|
|
523
|
+
const pendingCount = Number(sum.pending_finality_count || 0);
|
|
524
|
+
const standardCount = Number(modes.standard_count || 0);
|
|
525
|
+
const temporalCount = Number(modes.temporal_vdf_count || 0);
|
|
526
|
+
const biometricCount = Number(modes.biometric_passkey_count || 0);
|
|
527
|
+
const machineCount = Math.max(0, Number(sum.matched_entries_count || entries.length) - biometricCount);
|
|
528
|
+
|
|
529
|
+
const settledPct = Math.round((settledCount / totalMatched) * 100);
|
|
530
|
+
const standardPct = Math.round((standardCount / totalMatched) * 100);
|
|
531
|
+
const biometricPct = Math.round((biometricCount / totalMatched) * 100);
|
|
532
|
+
|
|
533
|
+
// Build Bitcoin Block Settlement Topology nodes from entries
|
|
534
|
+
const blockGroupsMap = new Map();
|
|
535
|
+
for (const e of entries) {
|
|
536
|
+
if (e.status === "settled" && e.block_height) {
|
|
537
|
+
const bh = String(e.block_height);
|
|
538
|
+
const cur = blockGroupsMap.get(bh) || {
|
|
539
|
+
block_height: bh,
|
|
540
|
+
count: 0,
|
|
541
|
+
txid: e.txid || null,
|
|
542
|
+
max_ticks: 0,
|
|
543
|
+
timestamp_utc: e.timestamp_utc || null,
|
|
544
|
+
};
|
|
545
|
+
cur.count++;
|
|
546
|
+
if (!cur.txid && e.txid) cur.txid = e.txid;
|
|
547
|
+
if (Number(e.vdf_ticks || 0) > cur.max_ticks) cur.max_ticks = Number(e.vdf_ticks || 0);
|
|
548
|
+
blockGroupsMap.set(bh, cur);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
const sortedBlocks = Array.from(blockGroupsMap.values())
|
|
552
|
+
.sort((a, b) => Number(a.block_height) - Number(b.block_height))
|
|
553
|
+
.slice(-5);
|
|
554
|
+
|
|
555
|
+
const topologyNodesHtml = [
|
|
556
|
+
...sortedBlocks.map(
|
|
557
|
+
(b) => `
|
|
558
|
+
<div class="topo-node">
|
|
559
|
+
<div class="topo-node-top">
|
|
560
|
+
<span class="dot dot-settled"></span>
|
|
561
|
+
<span class="mono topo-height">BLOCK #${escapeHtml(b.block_height)}</span>
|
|
562
|
+
</div>
|
|
563
|
+
<div class="topo-metric mono">${b.count} ${b.count === 1 ? "ANCHOR" : "ANCHORS"}</div>
|
|
564
|
+
<div class="topo-meta mono">
|
|
565
|
+
${
|
|
566
|
+
b.txid
|
|
567
|
+
? `<a href="https://mempool.space/tx/${escapeHtml(b.txid)}" target="_blank" rel="noopener">TX ${escapeHtml(
|
|
568
|
+
b.txid.slice(0, 8)
|
|
569
|
+
)}…${escapeHtml(b.txid.slice(-4))} ↗</a>`
|
|
570
|
+
: "SETTLED"
|
|
571
|
+
}
|
|
572
|
+
</div>
|
|
573
|
+
</div>`
|
|
574
|
+
),
|
|
575
|
+
pendingCount > 0
|
|
576
|
+
? `
|
|
577
|
+
<div class="topo-node topo-node-pending">
|
|
578
|
+
<div class="topo-node-top">
|
|
579
|
+
<span class="dot dot-pending"></span>
|
|
580
|
+
<span class="mono topo-height">VDF MEMPOOL QUEUE</span>
|
|
581
|
+
</div>
|
|
582
|
+
<div class="topo-metric mono">${pendingCount} PENDING</div>
|
|
583
|
+
<div class="topo-meta mono">NEXT EPOCH (~10m)</div>
|
|
584
|
+
</div>`
|
|
585
|
+
: "",
|
|
586
|
+
]
|
|
587
|
+
.filter(Boolean)
|
|
588
|
+
.join(`<div class="topo-connector"></div>`);
|
|
589
|
+
|
|
590
|
+
const signerRowsHtml = signers.length
|
|
591
|
+
? signers
|
|
592
|
+
.map((s, idx) => {
|
|
593
|
+
const sharePct = Math.round((Number(s.anchor_count || 0) / totalMatched) * 100);
|
|
594
|
+
return `
|
|
595
|
+
<tr>
|
|
596
|
+
<td class="mono muted">${String(idx + 1).padStart(2, "0")}</td>
|
|
597
|
+
<td>
|
|
598
|
+
<div class="signer-key-header">
|
|
599
|
+
<span class="tag tag-neutral">FIDO2 / COSE P-256</span>
|
|
600
|
+
<span class="mono muted">KEY ID: ${escapeHtml(s.signer_public_key.slice(0, 16).toUpperCase())}</span>
|
|
601
|
+
</div>
|
|
602
|
+
<div class="mono hash-full" style="margin-top:5px">${escapeHtml(s.signer_public_key)}</div>
|
|
603
|
+
</td>
|
|
604
|
+
<td class="mono">
|
|
605
|
+
<div style="display:flex;align-items:center;gap:10px">
|
|
606
|
+
<span style="font-weight:700;color:var(--text);min-width:24px">${s.anchor_count}</span>
|
|
607
|
+
<div class="mini-bar-track"><div class="mini-bar-fill" style="width:${sharePct}%"></div></div>
|
|
608
|
+
<span class="muted" style="font-size:11px">${sharePct}%</span>
|
|
609
|
+
</div>
|
|
610
|
+
</td>
|
|
611
|
+
<td class="mono">${s.settled_count} / ${s.anchor_count}</td>
|
|
612
|
+
<td class="mono muted">${escapeHtml(s.latest_timestamp_utc || "—")}</td>
|
|
613
|
+
</tr>`;
|
|
614
|
+
})
|
|
615
|
+
.join("")
|
|
616
|
+
: `<tr><td colspan="5" class="empty-cell">No hardware Passkey signatures recorded in this window. Link a FIDO2/WebAuthn hardware key with <code>reltime_link_passkey</code> and anchor with <code>biometric: true</code>.</td></tr>`;
|
|
617
|
+
|
|
618
|
+
const localVerificationHtml = localCheck
|
|
619
|
+
? `
|
|
620
|
+
<section class="panel">
|
|
621
|
+
<div class="panel-header">
|
|
622
|
+
<div>
|
|
623
|
+
<div class="section-index">SECTION 01 // ZERO-KNOWLEDGE LOCAL FILE INTEGRITY MATRIX</div>
|
|
624
|
+
<h2>Local Directory Cross-Reference</h2>
|
|
625
|
+
<p class="section-sub">Target Path: <span class="mono" style="color:var(--text-secondary)">${escapeHtml(
|
|
626
|
+
localCheck.target_path
|
|
627
|
+
)}</span> · 0 Bytes Uploaded · Local SHA-256 vs. Bitcoin Ledger</p>
|
|
628
|
+
</div>
|
|
629
|
+
<div class="status-pill ${
|
|
630
|
+
localCheck.summary.unanchored_or_modified_count === 0 ? "status-pill-ok" : "status-pill-alert"
|
|
631
|
+
}">
|
|
632
|
+
<span class="dot ${
|
|
633
|
+
localCheck.summary.unanchored_or_modified_count === 0 ? "dot-settled" : "dot-alert"
|
|
634
|
+
}"></span>
|
|
635
|
+
<span>${
|
|
636
|
+
localCheck.summary.unanchored_or_modified_count === 0
|
|
637
|
+
? `INTEGRITY VERIFIED (${localCheck.summary.total_files_scanned}/${localCheck.summary.total_files_scanned})`
|
|
638
|
+
: `${localCheck.summary.unanchored_or_modified_count} UNANCHORED OR MODIFIED`
|
|
639
|
+
}</span>
|
|
640
|
+
</div>
|
|
641
|
+
</div>
|
|
642
|
+
<div class="table-wrap">
|
|
643
|
+
<table>
|
|
644
|
+
<thead>
|
|
645
|
+
<tr>
|
|
646
|
+
<th style="width:42px">#</th>
|
|
647
|
+
<th>Artifact / File Name</th>
|
|
648
|
+
<th>Bytes</th>
|
|
649
|
+
<th>Local SHA-256 Digest</th>
|
|
650
|
+
<th>Verification Status</th>
|
|
651
|
+
<th>Attestation / Mode</th>
|
|
652
|
+
<th>Timechain Settlement</th>
|
|
653
|
+
<th style="text-align:right">Receipt</th>
|
|
654
|
+
</tr>
|
|
655
|
+
</thead>
|
|
656
|
+
<tbody>
|
|
657
|
+
${localCheck.files
|
|
658
|
+
.map((f, idx) => {
|
|
659
|
+
const statusTag =
|
|
660
|
+
f.integrity_status === "VERIFIED_SETTLED"
|
|
661
|
+
? `<span class="status-cell"><span class="dot dot-settled"></span><span class="mono" style="color:var(--text)">VERIFIED · SETTLED</span></span>`
|
|
662
|
+
: f.integrity_status === "VERIFIED_PENDING"
|
|
663
|
+
? `<span class="status-cell"><span class="dot dot-pending"></span><span class="mono" style="color:var(--text-secondary)">VERIFIED · PENDING</span></span>`
|
|
664
|
+
: `<span class="status-cell"><span class="dot dot-alert"></span><span class="mono" style="color:var(--crimson)">UNANCHORED / MODIFIED</span></span>`;
|
|
665
|
+
|
|
666
|
+
const signerText = f.signer_public_key
|
|
667
|
+
? `<span class="tag tag-neutral" title="Passkey COSE: ${escapeHtml(
|
|
668
|
+
f.signer_public_key
|
|
669
|
+
)}">PASSKEY ${escapeHtml(f.signer_public_key.slice(0, 8).toUpperCase())}…</span>`
|
|
670
|
+
: f.anchor_mode
|
|
671
|
+
? `<span class="tag tag-neutral">${escapeHtml(f.anchor_mode.toUpperCase())}</span>`
|
|
672
|
+
: `<span class="mono muted">—</span>`;
|
|
673
|
+
|
|
674
|
+
const btcText = f.block_height
|
|
675
|
+
? `<span class="mono" style="color:var(--text)">BLOCK #${escapeHtml(f.block_height)}</span>`
|
|
676
|
+
: f.integrity_status === "VERIFIED_PENDING"
|
|
677
|
+
? `<span class="mono muted">MEMPOOL QUEUE</span>`
|
|
678
|
+
: `<span class="mono muted">—</span>`;
|
|
679
|
+
|
|
680
|
+
const verifyLink = f.verify_url
|
|
681
|
+
? `<a class="action-link" href="${escapeHtml(f.verify_url)}" target="_blank" rel="noopener">VERIFY ↗</a>`
|
|
682
|
+
: `<span class="mono muted">—</span>`;
|
|
683
|
+
|
|
684
|
+
return `
|
|
685
|
+
<tr>
|
|
686
|
+
<td class="mono muted">${String(idx + 1).padStart(2, "0")}</td>
|
|
687
|
+
<td>
|
|
688
|
+
<span style="font-weight:600;color:var(--text)">${escapeHtml(f.file_name)}</span>
|
|
689
|
+
${f.is_stamped_copy ? `<span class="tag tag-subtle" style="margin-left:8px">STAMPED COPY</span>` : ""}
|
|
690
|
+
</td>
|
|
691
|
+
<td class="mono muted">${escapeHtml(f.size_human)}</td>
|
|
692
|
+
<td class="mono" style="color:var(--text-secondary)" title="${escapeHtml(f.local_sha256)}">${escapeHtml(
|
|
693
|
+
f.local_sha256.slice(0, 16)
|
|
694
|
+
)}…${escapeHtml(f.local_sha256.slice(-10))}</td>
|
|
695
|
+
<td>${statusTag}</td>
|
|
696
|
+
<td>${signerText}</td>
|
|
697
|
+
<td>${btcText}</td>
|
|
698
|
+
<td style="text-align:right">${verifyLink}</td>
|
|
699
|
+
</tr>`;
|
|
700
|
+
})
|
|
701
|
+
.join("")}
|
|
702
|
+
</tbody>
|
|
703
|
+
</table>
|
|
704
|
+
</div>
|
|
705
|
+
</section>`
|
|
706
|
+
: "";
|
|
707
|
+
|
|
708
|
+
const ledgerSectionNum = localCheck ? "03" : "02";
|
|
709
|
+
const signerSectionNum = localCheck ? "02" : "01";
|
|
710
|
+
|
|
711
|
+
const ledgerRowsHtml = entries.length
|
|
712
|
+
? entries
|
|
713
|
+
.map((e, idx) => {
|
|
714
|
+
const isSettled = e.status === "settled";
|
|
715
|
+
const isBiometric = Boolean(e.signer_public_key);
|
|
716
|
+
const modeType = e.anchor_mode || "standard";
|
|
717
|
+
const filterClasses = [
|
|
718
|
+
isSettled ? "status-settled" : "status-pending",
|
|
719
|
+
`mode-${modeType}`,
|
|
720
|
+
isBiometric ? "mode-biometric" : "",
|
|
721
|
+
]
|
|
722
|
+
.filter(Boolean)
|
|
723
|
+
.join(" ");
|
|
724
|
+
|
|
725
|
+
const statusHtml = isSettled
|
|
726
|
+
? `<span class="status-cell"><span class="dot dot-settled"></span><span class="mono" style="color:var(--text)">SETTLED</span></span>`
|
|
727
|
+
: `<span class="status-cell"><span class="dot dot-pending"></span><span class="mono muted">PENDING</span></span>`;
|
|
728
|
+
|
|
729
|
+
const modeHtml = `
|
|
730
|
+
<span class="tag tag-neutral">${escapeHtml(modeType.toUpperCase())}</span>
|
|
731
|
+
${
|
|
732
|
+
isBiometric
|
|
733
|
+
? `<span class="tag tag-subtle" title="Passkey COSE: ${escapeHtml(
|
|
734
|
+
e.signer_public_key
|
|
735
|
+
)}">PK:${escapeHtml(e.signer_public_key.slice(0, 8).toUpperCase())}</span>`
|
|
736
|
+
: ""
|
|
737
|
+
}
|
|
738
|
+
`;
|
|
739
|
+
|
|
740
|
+
const btcHtml = isSettled
|
|
741
|
+
? `
|
|
742
|
+
<div class="mono" style="color:var(--text);font-weight:600">BLOCK #${escapeHtml(e.block_height || "—")}</div>
|
|
743
|
+
${
|
|
744
|
+
e.txid
|
|
745
|
+
? `<a class="mono tx-link" href="https://mempool.space/tx/${escapeHtml(
|
|
746
|
+
e.txid
|
|
747
|
+
)}" target="_blank" rel="noopener" title="${escapeHtml(e.txid)}">TX ${escapeHtml(
|
|
748
|
+
e.txid.slice(0, 10)
|
|
749
|
+
)}…${escapeHtml(e.txid.slice(-6))} ↗</a>`
|
|
750
|
+
: ""
|
|
751
|
+
}
|
|
752
|
+
`
|
|
753
|
+
: `<span class="mono muted">AWAITING BLOCK</span>`;
|
|
754
|
+
|
|
755
|
+
return `
|
|
756
|
+
<tr class="ledger-row ${filterClasses}" data-search="${escapeHtml(
|
|
757
|
+
`${e.client_hash} ${e.t_index} ${e.original_file_hash || ""} ${e.signer_public_key || ""} ${
|
|
758
|
+
e.txid || ""
|
|
759
|
+
} ${e.block_height || ""} ${modeType}`.toLowerCase()
|
|
760
|
+
)}">
|
|
761
|
+
<td class="mono muted">${String(idx + 1).padStart(2, "0")}</td>
|
|
762
|
+
<td class="mono muted">${escapeHtml((e.timestamp_utc || "—").replace(".000Z", "Z"))}</td>
|
|
763
|
+
<td>${statusHtml}</td>
|
|
764
|
+
<td>${modeHtml}</td>
|
|
765
|
+
<td class="mono" style="color:var(--text-secondary)" title="Client Hash: ${escapeHtml(e.client_hash)}${
|
|
766
|
+
e.original_file_hash ? ` | Original File SHA-256: ${escapeHtml(e.original_file_hash)}` : ""
|
|
767
|
+
}">
|
|
768
|
+
<div>${escapeHtml(e.client_hash.slice(0, 14))}…${escapeHtml(e.client_hash.slice(-10))}</div>
|
|
769
|
+
${
|
|
770
|
+
e.original_file_hash
|
|
771
|
+
? `<div class="muted" style="font-size:10.5px;margin-top:2px">FILE: ${escapeHtml(
|
|
772
|
+
e.original_file_hash.slice(0, 12)
|
|
773
|
+
)}…</div>`
|
|
774
|
+
: ""
|
|
775
|
+
}
|
|
776
|
+
</td>
|
|
777
|
+
<td class="mono" style="color:var(--text-secondary)" title="T_index: ${escapeHtml(e.t_index)} | Solar Ticks: ${escapeHtml(
|
|
778
|
+
e.vdf_ticks
|
|
779
|
+
)}">
|
|
780
|
+
<div>${escapeHtml(e.t_index.slice(0, 14))}…${escapeHtml(e.t_index.slice(-8))}</div>
|
|
781
|
+
<div class="muted" style="font-size:10.5px;margin-top:2px">VDF TICKS: ${Number(
|
|
782
|
+
e.vdf_ticks || 0
|
|
783
|
+
).toLocaleString()}</div>
|
|
784
|
+
</td>
|
|
785
|
+
<td>${btcHtml}</td>
|
|
786
|
+
<td style="text-align:right">
|
|
787
|
+
<a class="action-link" href="${escapeHtml(e.verify_url)}" target="_blank" rel="noopener">VERIFY ↗</a>
|
|
788
|
+
</td>
|
|
789
|
+
</tr>`;
|
|
790
|
+
})
|
|
791
|
+
.join("")
|
|
792
|
+
: `<tr><td colspan="8" class="empty-cell">No ledger entries matched the active filter criteria.</td></tr>`;
|
|
793
|
+
|
|
794
|
+
return `<!DOCTYPE html>
|
|
795
|
+
<html lang="en" data-theme="dark">
|
|
796
|
+
<head>
|
|
797
|
+
<meta charset="UTF-8" />
|
|
798
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
799
|
+
<title>Rel-Time Superhighway // Forensic Timechain Audit Report</title>
|
|
800
|
+
<style>
|
|
801
|
+
:root {
|
|
802
|
+
--bg: #050506;
|
|
803
|
+
--grid-line: rgba(255, 255, 255, 0.015);
|
|
804
|
+
--panel: #0a0a0c;
|
|
805
|
+
--panel-alt: #0e0e12;
|
|
806
|
+
--border: #1e1e24;
|
|
807
|
+
--border-strong: #2e2e38;
|
|
808
|
+
--text: #f4f4f5;
|
|
809
|
+
--text-secondary: #d4d4d8;
|
|
810
|
+
--muted: #71717a;
|
|
811
|
+
--amber: #f59e0b;
|
|
812
|
+
--emerald: #10b981;
|
|
813
|
+
--crimson: #ef4444;
|
|
814
|
+
--track-bg: #18181d;
|
|
815
|
+
--bar-primary: #e4e4e7;
|
|
816
|
+
--bar-muted: #3f3f46;
|
|
817
|
+
--tag-bg: #141418;
|
|
818
|
+
--tab-active-bg: #18181f;
|
|
819
|
+
--row-hover: rgba(255, 255, 255, 0.015);
|
|
820
|
+
--btn-primary-fg: #050506;
|
|
821
|
+
}
|
|
822
|
+
html[data-theme="light"] {
|
|
823
|
+
--bg: #f4f4f6;
|
|
824
|
+
--grid-line: rgba(0, 0, 0, 0.03);
|
|
825
|
+
--panel: #ffffff;
|
|
826
|
+
--panel-alt: #f8f8fa;
|
|
827
|
+
--border: #e4e4e7;
|
|
828
|
+
--border-strong: #d4d4d8;
|
|
829
|
+
--text: #09090b;
|
|
830
|
+
--text-secondary: #3f3f46;
|
|
831
|
+
--muted: #71717a;
|
|
832
|
+
--amber: #d97706;
|
|
833
|
+
--emerald: #059669;
|
|
834
|
+
--crimson: #dc2626;
|
|
835
|
+
--track-bg: #e4e4e7;
|
|
836
|
+
--bar-primary: #18181b;
|
|
837
|
+
--bar-muted: #a1a1aa;
|
|
838
|
+
--tag-bg: #f4f4f5;
|
|
839
|
+
--tab-active-bg: #f4f4f5;
|
|
840
|
+
--row-hover: rgba(0, 0, 0, 0.02);
|
|
841
|
+
--btn-primary-fg: #ffffff;
|
|
842
|
+
}
|
|
843
|
+
* { box-sizing: border-box; }
|
|
844
|
+
body {
|
|
845
|
+
margin: 0;
|
|
846
|
+
padding: 28px 24px 64px;
|
|
847
|
+
background-color: var(--bg);
|
|
848
|
+
background-image:
|
|
849
|
+
linear-gradient(to right, var(--grid-line) 1px, transparent 1px),
|
|
850
|
+
linear-gradient(to bottom, var(--grid-line) 1px, transparent 1px);
|
|
851
|
+
background-size: 32px 32px;
|
|
852
|
+
color: var(--text);
|
|
853
|
+
font-family: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", Roboto, sans-serif;
|
|
854
|
+
font-size: 13px;
|
|
855
|
+
line-height: 1.45;
|
|
856
|
+
-webkit-font-smoothing: antialiased;
|
|
857
|
+
transition: background-color 0.15s ease, color 0.15s ease;
|
|
858
|
+
}
|
|
859
|
+
.container { max-width: 1360px; margin: 0 auto; }
|
|
860
|
+
.mono {
|
|
861
|
+
font-family: "JetBrains Mono", "SF Mono", "Fira Code", Menlo, Consolas, monospace;
|
|
862
|
+
font-variant-numeric: tabular-nums;
|
|
863
|
+
font-size: 12px;
|
|
864
|
+
}
|
|
865
|
+
.muted { color: var(--muted); }
|
|
866
|
+
|
|
867
|
+
/* Masthead */
|
|
868
|
+
.masthead {
|
|
869
|
+
background: var(--panel);
|
|
870
|
+
border: 1px solid var(--border);
|
|
871
|
+
border-top: 2px solid var(--amber);
|
|
872
|
+
padding: 22px 26px;
|
|
873
|
+
display: flex;
|
|
874
|
+
flex-wrap: wrap;
|
|
875
|
+
justify-content: space-between;
|
|
876
|
+
align-items: flex-start;
|
|
877
|
+
gap: 20px;
|
|
878
|
+
margin-bottom: 16px;
|
|
879
|
+
}
|
|
880
|
+
.eyebrow {
|
|
881
|
+
font-family: "JetBrains Mono", "SF Mono", Menlo, monospace;
|
|
882
|
+
font-size: 10.5px;
|
|
883
|
+
letter-spacing: 0.16em;
|
|
884
|
+
text-transform: uppercase;
|
|
885
|
+
color: var(--amber);
|
|
886
|
+
margin-bottom: 6px;
|
|
887
|
+
}
|
|
888
|
+
h1 {
|
|
889
|
+
margin: 0;
|
|
890
|
+
font-size: 22px;
|
|
891
|
+
font-weight: 700;
|
|
892
|
+
letter-spacing: -0.02em;
|
|
893
|
+
color: var(--text);
|
|
894
|
+
}
|
|
895
|
+
.masthead-meta {
|
|
896
|
+
margin-top: 10px;
|
|
897
|
+
display: flex;
|
|
898
|
+
flex-wrap: wrap;
|
|
899
|
+
gap: 18px;
|
|
900
|
+
font-size: 11.5px;
|
|
901
|
+
color: var(--muted);
|
|
902
|
+
}
|
|
903
|
+
.masthead-meta span strong {
|
|
904
|
+
color: var(--text-secondary);
|
|
905
|
+
font-weight: 600;
|
|
906
|
+
}
|
|
907
|
+
.actions-bar {
|
|
908
|
+
display: flex;
|
|
909
|
+
flex-wrap: wrap;
|
|
910
|
+
gap: 8px;
|
|
911
|
+
align-items: center;
|
|
912
|
+
}
|
|
913
|
+
.btn-primary {
|
|
914
|
+
background: var(--text);
|
|
915
|
+
color: var(--btn-primary-fg);
|
|
916
|
+
border: 1px solid var(--text);
|
|
917
|
+
padding: 9px 14px;
|
|
918
|
+
font-family: "JetBrains Mono", "SF Mono", Menlo, monospace;
|
|
919
|
+
font-size: 11px;
|
|
920
|
+
font-weight: 700;
|
|
921
|
+
letter-spacing: 0.06em;
|
|
922
|
+
text-transform: uppercase;
|
|
923
|
+
cursor: pointer;
|
|
924
|
+
text-decoration: none;
|
|
925
|
+
}
|
|
926
|
+
.btn-primary:hover {
|
|
927
|
+
background: var(--amber);
|
|
928
|
+
border-color: var(--amber);
|
|
929
|
+
color: #050506;
|
|
930
|
+
}
|
|
931
|
+
.btn-secondary {
|
|
932
|
+
background: transparent;
|
|
933
|
+
color: var(--text-secondary);
|
|
934
|
+
border: 1px solid var(--border-strong);
|
|
935
|
+
padding: 9px 14px;
|
|
936
|
+
font-family: "JetBrains Mono", "SF Mono", Menlo, monospace;
|
|
937
|
+
font-size: 11px;
|
|
938
|
+
font-weight: 600;
|
|
939
|
+
letter-spacing: 0.06em;
|
|
940
|
+
text-transform: uppercase;
|
|
941
|
+
cursor: pointer;
|
|
942
|
+
text-decoration: none;
|
|
943
|
+
}
|
|
944
|
+
.btn-secondary:hover {
|
|
945
|
+
border-color: var(--text-secondary);
|
|
946
|
+
color: var(--text);
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
/* Flush Architectural Telemetry Strip */
|
|
950
|
+
.telemetry-strip {
|
|
951
|
+
display: grid;
|
|
952
|
+
grid-template-columns: repeat(6, 1fr);
|
|
953
|
+
background: var(--panel);
|
|
954
|
+
border: 1px solid var(--border);
|
|
955
|
+
margin-bottom: 16px;
|
|
956
|
+
}
|
|
957
|
+
@media (max-width: 1100px) {
|
|
958
|
+
.telemetry-strip { grid-template-columns: repeat(3, 1fr); }
|
|
959
|
+
}
|
|
960
|
+
@media (max-width: 640px) {
|
|
961
|
+
.telemetry-strip { grid-template-columns: repeat(2, 1fr); }
|
|
962
|
+
}
|
|
963
|
+
.telemetry-cell {
|
|
964
|
+
padding: 16px 20px;
|
|
965
|
+
border-right: 1px solid var(--border);
|
|
966
|
+
border-bottom: 1px solid var(--border);
|
|
967
|
+
}
|
|
968
|
+
.telemetry-cell:last-child { border-right: none; }
|
|
969
|
+
.telemetry-label {
|
|
970
|
+
font-family: "JetBrains Mono", "SF Mono", Menlo, monospace;
|
|
971
|
+
font-size: 10px;
|
|
972
|
+
text-transform: uppercase;
|
|
973
|
+
letter-spacing: 0.12em;
|
|
974
|
+
color: var(--muted);
|
|
975
|
+
margin-bottom: 8px;
|
|
976
|
+
}
|
|
977
|
+
.telemetry-val {
|
|
978
|
+
font-family: "JetBrains Mono", "SF Mono", Menlo, monospace;
|
|
979
|
+
font-size: 24px;
|
|
980
|
+
font-weight: 700;
|
|
981
|
+
color: var(--text);
|
|
982
|
+
letter-spacing: -0.02em;
|
|
983
|
+
}
|
|
984
|
+
.telemetry-sub {
|
|
985
|
+
font-size: 11px;
|
|
986
|
+
color: var(--muted);
|
|
987
|
+
margin-top: 4px;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
/* Visualization Grid (2 Columns) */
|
|
991
|
+
.viz-grid {
|
|
992
|
+
display: grid;
|
|
993
|
+
grid-template-columns: 1fr 1.25fr;
|
|
994
|
+
gap: 16px;
|
|
995
|
+
margin-bottom: 16px;
|
|
996
|
+
}
|
|
997
|
+
@media (max-width: 980px) {
|
|
998
|
+
.viz-grid { grid-template-columns: 1fr; }
|
|
999
|
+
}
|
|
1000
|
+
.panel {
|
|
1001
|
+
background: var(--panel);
|
|
1002
|
+
border: 1px solid var(--border);
|
|
1003
|
+
padding: 20px 24px;
|
|
1004
|
+
margin-bottom: 16px;
|
|
1005
|
+
}
|
|
1006
|
+
.viz-grid .panel { margin-bottom: 0; }
|
|
1007
|
+
.panel-header {
|
|
1008
|
+
display: flex;
|
|
1009
|
+
flex-wrap: wrap;
|
|
1010
|
+
justify-content: space-between;
|
|
1011
|
+
align-items: flex-start;
|
|
1012
|
+
gap: 12px;
|
|
1013
|
+
padding-bottom: 14px;
|
|
1014
|
+
border-bottom: 1px solid var(--border);
|
|
1015
|
+
margin-bottom: 16px;
|
|
1016
|
+
}
|
|
1017
|
+
.section-index {
|
|
1018
|
+
font-family: "JetBrains Mono", "SF Mono", Menlo, monospace;
|
|
1019
|
+
font-size: 10px;
|
|
1020
|
+
letter-spacing: 0.14em;
|
|
1021
|
+
text-transform: uppercase;
|
|
1022
|
+
color: var(--muted);
|
|
1023
|
+
margin-bottom: 3px;
|
|
1024
|
+
}
|
|
1025
|
+
h2 {
|
|
1026
|
+
margin: 0;
|
|
1027
|
+
font-size: 15px;
|
|
1028
|
+
font-weight: 700;
|
|
1029
|
+
letter-spacing: -0.01em;
|
|
1030
|
+
color: var(--text);
|
|
1031
|
+
}
|
|
1032
|
+
.section-sub {
|
|
1033
|
+
margin: 3px 0 0;
|
|
1034
|
+
font-size: 12px;
|
|
1035
|
+
color: var(--muted);
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
/* Distribution Bars */
|
|
1039
|
+
.dist-row {
|
|
1040
|
+
margin-bottom: 14px;
|
|
1041
|
+
}
|
|
1042
|
+
.dist-row:last-child { margin-bottom: 0; }
|
|
1043
|
+
.dist-header {
|
|
1044
|
+
display: flex;
|
|
1045
|
+
justify-content: space-between;
|
|
1046
|
+
font-size: 11.5px;
|
|
1047
|
+
margin-bottom: 6px;
|
|
1048
|
+
}
|
|
1049
|
+
.dist-track {
|
|
1050
|
+
height: 6px;
|
|
1051
|
+
width: 100%;
|
|
1052
|
+
background: var(--track-bg);
|
|
1053
|
+
display: flex;
|
|
1054
|
+
overflow: hidden;
|
|
1055
|
+
}
|
|
1056
|
+
.dist-seg-primary { background: var(--bar-primary); height: 100%; }
|
|
1057
|
+
.dist-seg-emerald { background: var(--emerald); height: 100%; }
|
|
1058
|
+
.dist-seg-amber { background: var(--amber); height: 100%; }
|
|
1059
|
+
.dist-seg-muted { background: var(--bar-muted); height: 100%; }
|
|
1060
|
+
|
|
1061
|
+
/* Bitcoin Topology Strip */
|
|
1062
|
+
.topo-chain {
|
|
1063
|
+
display: flex;
|
|
1064
|
+
align-items: stretch;
|
|
1065
|
+
gap: 0;
|
|
1066
|
+
overflow-x: auto;
|
|
1067
|
+
padding: 6px 0 4px;
|
|
1068
|
+
}
|
|
1069
|
+
.topo-node {
|
|
1070
|
+
flex: 1;
|
|
1071
|
+
min-width: 135px;
|
|
1072
|
+
background: var(--panel-alt);
|
|
1073
|
+
border: 1px solid var(--border-strong);
|
|
1074
|
+
padding: 12px 14px;
|
|
1075
|
+
}
|
|
1076
|
+
.topo-node-pending {
|
|
1077
|
+
border-style: dashed;
|
|
1078
|
+
border-color: rgba(245, 158, 11, 0.55);
|
|
1079
|
+
}
|
|
1080
|
+
.topo-node-top {
|
|
1081
|
+
display: flex;
|
|
1082
|
+
align-items: center;
|
|
1083
|
+
gap: 6px;
|
|
1084
|
+
margin-bottom: 8px;
|
|
1085
|
+
}
|
|
1086
|
+
.topo-height {
|
|
1087
|
+
font-size: 11px;
|
|
1088
|
+
font-weight: 700;
|
|
1089
|
+
color: var(--text);
|
|
1090
|
+
}
|
|
1091
|
+
.topo-metric {
|
|
1092
|
+
font-size: 14px;
|
|
1093
|
+
font-weight: 700;
|
|
1094
|
+
color: var(--text-secondary);
|
|
1095
|
+
margin-bottom: 4px;
|
|
1096
|
+
}
|
|
1097
|
+
.topo-meta {
|
|
1098
|
+
font-size: 10.5px;
|
|
1099
|
+
color: var(--muted);
|
|
1100
|
+
}
|
|
1101
|
+
.topo-meta a {
|
|
1102
|
+
color: var(--muted);
|
|
1103
|
+
text-decoration: none;
|
|
1104
|
+
border-bottom: 1px dotted var(--muted);
|
|
1105
|
+
}
|
|
1106
|
+
.topo-meta a:hover { color: var(--text); border-bottom-color: var(--text); }
|
|
1107
|
+
.topo-connector {
|
|
1108
|
+
width: 18px;
|
|
1109
|
+
min-width: 18px;
|
|
1110
|
+
display: flex;
|
|
1111
|
+
align-items: center;
|
|
1112
|
+
justify-content: center;
|
|
1113
|
+
position: relative;
|
|
1114
|
+
}
|
|
1115
|
+
.topo-connector::before {
|
|
1116
|
+
content: "";
|
|
1117
|
+
width: 100%;
|
|
1118
|
+
height: 1px;
|
|
1119
|
+
background: var(--border-strong);
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
/* Status Dots & Tags */
|
|
1123
|
+
.status-cell {
|
|
1124
|
+
display: inline-flex;
|
|
1125
|
+
align-items: center;
|
|
1126
|
+
gap: 7px;
|
|
1127
|
+
white-space: nowrap;
|
|
1128
|
+
}
|
|
1129
|
+
.dot {
|
|
1130
|
+
width: 6px;
|
|
1131
|
+
height: 6px;
|
|
1132
|
+
border-radius: 50%;
|
|
1133
|
+
display: inline-block;
|
|
1134
|
+
flex-shrink: 0;
|
|
1135
|
+
}
|
|
1136
|
+
.dot-settled { background: var(--emerald); }
|
|
1137
|
+
.dot-pending { background: var(--amber); }
|
|
1138
|
+
.dot-alert { background: var(--crimson); }
|
|
1139
|
+
|
|
1140
|
+
.status-pill {
|
|
1141
|
+
display: inline-flex;
|
|
1142
|
+
align-items: center;
|
|
1143
|
+
gap: 8px;
|
|
1144
|
+
padding: 5px 10px;
|
|
1145
|
+
border: 1px solid var(--border-strong);
|
|
1146
|
+
background: var(--panel-alt);
|
|
1147
|
+
font-family: "JetBrains Mono", "SF Mono", Menlo, monospace;
|
|
1148
|
+
font-size: 11px;
|
|
1149
|
+
font-weight: 600;
|
|
1150
|
+
}
|
|
1151
|
+
.status-pill-ok { border-color: rgba(16, 185, 129, 0.4); color: var(--text); }
|
|
1152
|
+
.status-pill-alert { border-color: rgba(239, 68, 68, 0.5); color: var(--crimson); }
|
|
1153
|
+
|
|
1154
|
+
.tag {
|
|
1155
|
+
display: inline-block;
|
|
1156
|
+
padding: 2px 6px;
|
|
1157
|
+
font-family: "JetBrains Mono", "SF Mono", Menlo, monospace;
|
|
1158
|
+
font-size: 10px;
|
|
1159
|
+
font-weight: 600;
|
|
1160
|
+
letter-spacing: 0.04em;
|
|
1161
|
+
white-space: nowrap;
|
|
1162
|
+
}
|
|
1163
|
+
.tag-neutral {
|
|
1164
|
+
background: var(--tag-bg);
|
|
1165
|
+
border: 1px solid var(--border-strong);
|
|
1166
|
+
color: var(--text-secondary);
|
|
1167
|
+
}
|
|
1168
|
+
.tag-subtle {
|
|
1169
|
+
background: transparent;
|
|
1170
|
+
border: 1px solid var(--border);
|
|
1171
|
+
color: var(--muted);
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
.mini-bar-track {
|
|
1175
|
+
width: 72px;
|
|
1176
|
+
height: 4px;
|
|
1177
|
+
background: var(--track-bg);
|
|
1178
|
+
overflow: hidden;
|
|
1179
|
+
}
|
|
1180
|
+
.mini-bar-fill {
|
|
1181
|
+
height: 100%;
|
|
1182
|
+
background: var(--text-secondary);
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
/* Filter Bar */
|
|
1186
|
+
.filter-bar {
|
|
1187
|
+
display: flex;
|
|
1188
|
+
flex-wrap: wrap;
|
|
1189
|
+
justify-content: space-between;
|
|
1190
|
+
gap: 10px;
|
|
1191
|
+
align-items: center;
|
|
1192
|
+
margin-bottom: 14px;
|
|
1193
|
+
}
|
|
1194
|
+
.search-input {
|
|
1195
|
+
background: var(--bg);
|
|
1196
|
+
border: 1px solid var(--border-strong);
|
|
1197
|
+
color: var(--text);
|
|
1198
|
+
padding: 7px 12px;
|
|
1199
|
+
font-family: "JetBrains Mono", "SF Mono", Menlo, monospace;
|
|
1200
|
+
font-size: 11.5px;
|
|
1201
|
+
min-width: 290px;
|
|
1202
|
+
outline: none;
|
|
1203
|
+
}
|
|
1204
|
+
.search-input:focus { border-color: var(--text-secondary); }
|
|
1205
|
+
.tab-group {
|
|
1206
|
+
display: inline-flex;
|
|
1207
|
+
border: 1px solid var(--border-strong);
|
|
1208
|
+
background: var(--bg);
|
|
1209
|
+
}
|
|
1210
|
+
.filter-tab {
|
|
1211
|
+
background: transparent;
|
|
1212
|
+
border: none;
|
|
1213
|
+
border-right: 1px solid var(--border);
|
|
1214
|
+
color: var(--muted);
|
|
1215
|
+
padding: 6px 11px;
|
|
1216
|
+
font-family: "JetBrains Mono", "SF Mono", Menlo, monospace;
|
|
1217
|
+
font-size: 10.5px;
|
|
1218
|
+
font-weight: 600;
|
|
1219
|
+
text-transform: uppercase;
|
|
1220
|
+
letter-spacing: 0.05em;
|
|
1221
|
+
cursor: pointer;
|
|
1222
|
+
}
|
|
1223
|
+
.filter-tab:last-child { border-right: none; }
|
|
1224
|
+
.filter-tab:hover { color: var(--text-secondary); }
|
|
1225
|
+
.filter-tab.active {
|
|
1226
|
+
background: var(--tab-active-bg);
|
|
1227
|
+
color: var(--text);
|
|
1228
|
+
box-shadow: inset 0 -2px 0 var(--amber);
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
/* Tables */
|
|
1232
|
+
.table-wrap { overflow-x: auto; }
|
|
1233
|
+
table {
|
|
1234
|
+
width: 100%;
|
|
1235
|
+
border-collapse: collapse;
|
|
1236
|
+
}
|
|
1237
|
+
th {
|
|
1238
|
+
text-align: left;
|
|
1239
|
+
padding: 10px 12px;
|
|
1240
|
+
font-family: "JetBrains Mono", "SF Mono", Menlo, monospace;
|
|
1241
|
+
font-size: 10px;
|
|
1242
|
+
font-weight: 600;
|
|
1243
|
+
text-transform: uppercase;
|
|
1244
|
+
letter-spacing: 0.1em;
|
|
1245
|
+
color: var(--muted);
|
|
1246
|
+
background: var(--bg);
|
|
1247
|
+
border-top: 1px solid var(--border);
|
|
1248
|
+
border-bottom: 1px solid var(--border);
|
|
1249
|
+
white-space: nowrap;
|
|
1250
|
+
}
|
|
1251
|
+
td {
|
|
1252
|
+
padding: 11px 12px;
|
|
1253
|
+
border-bottom: 1px solid var(--border);
|
|
1254
|
+
vertical-align: middle;
|
|
1255
|
+
}
|
|
1256
|
+
tr:hover td { background: var(--row-hover); }
|
|
1257
|
+
.hash-full {
|
|
1258
|
+
word-break: break-all;
|
|
1259
|
+
color: var(--text-secondary);
|
|
1260
|
+
font-size: 11px;
|
|
1261
|
+
}
|
|
1262
|
+
.signer-key-header {
|
|
1263
|
+
display: flex;
|
|
1264
|
+
align-items: center;
|
|
1265
|
+
gap: 8px;
|
|
1266
|
+
}
|
|
1267
|
+
.action-link {
|
|
1268
|
+
font-family: "JetBrains Mono", "SF Mono", Menlo, monospace;
|
|
1269
|
+
font-size: 10.5px;
|
|
1270
|
+
font-weight: 600;
|
|
1271
|
+
color: var(--text-secondary);
|
|
1272
|
+
text-decoration: none;
|
|
1273
|
+
padding: 4px 8px;
|
|
1274
|
+
border: 1px solid var(--border-strong);
|
|
1275
|
+
background: var(--bg);
|
|
1276
|
+
white-space: nowrap;
|
|
1277
|
+
}
|
|
1278
|
+
.action-link:hover {
|
|
1279
|
+
border-color: var(--amber);
|
|
1280
|
+
color: var(--amber);
|
|
1281
|
+
}
|
|
1282
|
+
.tx-link {
|
|
1283
|
+
color: var(--muted);
|
|
1284
|
+
text-decoration: none;
|
|
1285
|
+
font-size: 10.5px;
|
|
1286
|
+
display: inline-block;
|
|
1287
|
+
margin-top: 2px;
|
|
1288
|
+
}
|
|
1289
|
+
.tx-link:hover { color: var(--text); }
|
|
1290
|
+
.empty-cell {
|
|
1291
|
+
text-align: center;
|
|
1292
|
+
color: var(--muted);
|
|
1293
|
+
padding: 24px;
|
|
1294
|
+
font-family: "JetBrains Mono", "SF Mono", Menlo, monospace;
|
|
1295
|
+
font-size: 11.5px;
|
|
1296
|
+
}
|
|
1297
|
+
@media print {
|
|
1298
|
+
body { background: var(--bg) !important; -webkit-print-color-adjust: exact; print-color-adjust: exact; padding: 10px; }
|
|
1299
|
+
.no-print { display: none !important; }
|
|
1300
|
+
}
|
|
1301
|
+
</style>
|
|
1302
|
+
</head>
|
|
1303
|
+
<body>
|
|
1304
|
+
<div class="container">
|
|
1305
|
+
<header class="masthead">
|
|
1306
|
+
<div>
|
|
1307
|
+
<div class="eyebrow">REL-TIME SOVEREIGN PROVENANCE PROTOCOL // FORENSIC AUDIT LEDGER</div>
|
|
1308
|
+
<h1>Cryptographic Data Integrity & Timechain Settlement Report</h1>
|
|
1309
|
+
<div class="masthead-meta mono">
|
|
1310
|
+
<span>GENERATED UTC: <strong>${escapeHtml(generatedUtc)}</strong></span>
|
|
1311
|
+
<span>SLA TIER: <strong>${escapeHtml(acct.tier || "Active")}</strong></span>
|
|
1312
|
+
<span>CREDENTIAL: <strong>${escapeHtml(acct.masked_api_key || "rel_key_***")}</strong></span>
|
|
1313
|
+
</div>
|
|
1314
|
+
</div>
|
|
1315
|
+
<div class="actions-bar no-print">
|
|
1316
|
+
<button id="themeToggleBtn" class="btn-secondary" onclick="toggleTheme()">[ ☀ LIGHT MODE ]</button>
|
|
1317
|
+
<a class="btn-secondary" href="https://rel-time.com/verify" target="_blank" rel="noopener">[ OPEN VERIFIER ↗ ]</a>
|
|
1318
|
+
<button class="btn-primary" onclick="window.print()">[ EXPORT PDF CERTIFICATE ]</button>
|
|
1319
|
+
</div>
|
|
1320
|
+
</header>
|
|
1321
|
+
|
|
1322
|
+
<div class="telemetry-strip">
|
|
1323
|
+
<div class="telemetry-cell">
|
|
1324
|
+
<div class="telemetry-label">Audited Scope</div>
|
|
1325
|
+
<div class="telemetry-val">${Number(sum.matched_entries_count || 0).toLocaleString()}</div>
|
|
1326
|
+
<div class="telemetry-sub mono">LIFETIME: ${Number(acct.lifetime_anchors || 0).toLocaleString()}</div>
|
|
1327
|
+
</div>
|
|
1328
|
+
<div class="telemetry-cell">
|
|
1329
|
+
<div class="telemetry-label">Timechain Finality</div>
|
|
1330
|
+
<div class="telemetry-val">${settledCount.toLocaleString()} <span style="font-size:14px;color:var(--muted)">/ ${Number(
|
|
1331
|
+
sum.matched_entries_count || 0
|
|
1332
|
+
)}</span></div>
|
|
1333
|
+
<div class="telemetry-sub mono">${escapeHtml(sum.settlement_rate_pct || "0%")} SETTLED · ${pendingCount} QUEUED</div>
|
|
1334
|
+
</div>
|
|
1335
|
+
<div class="telemetry-cell">
|
|
1336
|
+
<div class="telemetry-label">Bitcoin Blocks</div>
|
|
1337
|
+
<div class="telemetry-val">${Number(btc.unique_blocks_count || 0)}</div>
|
|
1338
|
+
<div class="telemetry-sub mono">${Number(btc.unique_txids_count || 0)} OP_RETURN TXS${
|
|
1339
|
+
btc.latest_settled_block ? ` · #${escapeHtml(btc.latest_settled_block)}` : ""
|
|
1340
|
+
}</div>
|
|
1341
|
+
</div>
|
|
1342
|
+
<div class="telemetry-cell">
|
|
1343
|
+
<div class="telemetry-label">Mode Split</div>
|
|
1344
|
+
<div class="telemetry-val">${standardCount} <span style="font-size:14px;color:var(--muted)">:</span> ${temporalCount}</div>
|
|
1345
|
+
<div class="telemetry-sub mono">STANDARD : TEMPORAL VDF</div>
|
|
1346
|
+
</div>
|
|
1347
|
+
<div class="telemetry-cell">
|
|
1348
|
+
<div class="telemetry-label">Passkey Authorities</div>
|
|
1349
|
+
<div class="telemetry-val">${signers.length}</div>
|
|
1350
|
+
<div class="telemetry-sub mono">${biometricCount} BIOMETRIC SIGNATURES</div>
|
|
1351
|
+
</div>
|
|
1352
|
+
<div class="telemetry-cell">
|
|
1353
|
+
<div class="telemetry-label">Available Fuel</div>
|
|
1354
|
+
<div class="telemetry-val" style="color:var(--amber)">${Number(
|
|
1355
|
+
acct.hash_credits_remaining || 0
|
|
1356
|
+
).toLocaleString()}</div>
|
|
1357
|
+
<div class="telemetry-sub mono">HASH CREDITS REMAINING</div>
|
|
1358
|
+
</div>
|
|
1359
|
+
</div>
|
|
1360
|
+
|
|
1361
|
+
<div class="viz-grid">
|
|
1362
|
+
<div class="panel">
|
|
1363
|
+
<div class="panel-header" style="margin-bottom:14px;padding-bottom:10px">
|
|
1364
|
+
<div>
|
|
1365
|
+
<div class="section-index">TELEMETRY // DISTRIBUTION METRICS</div>
|
|
1366
|
+
<h2>Pipeline & Attestation Ratios</h2>
|
|
1367
|
+
</div>
|
|
1368
|
+
</div>
|
|
1369
|
+
<div class="dist-row">
|
|
1370
|
+
<div class="dist-header mono">
|
|
1371
|
+
<span>BITCOIN FINALITY (${settledPct}% SETTLED)</span>
|
|
1372
|
+
<span class="muted">${settledCount} SETTLED / ${pendingCount} PENDING</span>
|
|
1373
|
+
</div>
|
|
1374
|
+
<div class="dist-track">
|
|
1375
|
+
<div class="dist-seg-emerald" style="width:${settledPct}%"></div>
|
|
1376
|
+
<div class="dist-seg-amber" style="width:${100 - settledPct}%"></div>
|
|
1377
|
+
</div>
|
|
1378
|
+
</div>
|
|
1379
|
+
<div class="dist-row">
|
|
1380
|
+
<div class="dist-header mono">
|
|
1381
|
+
<span>HASHING ARCHITECTURE (${standardPct}% STANDARD)</span>
|
|
1382
|
+
<span class="muted">${standardCount} INTEGRITY / ${temporalCount} TEMPORAL VDF</span>
|
|
1383
|
+
</div>
|
|
1384
|
+
<div class="dist-track">
|
|
1385
|
+
<div class="dist-seg-primary" style="width:${standardPct}%"></div>
|
|
1386
|
+
<div class="dist-seg-muted" style="width:${100 - standardPct}%"></div>
|
|
1387
|
+
</div>
|
|
1388
|
+
</div>
|
|
1389
|
+
<div class="dist-row">
|
|
1390
|
+
<div class="dist-header mono">
|
|
1391
|
+
<span>HARDWARE PASSKEY ATTESTATION (${biometricPct}%)</span>
|
|
1392
|
+
<span class="muted">${biometricCount} FIDO2 SIGNED / ${machineCount} MACHINE</span>
|
|
1393
|
+
</div>
|
|
1394
|
+
<div class="dist-track">
|
|
1395
|
+
<div class="dist-seg-primary" style="width:${biometricPct}%"></div>
|
|
1396
|
+
<div class="dist-seg-muted" style="width:${100 - biometricPct}%"></div>
|
|
1397
|
+
</div>
|
|
1398
|
+
</div>
|
|
1399
|
+
</div>
|
|
1400
|
+
|
|
1401
|
+
<div class="panel">
|
|
1402
|
+
<div class="panel-header" style="margin-bottom:14px;padding-bottom:10px">
|
|
1403
|
+
<div>
|
|
1404
|
+
<div class="section-index">TIMECHAIN TOPOLOGY // MERKLE BATCH ANCHORS</div>
|
|
1405
|
+
<h2>Bitcoin Block Settlement Sequence</h2>
|
|
1406
|
+
</div>
|
|
1407
|
+
<span class="mono muted" style="font-size:11px">SHA-256 MERKLE ROOT ➔ OP_RETURN</span>
|
|
1408
|
+
</div>
|
|
1409
|
+
<div class="topo-chain">
|
|
1410
|
+
${
|
|
1411
|
+
topologyNodesHtml ||
|
|
1412
|
+
`<div class="empty-cell" style="width:100%;padding:16px">No settled Bitcoin blocks in current filter window.</div>`
|
|
1413
|
+
}
|
|
1414
|
+
</div>
|
|
1415
|
+
</div>
|
|
1416
|
+
</div>
|
|
1417
|
+
|
|
1418
|
+
${localVerificationHtml}
|
|
1419
|
+
|
|
1420
|
+
<section class="panel">
|
|
1421
|
+
<div class="panel-header">
|
|
1422
|
+
<div>
|
|
1423
|
+
<div class="section-index">SECTION ${signerSectionNum} // CORPORATE HARDWARE PASSKEY REGISTRY</div>
|
|
1424
|
+
<h2>Authorized WebAuthn / FIDO2 Signer Authorities</h2>
|
|
1425
|
+
<p class="section-sub">Non-exportable Secure Enclave & YubiKey COSE public keys recorded across this corporate credential</p>
|
|
1426
|
+
</div>
|
|
1427
|
+
</div>
|
|
1428
|
+
<div class="table-wrap">
|
|
1429
|
+
<table>
|
|
1430
|
+
<thead>
|
|
1431
|
+
<tr>
|
|
1432
|
+
<th style="width:42px">#</th>
|
|
1433
|
+
<th>Hardware Signer Public Key (COSE Hex)</th>
|
|
1434
|
+
<th style="width:200px">Attested Volume</th>
|
|
1435
|
+
<th style="width:130px">Finality Ratio</th>
|
|
1436
|
+
<th style="width:190px">Latest Attestation (UTC)</th>
|
|
1437
|
+
</tr>
|
|
1438
|
+
</thead>
|
|
1439
|
+
<tbody>
|
|
1440
|
+
${signerRowsHtml}
|
|
1441
|
+
</tbody>
|
|
1442
|
+
</table>
|
|
1443
|
+
</div>
|
|
1444
|
+
</section>
|
|
1445
|
+
|
|
1446
|
+
<section class="panel">
|
|
1447
|
+
<div class="panel-header">
|
|
1448
|
+
<div>
|
|
1449
|
+
<div class="section-index">SECTION ${ledgerSectionNum} // CHRONOLOGICAL TIMECHAIN LEDGER</div>
|
|
1450
|
+
<h2>Cryptographic Anchor & VDF Sequence Log</h2>
|
|
1451
|
+
<p class="section-sub">Immutable record of SHA-256 payload digests, VDF thermodynamic proofs (T_index), and Bitcoin Merkle settlements</p>
|
|
1452
|
+
</div>
|
|
1453
|
+
</div>
|
|
1454
|
+
<div class="filter-bar no-print">
|
|
1455
|
+
<div class="tab-group">
|
|
1456
|
+
<button class="filter-tab active" onclick="setPill(this, 'all')">All (${entries.length})</button>
|
|
1457
|
+
<button class="filter-tab" onclick="setPill(this, 'status-settled')">Settled (${settledCount})</button>
|
|
1458
|
+
<button class="filter-tab" onclick="setPill(this, 'status-pending')">Pending (${pendingCount})</button>
|
|
1459
|
+
<button class="filter-tab" onclick="setPill(this, 'mode-biometric')">Passkey (${biometricCount})</button>
|
|
1460
|
+
<button class="filter-tab" onclick="setPill(this, 'mode-temporal')">Temporal VDF (${temporalCount})</button>
|
|
1461
|
+
<button class="filter-tab" onclick="setPill(this, 'mode-standard')">Standard (${standardCount})</button>
|
|
1462
|
+
</div>
|
|
1463
|
+
<input id="searchInput" class="search-input" type="text" placeholder="QUERY SHA-256 / T_INDEX / PASSKEY / TXID / BLOCK..." oninput="filterLedger()" />
|
|
1464
|
+
</div>
|
|
1465
|
+
<div class="table-wrap">
|
|
1466
|
+
<table>
|
|
1467
|
+
<thead>
|
|
1468
|
+
<tr>
|
|
1469
|
+
<th style="width:42px">#</th>
|
|
1470
|
+
<th>Timestamp (UTC)</th>
|
|
1471
|
+
<th>Finality</th>
|
|
1472
|
+
<th>Mode / Authority</th>
|
|
1473
|
+
<th>Client / File SHA-256 Digest</th>
|
|
1474
|
+
<th>Thermodynamic Proof (T_Index)</th>
|
|
1475
|
+
<th>Bitcoin Settlement</th>
|
|
1476
|
+
<th style="text-align:right">Proof</th>
|
|
1477
|
+
</tr>
|
|
1478
|
+
</thead>
|
|
1479
|
+
<tbody id="ledgerBody">
|
|
1480
|
+
${ledgerRowsHtml}
|
|
1481
|
+
</tbody>
|
|
1482
|
+
</table>
|
|
1483
|
+
</div>
|
|
1484
|
+
</section>
|
|
1485
|
+
</div>
|
|
1486
|
+
|
|
1487
|
+
<script>
|
|
1488
|
+
let currentFilter = 'all';
|
|
1489
|
+
function applyTheme(theme) {
|
|
1490
|
+
const root = document.documentElement;
|
|
1491
|
+
const btn = document.getElementById('themeToggleBtn');
|
|
1492
|
+
if (theme === 'light') {
|
|
1493
|
+
root.setAttribute('data-theme', 'light');
|
|
1494
|
+
if (btn) btn.textContent = '[ ☾ DARK MODE ]';
|
|
1495
|
+
} else {
|
|
1496
|
+
root.setAttribute('data-theme', 'dark');
|
|
1497
|
+
if (btn) btn.textContent = '[ ☀ LIGHT MODE ]';
|
|
1498
|
+
}
|
|
1499
|
+
try { localStorage.setItem('reltime_audit_theme', theme); } catch (e) {}
|
|
1500
|
+
}
|
|
1501
|
+
function toggleTheme() {
|
|
1502
|
+
const current = document.documentElement.getAttribute('data-theme') || 'dark';
|
|
1503
|
+
applyTheme(current === 'dark' ? 'light' : 'dark');
|
|
1504
|
+
}
|
|
1505
|
+
(function initTheme() {
|
|
1506
|
+
try {
|
|
1507
|
+
const saved = localStorage.getItem('reltime_audit_theme');
|
|
1508
|
+
if (saved === 'light' || saved === 'dark') applyTheme(saved);
|
|
1509
|
+
} catch (e) {}
|
|
1510
|
+
})();
|
|
1511
|
+
function setPill(btn, filter) {
|
|
1512
|
+
currentFilter = filter;
|
|
1513
|
+
document.querySelectorAll('.filter-tab').forEach(el => el.classList.remove('active'));
|
|
1514
|
+
btn.classList.add('active');
|
|
1515
|
+
filterLedger();
|
|
1516
|
+
}
|
|
1517
|
+
function filterLedger() {
|
|
1518
|
+
const q = (document.getElementById('searchInput').value || '').trim().toLowerCase();
|
|
1519
|
+
document.querySelectorAll('.ledger-row').forEach(row => {
|
|
1520
|
+
const matchesFilter = currentFilter === 'all' || row.classList.contains(currentFilter);
|
|
1521
|
+
const matchesQuery = !q || (row.getAttribute('data-search') || '').includes(q);
|
|
1522
|
+
row.style.display = (matchesFilter && matchesQuery) ? '' : 'none';
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
</script>
|
|
1526
|
+
</body>
|
|
1527
|
+
</html>`;
|
|
1528
|
+
}
|
|
1529
|
+
|
|
480
1530
|
const TOOLS = [
|
|
481
1531
|
{
|
|
482
1532
|
name: "reltime_anchor_file",
|
|
@@ -685,6 +1735,64 @@ const TOOLS = [
|
|
|
685
1735
|
required: ["action"],
|
|
686
1736
|
},
|
|
687
1737
|
},
|
|
1738
|
+
{
|
|
1739
|
+
name: "reltime_audit_trail",
|
|
1740
|
+
description:
|
|
1741
|
+
"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.",
|
|
1742
|
+
inputSchema: {
|
|
1743
|
+
type: "object",
|
|
1744
|
+
properties: {
|
|
1745
|
+
verify_local_path: {
|
|
1746
|
+
type: "string",
|
|
1747
|
+
description:
|
|
1748
|
+
"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.",
|
|
1749
|
+
},
|
|
1750
|
+
limit: {
|
|
1751
|
+
type: "number",
|
|
1752
|
+
description: "Maximum number of audit ledger entries to retrieve (default 100, max 500).",
|
|
1753
|
+
},
|
|
1754
|
+
status_filter: {
|
|
1755
|
+
type: "string",
|
|
1756
|
+
enum: ["all", "settled", "pending"],
|
|
1757
|
+
description: "Filter ledger entries by settlement status: 'all' (default), 'settled', or 'pending'.",
|
|
1758
|
+
},
|
|
1759
|
+
mode_filter: {
|
|
1760
|
+
type: "string",
|
|
1761
|
+
enum: ["all", "standard", "temporal", "biometric"],
|
|
1762
|
+
description:
|
|
1763
|
+
"Filter ledger entries by mode: 'all' (default), 'standard', 'temporal' (VDF T_index), or 'biometric' (Hardware Passkey attested).",
|
|
1764
|
+
},
|
|
1765
|
+
signer_filter: {
|
|
1766
|
+
type: "string",
|
|
1767
|
+
description:
|
|
1768
|
+
"Optional full or partial Hardware Passkey `public_key_hex` to filter anchors signed by a specific employee or corporate YubiKey.",
|
|
1769
|
+
},
|
|
1770
|
+
save_html_report: {
|
|
1771
|
+
type: "boolean",
|
|
1772
|
+
description:
|
|
1773
|
+
"If true (default), saves an interactive dark-mode HTML Superhighway Audit Trail dashboard (`reltime-audit-report-<date>.html`).",
|
|
1774
|
+
},
|
|
1775
|
+
open_in_browser: {
|
|
1776
|
+
type: "boolean",
|
|
1777
|
+
description:
|
|
1778
|
+
"If true (default), automatically opens the generated HTML Audit Trail dashboard in the user's browser.",
|
|
1779
|
+
},
|
|
1780
|
+
export_csv: {
|
|
1781
|
+
type: "boolean",
|
|
1782
|
+
description: "If true, also exports the audit ledger to a `.csv` spreadsheet file.",
|
|
1783
|
+
},
|
|
1784
|
+
output_dir: {
|
|
1785
|
+
type: "string",
|
|
1786
|
+
description:
|
|
1787
|
+
"Optional directory where the generated `.html` / `.csv` audit reports should be saved (defaults to `verify_local_path` folder or `~/.reltime/audits`).",
|
|
1788
|
+
},
|
|
1789
|
+
api_key: {
|
|
1790
|
+
type: "string",
|
|
1791
|
+
description: "Optional override for RELTIME_API_KEY.",
|
|
1792
|
+
},
|
|
1793
|
+
},
|
|
1794
|
+
},
|
|
1795
|
+
},
|
|
688
1796
|
];
|
|
689
1797
|
|
|
690
1798
|
async function executeTool(name, args = {}) {
|
|
@@ -984,6 +2092,405 @@ async function executeTool(name, args = {}) {
|
|
|
984
2092
|
return data;
|
|
985
2093
|
}
|
|
986
2094
|
|
|
2095
|
+
if (name === "reltime_audit_trail") {
|
|
2096
|
+
if (!key) {
|
|
2097
|
+
return buildPackSelectionPrompt(
|
|
2098
|
+
"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:"
|
|
2099
|
+
);
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
const acctRes = await fetch(`${API_BASE}/account`, {
|
|
2103
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
2104
|
+
});
|
|
2105
|
+
if (!acctRes.ok) {
|
|
2106
|
+
if (acctRes.status === 401 || acctRes.status === 403) {
|
|
2107
|
+
return buildPackSelectionPrompt(
|
|
2108
|
+
"The configured API key was not recognized. Ask the user which tier and payment method they would like to provision:"
|
|
2109
|
+
);
|
|
2110
|
+
}
|
|
2111
|
+
throw new Error(`Rel-Time Account error (${acctRes.status}): ${await acctRes.text()}`);
|
|
2112
|
+
}
|
|
2113
|
+
const acctData = await acctRes.json();
|
|
2114
|
+
|
|
2115
|
+
const maxEntries = Math.min(500, Math.max(1, Number(args.limit) || 100));
|
|
2116
|
+
const pageSize = Math.min(100, maxEntries);
|
|
2117
|
+
const pagesToFetch = Math.ceil(maxEntries / pageSize);
|
|
2118
|
+
let rawHistory = [];
|
|
2119
|
+
let totalInLedger = 0;
|
|
2120
|
+
|
|
2121
|
+
for (let p = 1; p <= pagesToFetch; p++) {
|
|
2122
|
+
const histRes = await fetch(`${API_BASE}/account/history?page=${p}&limit=${pageSize}`, {
|
|
2123
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
2124
|
+
});
|
|
2125
|
+
if (!histRes.ok) break;
|
|
2126
|
+
const histJson = await histRes.json();
|
|
2127
|
+
totalInLedger = Number(histJson.total || 0);
|
|
2128
|
+
const batch = Array.isArray(histJson.history) ? histJson.history : [];
|
|
2129
|
+
rawHistory.push(...batch);
|
|
2130
|
+
if (batch.length < pageSize || rawHistory.length >= maxEntries || rawHistory.length >= totalInLedger) {
|
|
2131
|
+
break;
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
rawHistory = rawHistory.slice(0, maxEntries);
|
|
2135
|
+
|
|
2136
|
+
const statusFilter = (args.status_filter || "all").toLowerCase();
|
|
2137
|
+
const modeFilter = (args.mode_filter || "all").toLowerCase();
|
|
2138
|
+
const signerFilter = (args.signer_filter || "").trim().toLowerCase();
|
|
2139
|
+
|
|
2140
|
+
const normalizedEntries = rawHistory.map((item) => {
|
|
2141
|
+
const clientHash = (item.client_hash || "").toLowerCase();
|
|
2142
|
+
const tIndex = (item.t_index || "").toLowerCase();
|
|
2143
|
+
const origHash = item.original_file_hash ? item.original_file_hash.toLowerCase() : null;
|
|
2144
|
+
const mode = (item.anchor_mode || "standard").toLowerCase();
|
|
2145
|
+
const lookupHash = mode === "temporal" ? tIndex || clientHash : clientHash;
|
|
2146
|
+
const tsSec = Number(item.timestamp || 0);
|
|
2147
|
+
const tsUtc = tsSec > 0 ? new Date(tsSec * 1000).toISOString() : null;
|
|
2148
|
+
return {
|
|
2149
|
+
timestamp: tsSec,
|
|
2150
|
+
timestamp_utc: tsUtc,
|
|
2151
|
+
status: item.status || "pending_finality",
|
|
2152
|
+
anchor_mode: mode,
|
|
2153
|
+
identity_mode: item.signer_public_key ? "biometric_passkey" : "anonymous",
|
|
2154
|
+
client_hash: clientHash,
|
|
2155
|
+
original_file_hash: origHash,
|
|
2156
|
+
t_index: tIndex,
|
|
2157
|
+
vdf_ticks: Number(item.vdf_ticks || 0),
|
|
2158
|
+
signer_public_key: item.signer_public_key || null,
|
|
2159
|
+
block_height: item.block_height || null,
|
|
2160
|
+
block_time: item.block_time || null,
|
|
2161
|
+
block_time_utc: item.block_time ? new Date(Number(item.block_time) * 1000).toISOString() : null,
|
|
2162
|
+
txid: item.txid || null,
|
|
2163
|
+
merkle_root: item.merkle_root || null,
|
|
2164
|
+
verify_url: `https://rel-time.com/verify?t=${lookupHash}`,
|
|
2165
|
+
sun_stamp_svg_url: `${API_BASE}/stamp/${lookupHash}`,
|
|
2166
|
+
};
|
|
2167
|
+
});
|
|
2168
|
+
|
|
2169
|
+
const filteredEntries = normalizedEntries.filter((e) => {
|
|
2170
|
+
if (statusFilter === "settled" && e.status !== "settled") return false;
|
|
2171
|
+
if (statusFilter === "pending" && e.status === "settled") return false;
|
|
2172
|
+
if (modeFilter === "standard" && e.anchor_mode !== "standard") return false;
|
|
2173
|
+
if (modeFilter === "temporal" && e.anchor_mode !== "temporal") return false;
|
|
2174
|
+
if (modeFilter === "biometric" && !e.signer_public_key) return false;
|
|
2175
|
+
if (signerFilter && !(e.signer_public_key || "").toLowerCase().includes(signerFilter)) return false;
|
|
2176
|
+
return true;
|
|
2177
|
+
});
|
|
2178
|
+
|
|
2179
|
+
// Aggregate KPI & Hardware Passkey Signer statistics
|
|
2180
|
+
let settledCount = 0;
|
|
2181
|
+
let pendingCount = 0;
|
|
2182
|
+
let standardCount = 0;
|
|
2183
|
+
let temporalCount = 0;
|
|
2184
|
+
let biometricCount = 0;
|
|
2185
|
+
const uniqueBlocks = new Set();
|
|
2186
|
+
const uniqueTxids = new Set();
|
|
2187
|
+
let latestBlock = null;
|
|
2188
|
+
let latestTxid = null;
|
|
2189
|
+
const signerMap = new Map();
|
|
2190
|
+
|
|
2191
|
+
for (const e of filteredEntries) {
|
|
2192
|
+
if (e.status === "settled") {
|
|
2193
|
+
settledCount++;
|
|
2194
|
+
if (e.block_height) {
|
|
2195
|
+
uniqueBlocks.add(e.block_height);
|
|
2196
|
+
if (!latestBlock || Number(e.block_height) > Number(latestBlock)) {
|
|
2197
|
+
latestBlock = e.block_height;
|
|
2198
|
+
latestTxid = e.txid || latestTxid;
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
if (e.txid) uniqueTxids.add(e.txid);
|
|
2202
|
+
} else {
|
|
2203
|
+
pendingCount++;
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
if (e.anchor_mode === "temporal") temporalCount++;
|
|
2207
|
+
else standardCount++;
|
|
2208
|
+
|
|
2209
|
+
if (e.signer_public_key) {
|
|
2210
|
+
biometricCount++;
|
|
2211
|
+
const pk = e.signer_public_key;
|
|
2212
|
+
const cur = signerMap.get(pk) || {
|
|
2213
|
+
signer_public_key: pk,
|
|
2214
|
+
short_key: `${pk.slice(0, 12)}...${pk.slice(-8)}`,
|
|
2215
|
+
anchor_count: 0,
|
|
2216
|
+
settled_count: 0,
|
|
2217
|
+
latest_timestamp_utc: e.timestamp_utc,
|
|
2218
|
+
};
|
|
2219
|
+
cur.anchor_count++;
|
|
2220
|
+
if (e.status === "settled") cur.settled_count++;
|
|
2221
|
+
if (e.timestamp_utc && (!cur.latest_timestamp_utc || e.timestamp_utc > cur.latest_timestamp_utc)) {
|
|
2222
|
+
cur.latest_timestamp_utc = e.timestamp_utc;
|
|
2223
|
+
}
|
|
2224
|
+
signerMap.set(pk, cur);
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
// Build fast lookup index of all known hashes in account history + known passkeys
|
|
2229
|
+
const ledgerByHash = new Map();
|
|
2230
|
+
const knownPasskeys = new Set();
|
|
2231
|
+
const localPasskey = loadSavedPasskey();
|
|
2232
|
+
if (localPasskey && localPasskey.public_key_hex) {
|
|
2233
|
+
knownPasskeys.add(localPasskey.public_key_hex);
|
|
2234
|
+
}
|
|
2235
|
+
for (const e of normalizedEntries) {
|
|
2236
|
+
if (e.client_hash) ledgerByHash.set(e.client_hash, e);
|
|
2237
|
+
if (e.t_index) ledgerByHash.set(e.t_index, e);
|
|
2238
|
+
if (e.original_file_hash) ledgerByHash.set(e.original_file_hash, e);
|
|
2239
|
+
if (e.signer_public_key) knownPasskeys.add(e.signer_public_key);
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
// Optional Zero-Knowledge Local File / Folder Integrity Verification
|
|
2243
|
+
let localPathVerification = null;
|
|
2244
|
+
if (args.verify_local_path) {
|
|
2245
|
+
const targetPath = resolveLocalFilePath(args.verify_local_path);
|
|
2246
|
+
if (!fs.existsSync(targetPath)) {
|
|
2247
|
+
throw new Error(`verify_local_path not found: ${targetPath}`);
|
|
2248
|
+
}
|
|
2249
|
+
const stat = fs.statSync(targetPath);
|
|
2250
|
+
let candidateFiles = [];
|
|
2251
|
+
if (stat.isFile()) {
|
|
2252
|
+
candidateFiles.push(targetPath);
|
|
2253
|
+
} else if (stat.isDirectory()) {
|
|
2254
|
+
const dirItems = fs.readdirSync(targetPath);
|
|
2255
|
+
for (const item of dirItems) {
|
|
2256
|
+
if (item.startsWith(".")) continue;
|
|
2257
|
+
if (
|
|
2258
|
+
item.startsWith("reltime-stamp-") ||
|
|
2259
|
+
item.startsWith("reltime-doomsday-proof-") ||
|
|
2260
|
+
item.startsWith("reltime-proof-") ||
|
|
2261
|
+
item.startsWith("reltime-audit-report-")
|
|
2262
|
+
) {
|
|
2263
|
+
continue;
|
|
2264
|
+
}
|
|
2265
|
+
const fullItemPath = path.join(targetPath, item);
|
|
2266
|
+
try {
|
|
2267
|
+
if (fs.statSync(fullItemPath).isFile()) {
|
|
2268
|
+
candidateFiles.push(fullItemPath);
|
|
2269
|
+
}
|
|
2270
|
+
} catch (e) {
|
|
2271
|
+
// Skip unreadable entry
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
const fileAuditResults = [];
|
|
2277
|
+
let vSettled = 0;
|
|
2278
|
+
let vPending = 0;
|
|
2279
|
+
let vUnanchored = 0;
|
|
2280
|
+
|
|
2281
|
+
for (const fPath of candidateFiles) {
|
|
2282
|
+
const buf = fs.readFileSync(fPath);
|
|
2283
|
+
const fName = path.basename(fPath);
|
|
2284
|
+
const rawSha256 = crypto.createHash("sha256").update(buf).digest("hex");
|
|
2285
|
+
const embeddedHash = extractStampedCopyEmbeddedHash(fPath, buf);
|
|
2286
|
+
const isStampedCopy = Boolean(embeddedHash) || fName.startsWith("reltime-stamped-");
|
|
2287
|
+
|
|
2288
|
+
let matchedEntry =
|
|
2289
|
+
ledgerByHash.get(rawSha256) || (embeddedHash ? ledgerByHash.get(embeddedHash) : null) || null;
|
|
2290
|
+
|
|
2291
|
+
// Also check biometric attested hashes SHA-256(rawSha256 + passkey_hex)
|
|
2292
|
+
if (!matchedEntry) {
|
|
2293
|
+
for (const pkHex of knownPasskeys) {
|
|
2294
|
+
const attestedCandidate = crypto
|
|
2295
|
+
.createHash("sha256")
|
|
2296
|
+
.update(`${rawSha256}${pkHex}`, "utf8")
|
|
2297
|
+
.digest("hex");
|
|
2298
|
+
if (ledgerByHash.has(attestedCandidate)) {
|
|
2299
|
+
matchedEntry = ledgerByHash.get(attestedCandidate);
|
|
2300
|
+
break;
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
// Fallback: query live GET /v1/proof/{hash} in case it was anchored outside the recent history window
|
|
2306
|
+
const probeHash = embeddedHash || rawSha256;
|
|
2307
|
+
if (!matchedEntry) {
|
|
2308
|
+
try {
|
|
2309
|
+
const proofRes = await fetch(`${API_BASE}/proof/${probeHash}`);
|
|
2310
|
+
if (proofRes.ok) {
|
|
2311
|
+
const proofData = await proofRes.json();
|
|
2312
|
+
const s = proofData.settlement || {};
|
|
2313
|
+
matchedEntry = {
|
|
2314
|
+
status: proofData.status || "settled",
|
|
2315
|
+
anchor_mode: proofData.anchor_mode || "standard",
|
|
2316
|
+
client_hash: proofData.client_hash || probeHash,
|
|
2317
|
+
t_index: proofData.t_index || probeHash,
|
|
2318
|
+
signer_public_key: proofData.signer_public_key || null,
|
|
2319
|
+
block_height: s.bitcoin_block_height || s.block_height || null,
|
|
2320
|
+
txid: s.bitcoin_txid || null,
|
|
2321
|
+
merkle_root: s.merkle_root || null,
|
|
2322
|
+
verify_url: `https://rel-time.com/verify?t=${probeHash}`,
|
|
2323
|
+
};
|
|
2324
|
+
}
|
|
2325
|
+
} catch (e) {
|
|
2326
|
+
// Ignore network error during fallback lookup
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
let integrityStatus = "UNANCHORED_OR_MODIFIED";
|
|
2331
|
+
if (matchedEntry) {
|
|
2332
|
+
if (matchedEntry.status === "settled") {
|
|
2333
|
+
integrityStatus = "VERIFIED_SETTLED";
|
|
2334
|
+
vSettled++;
|
|
2335
|
+
} else {
|
|
2336
|
+
integrityStatus = "VERIFIED_PENDING";
|
|
2337
|
+
vPending++;
|
|
2338
|
+
}
|
|
2339
|
+
} else {
|
|
2340
|
+
vUnanchored++;
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
const bytes = buf.length;
|
|
2344
|
+
const sizeHuman =
|
|
2345
|
+
bytes >= 1048576
|
|
2346
|
+
? `${(bytes / 1048576).toFixed(2)} MB`
|
|
2347
|
+
: bytes >= 1024
|
|
2348
|
+
? `${(bytes / 1024).toFixed(1)} KB`
|
|
2349
|
+
: `${bytes} B`;
|
|
2350
|
+
|
|
2351
|
+
fileAuditResults.push({
|
|
2352
|
+
file_name: fName,
|
|
2353
|
+
file_path: fPath,
|
|
2354
|
+
size_bytes: bytes,
|
|
2355
|
+
size_human: sizeHuman,
|
|
2356
|
+
local_sha256: embeddedHash || rawSha256,
|
|
2357
|
+
raw_file_sha256: rawSha256,
|
|
2358
|
+
embedded_stamp_hash: embeddedHash,
|
|
2359
|
+
is_stamped_copy: isStampedCopy,
|
|
2360
|
+
integrity_status: integrityStatus,
|
|
2361
|
+
anchor_mode: matchedEntry ? matchedEntry.anchor_mode : null,
|
|
2362
|
+
signer_public_key: matchedEntry ? matchedEntry.signer_public_key : null,
|
|
2363
|
+
t_index: matchedEntry ? matchedEntry.t_index : null,
|
|
2364
|
+
block_height: matchedEntry ? matchedEntry.block_height : null,
|
|
2365
|
+
txid: matchedEntry ? matchedEntry.txid : null,
|
|
2366
|
+
verify_url: matchedEntry ? matchedEntry.verify_url : null,
|
|
2367
|
+
});
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
localPathVerification = {
|
|
2371
|
+
target_path: targetPath,
|
|
2372
|
+
summary: {
|
|
2373
|
+
total_files_scanned: fileAuditResults.length,
|
|
2374
|
+
verified_settled_count: vSettled,
|
|
2375
|
+
verified_pending_count: vPending,
|
|
2376
|
+
unanchored_or_modified_count: vUnanchored,
|
|
2377
|
+
overall_integrity: vUnanchored === 0 ? "ALL_FILES_VERIFIED" : "ATTENTION_UNANCHORED_OR_MODIFIED_FILES",
|
|
2378
|
+
},
|
|
2379
|
+
files: fileAuditResults,
|
|
2380
|
+
};
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
const maskedKey = key.length > 16 ? `${key.slice(0, 14)}…${key.slice(-4)}` : "rel_key_***";
|
|
2384
|
+
const settlementRatePct =
|
|
2385
|
+
filteredEntries.length > 0 ? `${Math.round((settledCount / filteredEntries.length) * 100)}%` : "0%";
|
|
2386
|
+
|
|
2387
|
+
const auditPayload = {
|
|
2388
|
+
status: "audit_complete",
|
|
2389
|
+
generated_at_utc: new Date().toISOString(),
|
|
2390
|
+
account_overview: {
|
|
2391
|
+
tier: acctData.tier || "Active",
|
|
2392
|
+
hash_credits_remaining: Number(acctData.hash_credits || 0),
|
|
2393
|
+
lifetime_anchors: Number(acctData.total_usage || totalInLedger || 0),
|
|
2394
|
+
masked_api_key: maskedKey,
|
|
2395
|
+
},
|
|
2396
|
+
audit_summary: {
|
|
2397
|
+
total_ledger_entries: totalInLedger,
|
|
2398
|
+
matched_entries_count: filteredEntries.length,
|
|
2399
|
+
settled_on_bitcoin_count: settledCount,
|
|
2400
|
+
pending_finality_count: pendingCount,
|
|
2401
|
+
settlement_rate_pct: settlementRatePct,
|
|
2402
|
+
mode_breakdown: {
|
|
2403
|
+
standard_count: standardCount,
|
|
2404
|
+
temporal_vdf_count: temporalCount,
|
|
2405
|
+
biometric_passkey_count: biometricCount,
|
|
2406
|
+
},
|
|
2407
|
+
bitcoin_settlement: {
|
|
2408
|
+
unique_blocks_count: uniqueBlocks.size,
|
|
2409
|
+
unique_txids_count: uniqueTxids.size,
|
|
2410
|
+
latest_settled_block: latestBlock,
|
|
2411
|
+
latest_txid: latestTxid,
|
|
2412
|
+
},
|
|
2413
|
+
hardware_passkey_signers: Array.from(signerMap.values()),
|
|
2414
|
+
},
|
|
2415
|
+
local_path_verification: localPathVerification,
|
|
2416
|
+
audit_trail: filteredEntries,
|
|
2417
|
+
};
|
|
2418
|
+
|
|
2419
|
+
// Determine output directory for reports
|
|
2420
|
+
let reportDir = path.join(PASSKEY_CONFIG_DIR, "audits");
|
|
2421
|
+
if (args.output_dir) {
|
|
2422
|
+
reportDir = resolveLocalFilePath(args.output_dir);
|
|
2423
|
+
} else if (localPathVerification) {
|
|
2424
|
+
const tp = localPathVerification.target_path;
|
|
2425
|
+
reportDir = fs.statSync(tp).isDirectory() ? tp : path.dirname(tp);
|
|
2426
|
+
}
|
|
2427
|
+
if (!fs.existsSync(reportDir)) {
|
|
2428
|
+
fs.mkdirSync(reportDir, { recursive: true });
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
const dateStamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
2432
|
+
const savedReports = {};
|
|
2433
|
+
|
|
2434
|
+
if (args.save_html_report !== false) {
|
|
2435
|
+
const htmlPath = path.join(reportDir, `reltime-audit-report-${dateStamp}.html`);
|
|
2436
|
+
const htmlContent = generateAuditReportHtml(auditPayload);
|
|
2437
|
+
fs.writeFileSync(htmlPath, htmlContent, "utf8");
|
|
2438
|
+
savedReports.html_report_path = htmlPath;
|
|
2439
|
+
|
|
2440
|
+
if (args.open_in_browser !== false) {
|
|
2441
|
+
try {
|
|
2442
|
+
openSystemBrowser(htmlPath);
|
|
2443
|
+
savedReports.opened_in_browser = true;
|
|
2444
|
+
} catch (e) {
|
|
2445
|
+
savedReports.opened_in_browser = false;
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
|
|
2450
|
+
if (args.export_csv) {
|
|
2451
|
+
const csvPath = path.join(reportDir, `reltime-audit-trail-${dateStamp}.csv`);
|
|
2452
|
+
const csvHeader = [
|
|
2453
|
+
"timestamp_utc",
|
|
2454
|
+
"status",
|
|
2455
|
+
"anchor_mode",
|
|
2456
|
+
"identity_mode",
|
|
2457
|
+
"client_hash",
|
|
2458
|
+
"original_file_hash",
|
|
2459
|
+
"t_index",
|
|
2460
|
+
"vdf_ticks",
|
|
2461
|
+
"signer_public_key",
|
|
2462
|
+
"bitcoin_block_height",
|
|
2463
|
+
"bitcoin_txid",
|
|
2464
|
+
"merkle_root",
|
|
2465
|
+
"verify_url",
|
|
2466
|
+
].join(",");
|
|
2467
|
+
const csvRows = filteredEntries.map((e) =>
|
|
2468
|
+
[
|
|
2469
|
+
e.timestamp_utc || "",
|
|
2470
|
+
e.status || "",
|
|
2471
|
+
e.anchor_mode || "",
|
|
2472
|
+
e.identity_mode || "",
|
|
2473
|
+
e.client_hash || "",
|
|
2474
|
+
e.original_file_hash || "",
|
|
2475
|
+
e.t_index || "",
|
|
2476
|
+
e.vdf_ticks || 0,
|
|
2477
|
+
e.signer_public_key || "",
|
|
2478
|
+
e.block_height || "",
|
|
2479
|
+
e.txid || "",
|
|
2480
|
+
e.merkle_root || "",
|
|
2481
|
+
e.verify_url || "",
|
|
2482
|
+
]
|
|
2483
|
+
.map((v) => `"${String(v).replace(/"/g, '""')}"`)
|
|
2484
|
+
.join(",")
|
|
2485
|
+
);
|
|
2486
|
+
fs.writeFileSync(csvPath, [csvHeader, ...csvRows].join("\n"), "utf8");
|
|
2487
|
+
savedReports.csv_report_path = csvPath;
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2490
|
+
auditPayload.saved_reports = savedReports;
|
|
2491
|
+
return auditPayload;
|
|
2492
|
+
}
|
|
2493
|
+
|
|
987
2494
|
if (name === "reltime_provision_agent_key") {
|
|
988
2495
|
if (args.action === "create_invoice") {
|
|
989
2496
|
const validPacks = ["universal", "launch", "standard", "enterprise", "superhighway"];
|