Trace: add verification badge + context
This commit is contained in:
parent
2ca98cb1b9
commit
dede91c133
1 changed files with 892 additions and 5 deletions
|
|
@ -4,6 +4,7 @@ import path from "node:path";
|
|||
import url from "node:url";
|
||||
import { spawn } from "node:child_process";
|
||||
|
||||
import MarkdownIt from "markdown-it";
|
||||
import express from "express";
|
||||
import multer from "multer";
|
||||
|
||||
|
|
@ -17,8 +18,37 @@ const indexHtmlPath = path.join(distDir, "index.html");
|
|||
const privateUploadToken = process.env.PRIVATE_UPLOAD_TOKEN || "";
|
||||
const privateUploadMaxBytes = Number(process.env.PRIVATE_UPLOAD_MAX_BYTES || 25 * 1024 * 1024);
|
||||
const privateUploadStyle = process.env.PRIVATE_UPLOAD_STYLE || "if.dave.v1.2";
|
||||
const privateUploadActionPack = String(process.env.PRIVATE_UPLOAD_ACTION_PACK || "")
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
const revoiceRepoRoot = path.resolve(projectRoot, "..", "..");
|
||||
|
||||
function normalizeBaseUrl(value) {
|
||||
return String(value || "")
|
||||
.trim()
|
||||
.replace(/\/+$/g, "");
|
||||
}
|
||||
|
||||
function uniqBaseUrls(values) {
|
||||
const out = [];
|
||||
const seen = new Set();
|
||||
for (const v of values || []) {
|
||||
const base = normalizeBaseUrl(v);
|
||||
if (!base) continue;
|
||||
if (seen.has(base)) continue;
|
||||
seen.add(base);
|
||||
out.push(base);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function staticMirrorBaseUrls(primaryBaseUrl, publicBaseUrl) {
|
||||
const env = String(process.env.STATIC_MIRROR_PUBLIC_BASE_URLS || "").trim();
|
||||
const defaults = ["https://git.infrafabric.io"];
|
||||
const configured = env ? env.split(",") : defaults;
|
||||
return uniqBaseUrls([primaryBaseUrl, ...configured, publicBaseUrl]);
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value || "")
|
||||
.replaceAll("&", "&")
|
||||
|
|
@ -32,6 +62,613 @@ function ensureDir(dirPath) {
|
|||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
}
|
||||
|
||||
const markdown = new MarkdownIt({
|
||||
html: false,
|
||||
linkify: true,
|
||||
breaks: false,
|
||||
});
|
||||
const defaultFence = markdown.renderer.rules.fence;
|
||||
markdown.renderer.rules.fence = (tokens, idx, options, env, self) => {
|
||||
const token = tokens[idx];
|
||||
const info = String(token.info || "")
|
||||
.trim()
|
||||
.split(/\s+/g)[0]
|
||||
.toLowerCase();
|
||||
if (info === "mermaid") {
|
||||
return `<pre class="mermaid">${escapeHtml(token.content)}</pre>\n`;
|
||||
}
|
||||
if (typeof defaultFence === "function") return defaultFence(tokens, idx, options, env, self);
|
||||
return self.renderToken(tokens, idx, options);
|
||||
};
|
||||
|
||||
function renderMarkdownPage({ title, html, topLinksHtml }) {
|
||||
return [
|
||||
"<!doctype html>",
|
||||
"<html><head>",
|
||||
"<meta charset='utf-8'/>",
|
||||
"<meta name='viewport' content='width=device-width, initial-scale=1'/>",
|
||||
"<meta name='robots' content='noindex,nofollow'/>",
|
||||
`<title>${escapeHtml(title || "Shadow Dossier")}</title>`,
|
||||
"<style>",
|
||||
"body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;max-width:980px;margin:40px auto;padding:0 18px;line-height:1.55;color:#111}",
|
||||
"a{color:#0b5fff;text-decoration:none}a:hover{text-decoration:underline}",
|
||||
"h1,h2,h3{line-height:1.2;margin-top:1.6em}h1{margin-top:0}",
|
||||
"pre,code{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace}",
|
||||
"pre{background:#f7f7f7;border:1px solid #eee;border-radius:10px;padding:12px;overflow:auto}",
|
||||
"code{background:#f7f7f7;border-radius:6px;padding:2px 5px}",
|
||||
"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}",
|
||||
".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}",
|
||||
".badge small{font-weight:600;opacity:.85}",
|
||||
".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}",
|
||||
"</style>",
|
||||
"</head><body>",
|
||||
topLinksHtml ? `<div class="topbar">${topLinksHtml}</div>` : "",
|
||||
`<main class="markdown-body">${html}</main>`,
|
||||
"<script type='module'>",
|
||||
'import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";',
|
||||
"mermaid.initialize({ startOnLoad: true, securityLevel: 'strict' });",
|
||||
"</script>",
|
||||
"</body></html>",
|
||||
].join("");
|
||||
}
|
||||
|
||||
function renderVerificationBadgeHtml(verification) {
|
||||
const status = String(verification?.status || "").toLowerCase();
|
||||
const label = String(verification?.label || "").trim();
|
||||
const detail = String(verification?.detail || "").trim();
|
||||
|
||||
if (!status) return "";
|
||||
|
||||
const cls = status === "verified" ? "ok" : status === "warning" ? "warn" : "fail";
|
||||
const safeLabel = escapeHtml(label || status.toUpperCase());
|
||||
const safeDetail = escapeHtml(detail);
|
||||
return `<span class="badge ${cls}">${safeLabel}${safeDetail ? ` <small>${safeDetail}</small>` : ""}</span>`;
|
||||
}
|
||||
|
||||
async function computeVerificationStatus({ job, projectRoot, outputsDir, uploadsDir }) {
|
||||
const expectedOutput = String(job?.outputSha256 || "").trim();
|
||||
const expectedSource = String(job?.sourceSha256 || "").trim();
|
||||
const warningsPresent = Boolean(job?.warnings && String(job.warnings).trim());
|
||||
const status = String(job?.status || "").trim();
|
||||
|
||||
const outputPathRel = String(job?.outputPath || "").trim();
|
||||
const outputAbs = outputPathRel ? path.resolve(projectRoot, outputPathRel) : "";
|
||||
|
||||
let outputOk = false;
|
||||
if (expectedOutput && outputAbs && outputAbs.startsWith(outputsDir + path.sep) && fs.existsSync(outputAbs)) {
|
||||
try {
|
||||
const actual = await sha256File(outputAbs);
|
||||
outputOk = actual === expectedOutput;
|
||||
} catch {
|
||||
outputOk = false;
|
||||
}
|
||||
}
|
||||
|
||||
const sourcePathRel = String(job?.sourcePath || "").trim();
|
||||
const sourceAbs = sourcePathRel ? path.resolve(projectRoot, sourcePathRel) : "";
|
||||
let sourceOk = false;
|
||||
let sourceCheckKnown = false;
|
||||
if (expectedSource && sourceAbs && sourceAbs.startsWith(uploadsDir + path.sep) && fs.existsSync(sourceAbs)) {
|
||||
sourceCheckKnown = true;
|
||||
try {
|
||||
const actual = await sha256File(sourceAbs);
|
||||
sourceOk = actual === expectedSource;
|
||||
} catch {
|
||||
sourceOk = false;
|
||||
}
|
||||
} else if (expectedSource) {
|
||||
sourceCheckKnown = false;
|
||||
}
|
||||
|
||||
if (!outputOk) {
|
||||
return {
|
||||
status: "fail",
|
||||
label: "FAIL",
|
||||
detail: "hash mismatch",
|
||||
checks: { outputOk, sourceOk: sourceCheckKnown ? sourceOk : null, warningsPresent, jobStatus: status },
|
||||
};
|
||||
}
|
||||
|
||||
if (sourceCheckKnown && !sourceOk) {
|
||||
return {
|
||||
status: "fail",
|
||||
label: "FAIL",
|
||||
detail: "source mismatch",
|
||||
checks: { outputOk, sourceOk, warningsPresent, jobStatus: status },
|
||||
};
|
||||
}
|
||||
|
||||
if (warningsPresent || status === "done_with_warnings" || !sourceCheckKnown) {
|
||||
const detail = warningsPresent ? "warnings" : !sourceCheckKnown ? "source not locally verifiable" : "check details";
|
||||
return {
|
||||
status: "warning",
|
||||
label: "WARNING",
|
||||
detail,
|
||||
checks: { outputOk, sourceOk: sourceCheckKnown ? sourceOk : null, warningsPresent, jobStatus: status },
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: "verified",
|
||||
label: "VERIFIED",
|
||||
detail: "all checks passed",
|
||||
checks: { outputOk, sourceOk, warningsPresent, jobStatus: status },
|
||||
};
|
||||
}
|
||||
|
||||
function renderTraceMarkdown({ shareId, job, publicBaseUrl, staticPublicBaseUrl }) {
|
||||
const primaryBase = normalizeBaseUrl(staticPublicBaseUrl || publicBaseUrl);
|
||||
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());
|
||||
|
||||
const dossierUrl = `${primaryBase}/static/dossier/${encodeURIComponent(shareId)}`;
|
||||
const traceUrl = `${primaryBase}/static/trace/${encodeURIComponent(shareId)}`;
|
||||
const downloadUrl = `${primaryBase}/static/dossier/${encodeURIComponent(shareId)}/download`;
|
||||
const packUrl = `${primaryBase}/static/pack/${encodeURIComponent(shareId)}.md`;
|
||||
const sourceUrl = job?.sourceSha256
|
||||
? `${primaryBase}/static/source/${job.sourceSha256}${path.extname(job.sourcePath || "").toLowerCase()}`
|
||||
: "";
|
||||
|
||||
const directBase = primaryBase; // Caddy exposes /r/* on infrafabric.io and git.infrafabric.io too.
|
||||
const directDossierUrl = `${directBase}/r/${encodeURIComponent(shareId)}`;
|
||||
const directTraceUrl = `${directBase}/r/${encodeURIComponent(shareId)}/trace`;
|
||||
const directDownloadUrl = `${directBase}/r/${encodeURIComponent(shareId)}/download`;
|
||||
const directPackUrl = `${directBase}/r/${encodeURIComponent(shareId)}/pack.md`;
|
||||
|
||||
const lastResortBase = normalizeBaseUrl(publicBaseUrl);
|
||||
const lastResortDossierUrl = lastResortBase ? `${lastResortBase}/r/${encodeURIComponent(shareId)}` : "";
|
||||
const lastResortTraceUrl = lastResortBase ? `${lastResortBase}/r/${encodeURIComponent(shareId)}/trace` : "";
|
||||
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 (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).",
|
||||
"",
|
||||
"## What this trace proves",
|
||||
"",
|
||||
"- You can independently verify the downloaded dossier Markdown by hashing it and comparing to `Output sha256` below.",
|
||||
"- You can independently verify the hosted source file (if present) by hashing it and comparing to `Source sha256` below.",
|
||||
"- This page binds those two fingerprints together as a single public evidence record.",
|
||||
"",
|
||||
"## What this trace does not prove",
|
||||
"",
|
||||
"- It does not validate the truth of the source’s claims.",
|
||||
"- It does not attest to correctness of any commentary or recommendations in the dossier.",
|
||||
"- It does not expose internal prompts, intermediate steps, or private systems.",
|
||||
"",
|
||||
"## Links",
|
||||
"",
|
||||
`- Dossier (rendered): ${dossierUrl}`,
|
||||
`- Dossier (download Markdown): ${downloadUrl}`,
|
||||
`- Single-file pack (review + dossier + trace): ${packUrl}`,
|
||||
sourceUrl ? `- Source (PDF): ${sourceUrl}` : null,
|
||||
`- This trace page: ${traceUrl}`,
|
||||
mirrorBase ? "" : null,
|
||||
mirrorBase ? "## Mirror host (same paths)" : null,
|
||||
mirrorBase ? "" : null,
|
||||
mirrorBase ? `- Dossier: ${mirrorBase}/static/dossier/${encodeURIComponent(shareId)}` : null,
|
||||
mirrorBase ? `- Pack: ${mirrorBase}/static/pack/${encodeURIComponent(shareId)}.md` : null,
|
||||
mirrorBase ? `- Trace: ${mirrorBase}/static/trace/${encodeURIComponent(shareId)}` : null,
|
||||
mirrorBase && sourceUrl ? `- Source: ${mirrorBase}/static/source/${job.sourceSha256}${path.extname(job.sourcePath || "").toLowerCase()}` : null,
|
||||
"",
|
||||
"## Fallback links (direct)",
|
||||
"",
|
||||
`- Dossier: ${directDossierUrl}`,
|
||||
`- Download: ${directDownloadUrl}`,
|
||||
`- Pack: ${directPackUrl}`,
|
||||
`- Trace: ${directTraceUrl}`,
|
||||
lastResortBase && lastResortBase !== directBase ? "" : null,
|
||||
lastResortBase && lastResortBase !== directBase ? "## Last resort (alternate host)" : null,
|
||||
lastResortBase && lastResortBase !== directBase ? "" : null,
|
||||
lastResortBase && lastResortBase !== directBase ? `- Dossier: ${lastResortDossierUrl}` : null,
|
||||
lastResortBase && lastResortBase !== directBase ? `- Download: ${lastResortDownloadUrl}` : null,
|
||||
lastResortBase && lastResortBase !== directBase ? `- Trace: ${lastResortTraceUrl}` : null,
|
||||
"",
|
||||
"## Generation context",
|
||||
"",
|
||||
createdAt ? `- Generated at (UTC): \`${createdAt}\`` : null,
|
||||
status ? `- Status: \`${status}\`` : null,
|
||||
`- Quality warnings: ${warningsPresent ? "present (see pack trace JSON)" : "none recorded"}`,
|
||||
"",
|
||||
"## Verifiability",
|
||||
"",
|
||||
`- Trace ID: \`${job.id || ""}\``,
|
||||
`- Dossier: \`${job.originalFilename || ""}\``,
|
||||
`- Output sha256: \`${job.outputSha256 || ""}\``,
|
||||
`- Source sha256: \`${job.sourceSha256 || ""}\``,
|
||||
`- Style: \`${job.style || ""}\``,
|
||||
`- Source bytes: \`${String(job.sourceBytes ?? "")}\``,
|
||||
"",
|
||||
"## How to verify (locally)",
|
||||
"",
|
||||
"1) Download the dossier Markdown.",
|
||||
"2) Compute its hash and compare to `Output sha256` above:",
|
||||
"",
|
||||
"```bash",
|
||||
"sha256sum <downloaded-file>.md",
|
||||
"```",
|
||||
"",
|
||||
sourceUrl ? "3) Optionally, download the source and verify its hash matches `Source sha256` above:" : null,
|
||||
sourceUrl ? "" : null,
|
||||
sourceUrl
|
||||
? "```bash\nsha256sum <downloaded-source>\n```"
|
||||
: null,
|
||||
];
|
||||
|
||||
return lines.filter(Boolean).join("\n");
|
||||
}
|
||||
|
||||
function renderReviewPackMarkdown({ shareId, job, publicBaseUrl, externalReviewUrl, staticSourceUrl, staticPublicBaseUrl }) {
|
||||
const primaryBase = normalizeBaseUrl(staticPublicBaseUrl || publicBaseUrl);
|
||||
const bases = staticMirrorBaseUrls(primaryBase, publicBaseUrl);
|
||||
const mirrorBase = bases.find((b) => b !== primaryBase) || "";
|
||||
|
||||
const dossierUrl = `${primaryBase}/static/dossier/${encodeURIComponent(shareId)}`;
|
||||
const traceUrl = `${primaryBase}/static/trace/${encodeURIComponent(shareId)}`;
|
||||
const downloadUrl = `${primaryBase}/static/dossier/${encodeURIComponent(shareId)}/download`;
|
||||
const packUrl = `${primaryBase}/static/pack/${encodeURIComponent(shareId)}.md`;
|
||||
|
||||
const directBase = primaryBase; // Caddy exposes /r/* on infrafabric.io and git.infrafabric.io too.
|
||||
const directDossierUrl = `${directBase}/r/${encodeURIComponent(shareId)}`;
|
||||
const directTraceUrl = `${directBase}/r/${encodeURIComponent(shareId)}/trace`;
|
||||
const directDownloadUrl = `${directBase}/r/${encodeURIComponent(shareId)}/download`;
|
||||
const directPackUrl = `${directBase}/r/${encodeURIComponent(shareId)}/pack.md`;
|
||||
|
||||
const lastResortBase = normalizeBaseUrl(publicBaseUrl);
|
||||
const lastResortDossierUrl = lastResortBase ? `${lastResortBase}/r/${encodeURIComponent(shareId)}` : "";
|
||||
const lastResortTraceUrl = lastResortBase ? `${lastResortBase}/r/${encodeURIComponent(shareId)}/trace` : "";
|
||||
const lastResortDownloadUrl = lastResortBase ? `${lastResortBase}/r/${encodeURIComponent(shareId)}/download` : "";
|
||||
const lastResortPackUrl = lastResortBase ? `${lastResortBase}/r/${encodeURIComponent(shareId)}/pack.md` : "";
|
||||
|
||||
const lines = [
|
||||
"# InfraFabric External Review Pack — Shadow Dossier + IF.TTT trace",
|
||||
"",
|
||||
"Please review the dossier and the IF.TTT trace page. Provide constructive criticism and patch-style suggestions.",
|
||||
"",
|
||||
"## Assets",
|
||||
"",
|
||||
`- Dossier (rendered): ${dossierUrl}`,
|
||||
`- Dossier (download Markdown): ${downloadUrl}`,
|
||||
`- Single-file pack (review + dossier + trace): ${packUrl}`,
|
||||
staticSourceUrl ? `- Source (download): ${staticSourceUrl}` : null,
|
||||
`- IF.TTT trace page: ${traceUrl}`,
|
||||
externalReviewUrl ? `- Feedback intake (login): ${externalReviewUrl}` : null,
|
||||
mirrorBase ? "" : null,
|
||||
mirrorBase ? "## Alternate host mirror (same paths)" : null,
|
||||
mirrorBase ? "" : null,
|
||||
mirrorBase ? `- Dossier (rendered): ${mirrorBase}/static/dossier/${encodeURIComponent(shareId)}` : null,
|
||||
mirrorBase ? `- Dossier (download Markdown): ${mirrorBase}/static/dossier/${encodeURIComponent(shareId)}/download` : null,
|
||||
mirrorBase ? `- Single-file pack: ${mirrorBase}/static/pack/${encodeURIComponent(shareId)}.md` : null,
|
||||
mirrorBase ? `- IF.TTT trace page: ${mirrorBase}/static/trace/${encodeURIComponent(shareId)}` : null,
|
||||
"",
|
||||
"## Fallback links (direct)",
|
||||
"",
|
||||
"- These may be blocked by some scanners/LLM sandboxes; use only if the `infrafabric.io/static/*` links fail.",
|
||||
"",
|
||||
`- Dossier (rendered): ${directDossierUrl}`,
|
||||
`- Dossier (download Markdown): ${directDownloadUrl}`,
|
||||
`- Single-file pack (review + dossier + trace): ${directPackUrl}`,
|
||||
`- IF.TTT trace page: ${directTraceUrl}`,
|
||||
lastResortBase && lastResortBase !== directBase ? "" : null,
|
||||
lastResortBase && lastResortBase !== directBase ? "## Last resort (alternate host)" : null,
|
||||
lastResortBase && lastResortBase !== directBase ? "" : null,
|
||||
lastResortBase && lastResortBase !== directBase ? `- Dossier (rendered): ${lastResortDossierUrl}` : null,
|
||||
lastResortBase && lastResortBase !== directBase ? `- Dossier (download): ${lastResortDownloadUrl}` : null,
|
||||
lastResortBase && lastResortBase !== directBase ? `- Pack: ${lastResortPackUrl}` : null,
|
||||
lastResortBase && lastResortBase !== directBase ? `- Trace: ${lastResortTraceUrl}` : null,
|
||||
"",
|
||||
"## Response metadata (required)",
|
||||
"",
|
||||
"- `llm_name`: provider + model name/version (exact string)",
|
||||
"- `probable_model`: if `llm_name` is ambiguous, your best guess",
|
||||
"- `cutoff_date`: training data cutoff (YYYY-MM, or “unknown”)",
|
||||
"- `response_date_utc`: current date/time in UTC",
|
||||
"- `web_access_used`: `yes`/`no` (if yes, list URLs relied on)",
|
||||
"",
|
||||
"## Executive read (fast)",
|
||||
"",
|
||||
"- 5–10 bullets: what works / what doesn’t",
|
||||
"- 3 bullets: who this will land with vs who will reject it",
|
||||
"",
|
||||
"## Scorecard (0–5 each)",
|
||||
"",
|
||||
"- Mirror integrity (section order, headings, high-signal numbers/dates, lists/tables)",
|
||||
"- Layout fidelity (does it “feel” like the source?)",
|
||||
"- Humor discipline (sharp without being template-y)",
|
||||
"- Mermaid value (valid syntax; clarifies, not decorates)",
|
||||
"- IF.TTT demo value (understandable + credible + worth clicking)",
|
||||
"- Call-to-action stealth (curious about governance stack without feeling spammed)",
|
||||
"",
|
||||
"## Patch suggestions (actionable)",
|
||||
"",
|
||||
"Provide 5–15 patch suggestions. Use this format per item:",
|
||||
"",
|
||||
"- `target`: dossier / trace page / “generator rules” / site UX",
|
||||
"- `problem`: 1 sentence",
|
||||
"- `change`: proposed text or behavior (copy/pasteable if possible)",
|
||||
"- `why`: 1 sentence",
|
||||
"- `priority`: P0 / P1 / P2",
|
||||
"- `confidence`: high / medium / low",
|
||||
"",
|
||||
"## Plan iteration (debug our plan)",
|
||||
"",
|
||||
"Propose a revised 5–8 step plan for improving the system. Prioritize steps that:",
|
||||
"",
|
||||
"- reduce repeatable/template patterns",
|
||||
"- strengthen mirror fidelity (numbers/tables/caveats)",
|
||||
"- make Mermaid + output correct-by-construction",
|
||||
"- make IF.TTT feel like real chain-of-custody (not just “we printed a hash”)",
|
||||
"",
|
||||
"## Fact checking guidance (important)",
|
||||
"",
|
||||
"Default posture: don’t assume stats are true.",
|
||||
"",
|
||||
"- If you did not verify a number externally, refer to it as “the source claims …”",
|
||||
"- If you did verify, cite URLs and state confirmed/disputed/unclear",
|
||||
"- If a stat cannot be confirmed, recommend either: keep it attributed (“source claims”) or remove it from commentary",
|
||||
"",
|
||||
"## Notes",
|
||||
"",
|
||||
`- Dossier ID: \`${job.id || ""}\``,
|
||||
`- Style: \`${job.style || ""}\``,
|
||||
]
|
||||
.filter((line) => line !== null)
|
||||
.join("\n");
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
function renderSingleFilePackMarkdown({ shareId, job, publicBaseUrl, externalReviewUrl, staticSourceUrl, staticPublicBaseUrl, dossierMarkdown }) {
|
||||
const primaryBase = normalizeBaseUrl(staticPublicBaseUrl || publicBaseUrl);
|
||||
const bases = staticMirrorBaseUrls(primaryBase, publicBaseUrl);
|
||||
const mirrorBase = bases.find((b) => b !== primaryBase) || "";
|
||||
|
||||
const dossierUrl = `${primaryBase}/static/dossier/${encodeURIComponent(shareId)}`;
|
||||
const traceUrl = `${primaryBase}/static/trace/${encodeURIComponent(shareId)}`;
|
||||
const downloadUrl = `${primaryBase}/static/dossier/${encodeURIComponent(shareId)}/download`;
|
||||
const reviewUrl = `${primaryBase}/static/review/${encodeURIComponent(shareId)}.md`;
|
||||
const packUrl = `${primaryBase}/static/pack/${encodeURIComponent(shareId)}.md`;
|
||||
|
||||
const jobSlim = {
|
||||
id: job?.id || "",
|
||||
status: job?.status || "",
|
||||
createdAt: job?.createdAt || "",
|
||||
originalFilename: job?.originalFilename || "",
|
||||
style: job?.style || "",
|
||||
sourceSha256: job?.sourceSha256 || "",
|
||||
outputSha256: job?.outputSha256 || "",
|
||||
warnings: job?.warnings || "",
|
||||
};
|
||||
|
||||
const lines = [
|
||||
"# InfraFabric External Review Pack — Single File",
|
||||
"",
|
||||
"This is a single-file bundle intended for review environments that cannot reliably fetch multiple URLs.",
|
||||
"",
|
||||
"## Links",
|
||||
"",
|
||||
`- Pack (this file): ${packUrl}`,
|
||||
`- Review pack (links only): ${reviewUrl}`,
|
||||
`- Dossier (rendered): ${dossierUrl}`,
|
||||
`- Dossier (download Markdown): ${downloadUrl}`,
|
||||
`- IF.TTT trace page: ${traceUrl}`,
|
||||
staticSourceUrl ? `- Source (download): ${staticSourceUrl}` : null,
|
||||
externalReviewUrl ? `- Feedback intake (login): ${externalReviewUrl}` : null,
|
||||
mirrorBase ? "" : null,
|
||||
mirrorBase ? "## Alternate host mirror (same paths)" : null,
|
||||
mirrorBase ? "" : null,
|
||||
mirrorBase ? `- Pack (this file): ${mirrorBase}/static/pack/${encodeURIComponent(shareId)}.md` : null,
|
||||
mirrorBase ? `- Review pack: ${mirrorBase}/static/review/${encodeURIComponent(shareId)}.md` : null,
|
||||
mirrorBase ? `- Dossier: ${mirrorBase}/static/dossier/${encodeURIComponent(shareId)}` : null,
|
||||
mirrorBase ? `- Trace: ${mirrorBase}/static/trace/${encodeURIComponent(shareId)}` : null,
|
||||
"",
|
||||
"## Review instructions (portable)",
|
||||
"",
|
||||
"Hard rules:",
|
||||
"1) 100% factual: every non-trivial claim must be tagged [SOURCE]/[DOSSIER]/[TRACE]/[INFERENCE]. If unverified, say “unverified” and stop.",
|
||||
"2) Vendor-neutral: critique deployment conditions + org behaviors, not vendor intent/competence.",
|
||||
"3) Mirror discipline: follow the dossier’s section order; do not invent a new structure.",
|
||||
"",
|
||||
"Deliverables:",
|
||||
"A) 5–10 bullets: what works / what doesn’t (tag each)",
|
||||
"B) Scorecard (0–5): mirror integrity, layout fidelity, humor discipline, mermaid value, IF.TTT demo value, CTA stealth",
|
||||
"C) Section-by-section critique (mirror headings): what’s mirrored, what’s missing, what feels templated/repeated",
|
||||
"D) Vendor-safe conclusion rewrite: success conditions / traps / questions-to-ask-vendor",
|
||||
"E) Unified diff patches against `IF_DAVE_BIBLE_v1.3.md` (and patchset if needed)",
|
||||
"",
|
||||
"## IF.TTT trace (portable extract)",
|
||||
"",
|
||||
"```json",
|
||||
JSON.stringify(jobSlim, null, 2),
|
||||
"```",
|
||||
"",
|
||||
"## Shadow dossier (Markdown)",
|
||||
"",
|
||||
"```markdown",
|
||||
String(dossierMarkdown || "").trim(),
|
||||
"```",
|
||||
"",
|
||||
];
|
||||
|
||||
return lines.filter((line) => line !== null && line !== undefined).join("\n");
|
||||
}
|
||||
|
||||
function extractSection(markdownText, sectionMatcher) {
|
||||
const text = String(markdownText || "");
|
||||
if (!text.trim()) return "";
|
||||
const lines = text.split("\n");
|
||||
const headingRe = /^##\s+/;
|
||||
let start = -1;
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (!headingRe.test(lines[i])) continue;
|
||||
const title = lines[i].replace(/^##\s+/, "").trim();
|
||||
if (sectionMatcher && sectionMatcher.test(title)) {
|
||||
start = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (start === -1) return "";
|
||||
let end = lines.length;
|
||||
for (let i = start + 1; i < lines.length; i++) {
|
||||
if (headingRe.test(lines[i])) {
|
||||
end = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return lines.slice(start, end).join("\n").trim();
|
||||
}
|
||||
|
||||
function extractFirstMermaid(markdownText) {
|
||||
const text = String(markdownText || "");
|
||||
const m = text.match(/```mermaid\s+([\s\S]*?)```/m);
|
||||
return m ? String(m[1] || "").trim() : "";
|
||||
}
|
||||
|
||||
function extractHeroMermaid(dossierMarkdown) {
|
||||
// Prefer the section that usually produces the most "hero" marketing loop for this dossier: audits.
|
||||
const sec02 = extractSection(dossierMarkdown, /^02\b/);
|
||||
const sec02Diagram = extractFirstMermaid(sec02);
|
||||
if (sec02Diagram) return { label: "Audit theater loop", diagram: sec02Diagram };
|
||||
const any = extractFirstMermaid(dossierMarkdown);
|
||||
if (any) return { label: "Incentive loop (inferred)", diagram: any };
|
||||
return { label: "", diagram: "" };
|
||||
}
|
||||
|
||||
function renderMarketingPackMarkdown({ shareId, job, publicBaseUrl, staticPublicBaseUrl, staticSourceUrl, dossierMarkdown }) {
|
||||
const primaryBase = normalizeBaseUrl(staticPublicBaseUrl || publicBaseUrl);
|
||||
const bases = staticMirrorBaseUrls(primaryBase, publicBaseUrl);
|
||||
const mirrorBase = bases.find((b) => b !== primaryBase) || "";
|
||||
|
||||
const marketingUrl = `${primaryBase}/static/marketing/${encodeURIComponent(shareId)}.md`;
|
||||
const dossierUrl = `${primaryBase}/static/dossier/${encodeURIComponent(shareId)}`;
|
||||
const traceUrl = `${primaryBase}/static/trace/${encodeURIComponent(shareId)}`;
|
||||
const packUrl = `${primaryBase}/static/pack/${encodeURIComponent(shareId)}.md`;
|
||||
|
||||
const hero = extractHeroMermaid(dossierMarkdown);
|
||||
|
||||
const lines = [
|
||||
"# InfraFabric Red Team — Marketing-safe excerpt",
|
||||
"",
|
||||
"This is a **marketing-safe surface** for sharing a Shadow Dossier without rewriting it into “features”.",
|
||||
"",
|
||||
"## One sentence (blessed)",
|
||||
"",
|
||||
"> InfraFabric shows why AI controls fail in practice and what must be true for them to hold.",
|
||||
"",
|
||||
"## Hero asset (blessed)",
|
||||
"",
|
||||
hero.diagram ? `### ${hero.label}` : "### (No diagram available)",
|
||||
"",
|
||||
hero.diagram ? "```mermaid" : null,
|
||||
hero.diagram ? hero.diagram : null,
|
||||
hero.diagram ? "```" : null,
|
||||
"",
|
||||
"## Allowed pulls (blessed)",
|
||||
"",
|
||||
"- Section titles",
|
||||
"- “Dave Factor” blocks (verbatim)",
|
||||
"- Mermaid diagrams (unchanged)",
|
||||
"- Red Team Conclusion questions (verbatim)",
|
||||
"",
|
||||
"## Forbidden moves (do not do this)",
|
||||
"",
|
||||
"- Rewriting countermeasures as product capabilities",
|
||||
"- Adding adjectives that imply guarantees (\"proven\", \"complete\", \"bulletproof\")",
|
||||
"- Removing the “problem isn’t X” framing (it’s the vendor-safe contract)",
|
||||
"- Inventing stats, timelines, or customer claims",
|
||||
"",
|
||||
"## CTAs (safe buttons)",
|
||||
"",
|
||||
"- Run this against your own rollout",
|
||||
"- See the trace behind this critique",
|
||||
"- Ask these questions internally",
|
||||
"",
|
||||
"## Links",
|
||||
"",
|
||||
`- Marketing excerpt (this file): ${marketingUrl}`,
|
||||
`- Full dossier (rendered): ${dossierUrl}`,
|
||||
`- Single-file pack (review + dossier + trace): ${packUrl}`,
|
||||
`- IF.TTT trace: ${traceUrl}`,
|
||||
staticSourceUrl ? `- Source (PDF): ${staticSourceUrl}` : null,
|
||||
mirrorBase ? "" : null,
|
||||
mirrorBase ? "## Mirror host (same paths)" : null,
|
||||
mirrorBase ? "" : null,
|
||||
mirrorBase ? `- Marketing excerpt: ${mirrorBase}/static/marketing/${encodeURIComponent(shareId)}.md` : null,
|
||||
mirrorBase ? `- Full dossier: ${mirrorBase}/static/dossier/${encodeURIComponent(shareId)}` : null,
|
||||
mirrorBase ? `- Pack: ${mirrorBase}/static/pack/${encodeURIComponent(shareId)}.md` : null,
|
||||
mirrorBase ? `- Trace: ${mirrorBase}/static/trace/${encodeURIComponent(shareId)}` : null,
|
||||
].filter((l) => l !== null);
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function ensureStaticSourceFile({ job, uploadsDir, staticSourceDir, projectRoot }) {
|
||||
if (!job?.sourcePath) return null;
|
||||
if (!job?.sourceSha256) return null;
|
||||
|
||||
const ext = path.extname(String(job.sourcePath || "")).slice(0, 12).toLowerCase() || ".bin";
|
||||
const fileName = `${job.sourceSha256}${ext}`;
|
||||
const destAbs = path.join(staticSourceDir, fileName);
|
||||
if (fs.existsSync(destAbs)) return { fileName, destAbs, urlPath: `/static/source/${fileName}` };
|
||||
|
||||
const srcAbs = path.resolve(projectRoot, job.sourcePath);
|
||||
if (!srcAbs.startsWith(uploadsDir + path.sep)) return null;
|
||||
if (!fs.existsSync(srcAbs)) return null;
|
||||
|
||||
ensureDir(staticSourceDir);
|
||||
fs.copyFileSync(srcAbs, destAbs);
|
||||
return { fileName, destAbs, urlPath: `/static/source/${fileName}` };
|
||||
}
|
||||
|
||||
function buildExternalReviewUrl(baseUrl, sheetId) {
|
||||
const base = String(baseUrl || "").trim();
|
||||
const id = String(sheetId || "").trim();
|
||||
if (!base || !id) return "";
|
||||
try {
|
||||
const u = new URL(base);
|
||||
u.searchParams.set("sheet", id);
|
||||
return u.toString();
|
||||
} catch {
|
||||
return base.includes("?") ? `${base}&sheet=${encodeURIComponent(id)}` : `${base}?sheet=${encodeURIComponent(id)}`;
|
||||
}
|
||||
}
|
||||
|
||||
function publicBaseFromRequest(req, fallbackHost) {
|
||||
const host = req.get("host") || fallbackHost || "red-team.infrafabric.io";
|
||||
const protocol = req.protocol || "https";
|
||||
return `${protocol}://${host}`.replace(/\/+$/g, "");
|
||||
}
|
||||
|
||||
function staticPublicBaseUrlForRequest(req, fallbackPublicBaseUrl) {
|
||||
const explicit = String(process.env.STATIC_SOURCE_PUBLIC_BASE_URL || "").trim();
|
||||
if (explicit) return explicit.replace(/\/+$/g, "");
|
||||
return publicBaseFromRequest(req, fallbackPublicBaseUrl);
|
||||
}
|
||||
|
||||
function looksLikeUuid(value) {
|
||||
return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(String(value || ""));
|
||||
}
|
||||
|
|
@ -55,6 +692,25 @@ function writeJob(jobsDir, job) {
|
|||
fs.writeFileSync(p, JSON.stringify(job, null, 2) + "\n", "utf8");
|
||||
}
|
||||
|
||||
function shareJsonPath(sharesDir, shareId) {
|
||||
return path.join(sharesDir, `${shareId}.json`);
|
||||
}
|
||||
|
||||
function writeShare(sharesDir, shareId, data) {
|
||||
const p = shareJsonPath(sharesDir, shareId);
|
||||
fs.writeFileSync(p, JSON.stringify(data, null, 2) + "\n", "utf8");
|
||||
}
|
||||
|
||||
function readShare(sharesDir, shareId) {
|
||||
const p = shareJsonPath(sharesDir, shareId);
|
||||
if (!fs.existsSync(p)) return null;
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(p, "utf8"));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function sha256File(filePath) {
|
||||
return await new Promise((resolve, reject) => {
|
||||
const h = crypto.createHash("sha256");
|
||||
|
|
@ -104,9 +760,13 @@ async function generateShadowDossier({ inputPath, outputPath }) {
|
|||
PYTHONPATH: path.join(revoiceRepoRoot, "src"),
|
||||
};
|
||||
|
||||
const genArgs = ["-m", "revoice", "generate", "--style", privateUploadStyle];
|
||||
if (["1", "true", "yes", "on"].includes(privateUploadActionPack)) genArgs.push("--action-pack");
|
||||
genArgs.push("--input", inputPath, "--output", outputPath);
|
||||
|
||||
const gen = await runProcess(
|
||||
"python3",
|
||||
["-m", "revoice", "generate", "--style", privateUploadStyle, "--input", inputPath, "--output", outputPath],
|
||||
genArgs,
|
||||
{ cwd: revoiceRepoRoot, env: baseEnv }
|
||||
);
|
||||
if (gen.code !== 0) {
|
||||
|
|
@ -166,17 +826,26 @@ function generateRoastText(content) {
|
|||
function main() {
|
||||
const port = Number(process.env.PORT || 8080);
|
||||
const app = express();
|
||||
const dataDir = path.join(projectRoot, "data");
|
||||
app.set("trust proxy", true);
|
||||
const configuredDataDir = String(process.env.RED_TEAM_DATA_DIR || "").trim();
|
||||
const dataDir = configuredDataDir ? path.resolve(configuredDataDir) : path.join(projectRoot, "data");
|
||||
const uploadsDir = path.join(dataDir, "uploads");
|
||||
const outputsDir = path.join(dataDir, "outputs");
|
||||
const jobsDir = path.join(dataDir, "jobs");
|
||||
const sharesDir = path.join(dataDir, "shares");
|
||||
const staticDir = path.join(dataDir, "static");
|
||||
const staticSourceDir = path.join(staticDir, "source");
|
||||
|
||||
ensureDir(uploadsDir);
|
||||
ensureDir(outputsDir);
|
||||
ensureDir(jobsDir);
|
||||
ensureDir(sharesDir);
|
||||
ensureDir(staticDir);
|
||||
ensureDir(staticSourceDir);
|
||||
|
||||
app.disable("x-powered-by");
|
||||
app.use(express.json({ limit: "256kb" }));
|
||||
app.use("/static", express.static(staticDir, { fallthrough: false, etag: true, maxAge: "30d" }));
|
||||
|
||||
app.get("/healthz", (_req, res) => {
|
||||
res.status(200).json({ ok: true });
|
||||
|
|
@ -256,7 +925,16 @@ function main() {
|
|||
const refresh = isDone || isError ? "" : "<meta http-equiv='refresh' content='2'/>";
|
||||
|
||||
const downloadLink = isDone
|
||||
? `<p><a href="/private/${encodeURIComponent(token)}/download/${encodeURIComponent(jobId)}">Download shadow dossier</a></p>`
|
||||
? `<p><a href="/private/${encodeURIComponent(token)}/download/${encodeURIComponent(jobId)}">Download Markdown</a></p>`
|
||||
: "";
|
||||
const viewLink = isDone
|
||||
? `<p><a href="/private/${encodeURIComponent(token)}/view/${encodeURIComponent(jobId)}">View rendered dossier</a></p>`
|
||||
: "";
|
||||
const shareLinks = isDone && job.shareId
|
||||
? [
|
||||
"<p><strong>Share links</strong> (no repo):</p>",
|
||||
`<p><a href="/r/${encodeURIComponent(job.shareId)}">Public view</a> · <a href="/r/${encodeURIComponent(job.shareId)}/download">Public download</a></p>`,
|
||||
].join("")
|
||||
: "";
|
||||
|
||||
const sourceLink = job.sourcePath
|
||||
|
|
@ -285,8 +963,11 @@ function main() {
|
|||
job.originalFilename ? `<p>Source: <code>${escapeHtml(job.originalFilename)}</code></p>` : "",
|
||||
job.sourceSha256 ? `<p>Source sha256: <code>${escapeHtml(job.sourceSha256)}</code></p>` : "",
|
||||
job.outputSha256 ? `<p>Output sha256: <code>${escapeHtml(job.outputSha256)}</code></p>` : "",
|
||||
job.shareId ? `<p>Share ID: <code>${escapeHtml(job.shareId)}</code></p>` : "",
|
||||
downloadLink,
|
||||
viewLink,
|
||||
sourceLink,
|
||||
shareLinks,
|
||||
warnings ? "<h2>Warnings</h2>" + warnings : "",
|
||||
isError ? "<h2>Error</h2>" + error : "",
|
||||
`<p><a href="/private/${encodeURIComponent(token)}">Back to upload</a></p>`,
|
||||
|
|
@ -295,6 +976,209 @@ function main() {
|
|||
);
|
||||
});
|
||||
|
||||
app.get("/private/:token/view/:jobId", privateGuard, (req, res) => {
|
||||
const jobId = String(req.params.jobId || "");
|
||||
if (!looksLikeUuid(jobId)) return res.status(404).type("text/plain").send("Not found");
|
||||
const job = readJob(jobsDir, jobId);
|
||||
if (!job) return res.status(404).type("text/plain").send("Not found");
|
||||
if (!job.outputPath) return res.status(409).type("text/plain").send("Not ready");
|
||||
|
||||
const abs = path.resolve(projectRoot, job.outputPath);
|
||||
if (!abs.startsWith(outputsDir + path.sep)) return res.status(400).type("text/plain").send("Bad path");
|
||||
if (!fs.existsSync(abs)) return res.status(404).type("text/plain").send("Not found");
|
||||
|
||||
const mdText = fs.readFileSync(abs, "utf8");
|
||||
const html = markdown.render(mdText);
|
||||
|
||||
const token = req.params.token;
|
||||
const topLinks = [
|
||||
`<a href="/private/${encodeURIComponent(token)}/download/${encodeURIComponent(jobId)}">Download Markdown</a>`,
|
||||
job.sourcePath ? `<a href="/private/${encodeURIComponent(token)}/source/${encodeURIComponent(jobId)}">Download source</a>` : "",
|
||||
job.shareId ? `<a href="/r/${encodeURIComponent(job.shareId)}">Share view</a>` : "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" · ");
|
||||
|
||||
res.status(200).type("text/html; charset=utf-8").send(renderMarkdownPage({ title: job.originalFilename || "Shadow dossier", html, topLinksHtml: topLinks }));
|
||||
});
|
||||
|
||||
app.get("/r/:shareId/trace", async (req, res) => {
|
||||
const shareId = String(req.params.shareId || "").trim();
|
||||
if (!shareId) return res.status(404).type("text/plain").send("Not found");
|
||||
const share = readShare(sharesDir, shareId);
|
||||
if (!share?.jobId || !looksLikeUuid(share.jobId)) return res.status(404).type("text/plain").send("Not found");
|
||||
const job = readJob(jobsDir, share.jobId);
|
||||
if (!job?.outputPath) return res.status(404).type("text/plain").send("Not found");
|
||||
|
||||
const verification = await computeVerificationStatus({ job, projectRoot, outputsDir, uploadsDir });
|
||||
const jobForRender = { ...job, _verification: verification };
|
||||
|
||||
const publicBaseUrl = publicBaseFromRequest(req, "red-team.infrafabric.io");
|
||||
const staticPublicBaseUrl = staticPublicBaseUrlForRequest(req, publicBaseUrl);
|
||||
const md = renderTraceMarkdown({ shareId, job: jobForRender, publicBaseUrl, staticPublicBaseUrl });
|
||||
const html = markdown.render(md);
|
||||
|
||||
const badge = renderVerificationBadgeHtml(verification);
|
||||
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>` : "",
|
||||
`<a href="/r/${encodeURIComponent(shareId)}/review-pack.md">Review pack (MD)</a>`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" · ");
|
||||
|
||||
res
|
||||
.status(200)
|
||||
.type("text/html; charset=utf-8")
|
||||
.send(renderMarkdownPage({ title: "IF.TTT trace", html, topLinksHtml: topLinks }));
|
||||
});
|
||||
|
||||
app.get("/r/:shareId/source", (req, res) => {
|
||||
const shareId = String(req.params.shareId || "").trim();
|
||||
if (!shareId) return res.status(404).type("text/plain").send("Not found");
|
||||
const share = readShare(sharesDir, shareId);
|
||||
if (!share?.jobId || !looksLikeUuid(share.jobId)) return res.status(404).type("text/plain").send("Not found");
|
||||
const job = readJob(jobsDir, share.jobId);
|
||||
if (!job?.sourcePath) return res.status(404).type("text/plain").send("Not found");
|
||||
|
||||
const staticFile = ensureStaticSourceFile({ job, uploadsDir, staticSourceDir, projectRoot });
|
||||
if (!staticFile) return res.status(404).type("text/plain").send("Not found");
|
||||
res.redirect(302, staticFile.urlPath);
|
||||
});
|
||||
|
||||
app.get("/r/:shareId/review-pack.md", (req, res) => {
|
||||
const shareId = String(req.params.shareId || "").trim();
|
||||
if (!shareId) return res.status(404).type("text/plain").send("Not found");
|
||||
const share = readShare(sharesDir, shareId);
|
||||
if (!share?.jobId || !looksLikeUuid(share.jobId)) return res.status(404).type("text/plain").send("Not found");
|
||||
const job = readJob(jobsDir, share.jobId);
|
||||
if (!job?.outputPath) return res.status(404).type("text/plain").send("Not found");
|
||||
|
||||
const staticSource = ensureStaticSourceFile({ job, uploadsDir, staticSourceDir, projectRoot });
|
||||
const externalReviewBaseUrl = String(process.env.EXTERNAL_REVIEW_BASE_URL || "https://emo-social.infrafabric.io/external-review.html");
|
||||
const externalReviewUrl = buildExternalReviewUrl(externalReviewBaseUrl, share.reviewSheetId);
|
||||
const publicBaseUrl = publicBaseFromRequest(req, "red-team.infrafabric.io");
|
||||
const staticPublicBaseUrl = staticPublicBaseUrlForRequest(req, publicBaseUrl);
|
||||
const staticSourceUrl = staticSource ? `${staticPublicBaseUrl}${staticSource.urlPath}` : "";
|
||||
|
||||
const md = renderReviewPackMarkdown({ shareId, job, publicBaseUrl, externalReviewUrl, staticSourceUrl, staticPublicBaseUrl });
|
||||
const baseName = (job.originalFilename || "dossier").replace(/[^A-Za-z0-9._-]+/g, "-").slice(0, 60);
|
||||
res
|
||||
.status(200)
|
||||
.type("text/markdown; charset=utf-8")
|
||||
.send(md);
|
||||
});
|
||||
|
||||
app.get("/r/:shareId/pack.md", (req, res) => {
|
||||
const shareId = String(req.params.shareId || "").trim();
|
||||
if (!shareId) return res.status(404).type("text/plain").send("Not found");
|
||||
const share = readShare(sharesDir, shareId);
|
||||
if (!share?.jobId || !looksLikeUuid(share.jobId)) return res.status(404).type("text/plain").send("Not found");
|
||||
const job = readJob(jobsDir, share.jobId);
|
||||
if (!job?.outputPath) return res.status(404).type("text/plain").send("Not found");
|
||||
|
||||
const abs = path.resolve(projectRoot, job.outputPath);
|
||||
if (!abs.startsWith(outputsDir + path.sep)) return res.status(400).type("text/plain").send("Bad path");
|
||||
if (!fs.existsSync(abs)) return res.status(404).type("text/plain").send("Not found");
|
||||
const dossierMarkdown = fs.readFileSync(abs, "utf8");
|
||||
|
||||
const staticSource = ensureStaticSourceFile({ job, uploadsDir, staticSourceDir, projectRoot });
|
||||
const externalReviewBaseUrl = String(process.env.EXTERNAL_REVIEW_BASE_URL || "https://emo-social.infrafabric.io/external-review.html");
|
||||
const externalReviewUrl = buildExternalReviewUrl(externalReviewBaseUrl, share.reviewSheetId);
|
||||
const publicBaseUrl = publicBaseFromRequest(req, "red-team.infrafabric.io");
|
||||
const staticPublicBaseUrl = staticPublicBaseUrlForRequest(req, publicBaseUrl);
|
||||
const staticSourceUrl = staticSource ? `${staticPublicBaseUrl}${staticSource.urlPath}` : "";
|
||||
|
||||
const md = renderSingleFilePackMarkdown({
|
||||
shareId,
|
||||
job,
|
||||
publicBaseUrl,
|
||||
externalReviewUrl,
|
||||
staticSourceUrl,
|
||||
staticPublicBaseUrl,
|
||||
dossierMarkdown,
|
||||
});
|
||||
|
||||
res.status(200).type("text/markdown; charset=utf-8").send(md);
|
||||
});
|
||||
|
||||
app.get("/r/:shareId/marketing.md", (req, res) => {
|
||||
const shareId = String(req.params.shareId || "").trim();
|
||||
if (!shareId) return res.status(404).type("text/plain").send("Not found");
|
||||
const share = readShare(sharesDir, shareId);
|
||||
if (!share?.jobId || !looksLikeUuid(share.jobId)) return res.status(404).type("text/plain").send("Not found");
|
||||
const job = readJob(jobsDir, share.jobId);
|
||||
if (!job?.outputPath) return res.status(404).type("text/plain").send("Not found");
|
||||
|
||||
const abs = path.resolve(projectRoot, job.outputPath);
|
||||
if (!abs.startsWith(outputsDir + path.sep)) return res.status(400).type("text/plain").send("Bad path");
|
||||
if (!fs.existsSync(abs)) return res.status(404).type("text/plain").send("Not found");
|
||||
const dossierMarkdown = fs.readFileSync(abs, "utf8");
|
||||
|
||||
const staticSource = ensureStaticSourceFile({ job, uploadsDir, staticSourceDir, projectRoot });
|
||||
const publicBaseUrl = publicBaseFromRequest(req, "red-team.infrafabric.io");
|
||||
const staticPublicBaseUrl = staticPublicBaseUrlForRequest(req, publicBaseUrl);
|
||||
const staticSourceUrl = staticSource ? `${staticPublicBaseUrl}${staticSource.urlPath}` : "";
|
||||
|
||||
const md = renderMarketingPackMarkdown({
|
||||
shareId,
|
||||
job,
|
||||
publicBaseUrl,
|
||||
staticPublicBaseUrl,
|
||||
staticSourceUrl,
|
||||
dossierMarkdown,
|
||||
});
|
||||
|
||||
res.status(200).type("text/markdown; charset=utf-8").send(md);
|
||||
});
|
||||
|
||||
app.get("/r/:shareId", (req, res) => {
|
||||
const shareId = String(req.params.shareId || "").trim();
|
||||
if (!shareId) return res.status(404).type("text/plain").send("Not found");
|
||||
const share = readShare(sharesDir, shareId);
|
||||
if (!share?.jobId || !looksLikeUuid(share.jobId)) return res.status(404).type("text/plain").send("Not found");
|
||||
const job = readJob(jobsDir, share.jobId);
|
||||
if (!job?.outputPath) return res.status(404).type("text/plain").send("Not found");
|
||||
|
||||
const abs = path.resolve(projectRoot, job.outputPath);
|
||||
if (!abs.startsWith(outputsDir + path.sep)) return res.status(400).type("text/plain").send("Bad path");
|
||||
if (!fs.existsSync(abs)) return res.status(404).type("text/plain").send("Not found");
|
||||
|
||||
const mdText = fs.readFileSync(abs, "utf8");
|
||||
const html = markdown.render(mdText);
|
||||
const externalReviewBaseUrl = String(process.env.EXTERNAL_REVIEW_BASE_URL || "https://emo-social.infrafabric.io/external-review.html");
|
||||
const externalReviewUrl = buildExternalReviewUrl(externalReviewBaseUrl, share.reviewSheetId);
|
||||
const topLinks = [
|
||||
`<a href="/r/${encodeURIComponent(shareId)}/download">Download Markdown</a>`,
|
||||
job.sourcePath ? `<a href="/r/${encodeURIComponent(shareId)}/source">Download source</a>` : "",
|
||||
`<a href="/r/${encodeURIComponent(shareId)}/trace">IF.TTT trace</a>`,
|
||||
`<a href="/r/${encodeURIComponent(shareId)}/review-pack.md">Review pack (MD)</a>`,
|
||||
`<a href="/r/${encodeURIComponent(shareId)}/marketing.md">Marketing excerpt (MD)</a>`,
|
||||
externalReviewUrl ? `<a href="${escapeHtml(externalReviewUrl)}" target="_blank" rel="noreferrer">Feedback intake (login)</a>` : "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" · ");
|
||||
res.status(200).type("text/html; charset=utf-8").send(renderMarkdownPage({ title: job.originalFilename || "Shadow dossier", html, topLinksHtml: topLinks }));
|
||||
});
|
||||
|
||||
app.get("/r/:shareId/download", (req, res) => {
|
||||
const shareId = String(req.params.shareId || "").trim();
|
||||
if (!shareId) return res.status(404).type("text/plain").send("Not found");
|
||||
const share = readShare(sharesDir, shareId);
|
||||
if (!share?.jobId || !looksLikeUuid(share.jobId)) return res.status(404).type("text/plain").send("Not found");
|
||||
const job = readJob(jobsDir, share.jobId);
|
||||
if (!job?.outputPath) return res.status(404).type("text/plain").send("Not found");
|
||||
|
||||
const abs = path.resolve(projectRoot, job.outputPath);
|
||||
if (!abs.startsWith(outputsDir + path.sep)) return res.status(400).type("text/plain").send("Bad path");
|
||||
if (!fs.existsSync(abs)) return res.status(404).type("text/plain").send("Not found");
|
||||
|
||||
const baseName = (job.originalFilename || "dossier").replace(/[^A-Za-z0-9._-]+/g, "-").slice(0, 60);
|
||||
res.download(abs, `${baseName}.shadow.dave.md`);
|
||||
});
|
||||
|
||||
app.get("/private/:token/download/:jobId", privateGuard, (req, res) => {
|
||||
const jobId = String(req.params.jobId || "");
|
||||
if (!looksLikeUuid(jobId)) return res.status(404).type("text/plain").send("Not found");
|
||||
|
|
@ -328,8 +1212,9 @@ function main() {
|
|||
if (!file?.path) return res.status(400).type("text/plain").send("Missing file");
|
||||
|
||||
const relSourcePath = path.relative(projectRoot, file.path);
|
||||
const relOutputPath = path.join("data", "outputs", `${jobId}.shadow.dave.md`);
|
||||
const absOutputPath = path.resolve(projectRoot, relOutputPath);
|
||||
const absOutputPath = path.join(outputsDir, `${jobId}.shadow.dave.md`);
|
||||
const relOutputPath = path.relative(projectRoot, absOutputPath);
|
||||
const shareId = crypto.randomBytes(18).toString("base64url");
|
||||
|
||||
const now = new Date().toISOString();
|
||||
const job = {
|
||||
|
|
@ -339,6 +1224,7 @@ function main() {
|
|||
originalFilename: file.originalname || "",
|
||||
sourcePath: relSourcePath,
|
||||
outputPath: relOutputPath,
|
||||
shareId,
|
||||
style: privateUploadStyle,
|
||||
sourceBytes: Number(file.size || 0),
|
||||
sourceSha256: "",
|
||||
|
|
@ -357,6 +1243,7 @@ function main() {
|
|||
}
|
||||
|
||||
writeJob(jobsDir, job);
|
||||
writeShare(sharesDir, shareId, { shareId, jobId, createdAt: now });
|
||||
|
||||
void (async () => {
|
||||
try {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue