Trace: move verification badge into header
This commit is contained in:
parent
8b1cb0724a
commit
bac86571a8
1 changed files with 52 additions and 17 deletions
|
|
@ -81,7 +81,7 @@ markdown.renderer.rules.fence = (tokens, idx, options, env, self) => {
|
|||
return self.renderToken(tokens, idx, options);
|
||||
};
|
||||
|
||||
function renderMarkdownPage({ title, html, topLinksHtml }) {
|
||||
function renderMarkdownPage({ title, headerHtml, html, topLinksHtml }) {
|
||||
return [
|
||||
"<!doctype html>",
|
||||
"<html><head>",
|
||||
|
|
@ -99,6 +99,7 @@ function renderMarkdownPage({ title, html, topLinksHtml }) {
|
|||
"blockquote{border-left:4px solid #ddd;margin:16px 0;padding:2px 14px;color:#444}",
|
||||
"table{border-collapse:collapse;width:100%;margin:14px 0}",
|
||||
"th,td{border:1px solid #ddd;padding:8px;vertical-align:top}",
|
||||
".page-header{margin:0 0 18px 0}",
|
||||
".topbar{display:flex;gap:14px;flex-wrap:wrap;margin:0 0 18px 0;font-size:14px}",
|
||||
".topbar code{padding:2px 6px}",
|
||||
".badge{display:inline-flex;align-items:center;gap:8px;padding:6px 10px;border-radius:999px;border:1px solid transparent;font-weight:700;letter-spacing:.02em}",
|
||||
|
|
@ -106,8 +107,16 @@ function renderMarkdownPage({ title, html, topLinksHtml }) {
|
|||
".badge.ok{background:#e6f4ea;border-color:#b7dfc1;color:#137333}",
|
||||
".badge.warn{background:#fff7e0;border-color:#ffe0a3;color:#8b5a00}",
|
||||
".badge.fail{background:#fce8e6;border-color:#f6b8b6;color:#a50e0e}",
|
||||
".trace-header{display:flex;flex-direction:column;gap:10px;margin:0 0 18px 0;padding:14px 14px;border:1px solid #eee;border-radius:14px;background:#fff}",
|
||||
".trace-header-title{display:flex;align-items:center;gap:12px;flex-wrap:wrap}",
|
||||
".trace-header-title h1{margin:0;font-size:24px}",
|
||||
".trace-meta{display:flex;flex-wrap:wrap;gap:10px;font-size:14px;color:#333}",
|
||||
".trace-meta code{padding:2px 6px}",
|
||||
".trace-checks{margin:0;padding-left:18px;color:#333;font-size:14px}",
|
||||
".trace-checks li{margin:2px 0}",
|
||||
"</style>",
|
||||
"</head><body>",
|
||||
headerHtml ? `<header class="page-header">${headerHtml}</header>` : "",
|
||||
topLinksHtml ? `<div class="topbar">${topLinksHtml}</div>` : "",
|
||||
`<main class="markdown-body">${html}</main>`,
|
||||
"<script type='module'>",
|
||||
|
|
@ -131,6 +140,45 @@ function renderVerificationBadgeHtml(verification) {
|
|||
return `<span class="badge ${cls}">${safeLabel}${safeDetail ? ` <small>${safeDetail}</small>` : ""}</span>`;
|
||||
}
|
||||
|
||||
function renderTraceHeaderHtml({ verification, job }) {
|
||||
const badge = renderVerificationBadgeHtml(verification);
|
||||
const createdAt = job?.createdAt ? String(job.createdAt) : "";
|
||||
const traceId = String(job?.id || "");
|
||||
const style = String(job?.style || "");
|
||||
|
||||
const checks = verification?.checks || {};
|
||||
const outputOk = checks.outputOk === true;
|
||||
const sourceOk = checks.sourceOk;
|
||||
const warningsPresent = checks.warningsPresent === true;
|
||||
|
||||
const outputLabel = outputOk ? "PASS" : "FAIL";
|
||||
const sourceLabel = sourceOk === true ? "PASS" : sourceOk === false ? "FAIL" : "UNKNOWN";
|
||||
|
||||
const safeCreatedAt = createdAt ? escapeHtml(createdAt) : "";
|
||||
const safeTraceId = traceId ? escapeHtml(traceId) : "";
|
||||
const safeStyle = style ? escapeHtml(style) : "";
|
||||
|
||||
const metaParts = [
|
||||
safeCreatedAt ? `<span>Generated (UTC): <code>${safeCreatedAt}</code></span>` : "",
|
||||
safeTraceId ? `<span>Trace ID: <code>${safeTraceId}</code></span>` : "",
|
||||
safeStyle ? `<span>Style: <code>${safeStyle}</code></span>` : "",
|
||||
].filter(Boolean);
|
||||
|
||||
return [
|
||||
`<div class="trace-header">`,
|
||||
` <div class="trace-header-title">${badge}<h1>IF.TTT trace</h1></div>`,
|
||||
metaParts.length ? ` <div class="trace-meta">${metaParts.join(" · ")}</div>` : "",
|
||||
` <ul class="trace-checks">`,
|
||||
` <li>Output hash check: <strong>${escapeHtml(outputLabel)}</strong></li>`,
|
||||
` <li>Source hash check: <strong>${escapeHtml(sourceLabel)}</strong></li>`,
|
||||
` <li>Quality warnings: <strong>${warningsPresent ? "present" : "none recorded"}</strong></li>`,
|
||||
` </ul>`,
|
||||
`</div>`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("");
|
||||
}
|
||||
|
||||
async function computeVerificationStatus({ job, projectRoot, outputsDir, uploadsDir }) {
|
||||
const expectedOutput = String(job?.outputSha256 || "").trim();
|
||||
const expectedSource = String(job?.sourceSha256 || "").trim();
|
||||
|
|
@ -207,10 +255,6 @@ function renderTraceMarkdown({ shareId, job, publicBaseUrl, staticPublicBaseUrl
|
|||
const bases = staticMirrorBaseUrls(primaryBase, publicBaseUrl);
|
||||
const mirrorBase = bases.find((b) => b !== primaryBase) || "";
|
||||
|
||||
const verificationStatus = String(job?._verification?.status || "").toUpperCase() || "UNKNOWN";
|
||||
const verificationDetail = String(job?._verification?.detail || "").trim();
|
||||
const verificationChecks = job?._verification?.checks || {};
|
||||
|
||||
const createdAt = job?.createdAt ? String(job.createdAt) : "";
|
||||
const status = job?.status ? String(job.status) : "";
|
||||
const warningsPresent = Boolean(job?.warnings && String(job.warnings).trim());
|
||||
|
|
@ -235,15 +279,7 @@ function renderTraceMarkdown({ shareId, job, publicBaseUrl, staticPublicBaseUrl
|
|||
const lastResortDownloadUrl = lastResortBase ? `${lastResortBase}/r/${encodeURIComponent(shareId)}/download` : "";
|
||||
|
||||
const lines = [
|
||||
"# IF.TTT trace (public evidence view)",
|
||||
"",
|
||||
"## Trace verification status",
|
||||
"",
|
||||
`**${verificationStatus}**${verificationDetail ? ` — ${verificationDetail}` : ""}`,
|
||||
"",
|
||||
"- Output hash check: " + (verificationChecks.outputOk ? "**PASS**" : "**FAIL**"),
|
||||
"- Source hash check: " + (verificationChecks.sourceOk === true ? "**PASS**" : verificationChecks.sourceOk === false ? "**FAIL**" : "**UNKNOWN**"),
|
||||
"- Quality warnings: " + (warningsPresent ? "**present**" : "none recorded"),
|
||||
"## IF.TTT trace (public evidence view)",
|
||||
"",
|
||||
"IF.TTT (Traceable, Transparent, Trustworthy) is InfraFabric’s chain-of-custody protocol: it binds the **source fingerprint** to the **generated output fingerprint**, so a skeptical reader can verify what was produced and from which input, without needing access to internal systems.",
|
||||
"This page is intentionally scoped to **one dossier only** (no index, no directory listing).",
|
||||
|
|
@ -1018,9 +1054,8 @@ function main() {
|
|||
const md = renderTraceMarkdown({ shareId, job: jobForRender, publicBaseUrl, staticPublicBaseUrl });
|
||||
const html = markdown.render(md);
|
||||
|
||||
const badge = renderVerificationBadgeHtml(verification);
|
||||
const headerHtml = renderTraceHeaderHtml({ verification, job: jobForRender });
|
||||
const topLinks = [
|
||||
badge,
|
||||
`<a href="/r/${encodeURIComponent(shareId)}">Back to dossier</a>`,
|
||||
`<a href="/r/${encodeURIComponent(shareId)}/download">Download Markdown</a>`,
|
||||
job.sourcePath ? `<a href="/r/${encodeURIComponent(shareId)}/source">Download source</a>` : "",
|
||||
|
|
@ -1032,7 +1067,7 @@ function main() {
|
|||
res
|
||||
.status(200)
|
||||
.type("text/html; charset=utf-8")
|
||||
.send(renderMarkdownPage({ title: "IF.TTT trace", html, topLinksHtml: topLinks }));
|
||||
.send(renderMarkdownPage({ title: "IF.TTT trace", headerHtml, html, topLinksHtml: topLinks }));
|
||||
});
|
||||
|
||||
app.get("/r/:shareId/source", (req, res) => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue