IF.TTT: improve reviewability + thread pack extraction
This commit is contained in:
parent
bac86571a8
commit
b644130412
4 changed files with 1206 additions and 63 deletions
41
AGENTS.md
41
AGENTS.md
|
|
@ -22,11 +22,52 @@ There is a sync job that mirrors `https://git.infrafabric.io/danny/hosted.git` i
|
|||
**Important:** The sync uses `rsync --delete`, so anything not in the mirrored repo would normally be removed. To keep operator-generated review artifacts stable, the sync script now excludes:
|
||||
- `bibles/`
|
||||
- `review/`
|
||||
- `iftrace.py` (operator-maintained; don’t overwrite via repo sync)
|
||||
|
||||
So **publish operator-generated bibles/review packs under**:
|
||||
- `/srv/hosted-static/public/bibles/…`
|
||||
- `/srv/hosted-static/public/review/…`
|
||||
|
||||
## HTML-only sandbox fallback (new; critical for external reviewers)
|
||||
|
||||
Some LLM “web fetchers” can load HTML but fail on `.md/.py/.tar.gz`. To keep the IF.TTT “open governance” premise intact for external review:
|
||||
|
||||
- Keep the raw assets (`.md`, `.tar.gz`) **and** provide an **HTML view** on the same stable alias surface.
|
||||
- Share surface:
|
||||
- Raw pack: `/static/pack/<shareId>.md`
|
||||
- HTML pack view: `/static/pack/<shareId>`
|
||||
- Raw review pack: `/static/review/<shareId>.md` (alt: `/static/review-pack/<shareId>.md`)
|
||||
- HTML review pack view: `/static/review/<shareId>` (alt: `/static/review-pack/<shareId>`)
|
||||
- Raw marketing: `/static/marketing/<shareId>.md`
|
||||
- HTML marketing view: `/static/marketing/<shareId>`
|
||||
- Hosted review artifacts (`/static/hosted/review/**`) also have `.html` wrappers generated post-sync.
|
||||
|
||||
Implementation notes:
|
||||
- Caddy rewrites `/static/*` (HTML view endpoints) to the red-team app (`pct 212`).
|
||||
- Hosted `.html` wrappers are generated by `pct 210:/usr/local/bin/hosted_static_build_html_wrappers.py` after each sync.
|
||||
|
||||
## Full stack + links (operator reference)
|
||||
|
||||
- `/root/docs/19-ifttt-full-stack-and-working-links.md` is the “single page” reference for:
|
||||
- Which apps run where (pct IDs + IPs)
|
||||
- Which URLs are canonical for sharing
|
||||
- Copy/paste-safe example links
|
||||
- IF.TTT public overview page (hosted-static): https://infrafabric.io/static/hosted/ifttt/
|
||||
|
||||
## IF.TTT paper update review pack (known-good example)
|
||||
|
||||
Use this pack when requesting external critique of the IF.TTT paper update (receipt-first chronology + public receipts + triage bundles):
|
||||
|
||||
- Landing: `https://infrafabric.io/static/hosted/review/ifttt-paper-update/2025-12-28/`
|
||||
- Pack (MD): `https://infrafabric.io/static/hosted/review/ifttt-paper-update/2025-12-28/review-pack.md`
|
||||
- Pack (HTML): `https://infrafabric.io/static/hosted/review/ifttt-paper-update/2025-12-28/review-pack.html`
|
||||
- Pack (tar.gz): `https://infrafabric.io/static/hosted/review/ifttt-paper-update/2025-12-28/review-pack.tar.gz`
|
||||
- Pack hash: `https://infrafabric.io/static/hosted/review/ifttt-paper-update/2025-12-28/review-pack.tar.gz.sha256`
|
||||
- Triage selector demo (canonical): `https://infrafabric.io/static/hosted/review/trace-bundles/d70ed99a/index.md`
|
||||
- Offline verifier: `https://infrafabric.io/static/hosted/iftrace.py`
|
||||
|
||||
Note: some LLM “web fetchers” reject `.tar.gz` with a client-side `415` even when browsers/curl succeed; use the `.html` pack in those environments.
|
||||
|
||||
## Week review packs (v1.8)
|
||||
|
||||
Week v1.8 packs are published here:
|
||||
|
|
|
|||
|
|
@ -8,6 +8,24 @@ import MarkdownIt from "markdown-it";
|
|||
import express from "express";
|
||||
import multer from "multer";
|
||||
|
||||
/*
|
||||
Public, no-login receipt surface (IF.TTT)
|
||||
----------------------------------------
|
||||
This server exposes Shadow Dossiers and their "receipt" artifacts via two parallel
|
||||
representations:
|
||||
|
||||
- Raw (download-friendly): `*.md` (and tarballs elsewhere)
|
||||
- HTML views: same path without the `.md` suffix
|
||||
|
||||
Rationale: some external review environments (including certain LLM "web fetchers")
|
||||
reliably load `text/html` but may reject "downloadable" assets like `.md/.py/.tar.gz`.
|
||||
Keeping both surfaces makes the governance/receipt premise reviewable by humans *and*
|
||||
restricted sandboxes.
|
||||
|
||||
Deployment detail: the stable public aliases live under `/static/*` on the public
|
||||
domain and are reverse-proxied here (see operator docs: `/root/docs/17-ifttt-public-receipt-surface.md`).
|
||||
*/
|
||||
|
||||
const __filename = url.fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
|
|
@ -145,6 +163,7 @@ function renderTraceHeaderHtml({ verification, job }) {
|
|||
const createdAt = job?.createdAt ? String(job.createdAt) : "";
|
||||
const traceId = String(job?.id || "");
|
||||
const style = String(job?.style || "");
|
||||
const ttt = job?._ttt_trace_receipt || null;
|
||||
|
||||
const checks = verification?.checks || {};
|
||||
const outputOk = checks.outputOk === true;
|
||||
|
|
@ -154,6 +173,11 @@ function renderTraceHeaderHtml({ verification, job }) {
|
|||
const outputLabel = outputOk ? "PASS" : "FAIL";
|
||||
const sourceLabel = sourceOk === true ? "PASS" : sourceOk === false ? "FAIL" : "UNKNOWN";
|
||||
|
||||
const quantumReady = Boolean(ttt && ttt.quantum_ready === true);
|
||||
const pqAlgo = ttt && ttt.pq_algo ? String(ttt.pq_algo) : "";
|
||||
const pqStatus = ttt && ttt.pq_status ? String(ttt.pq_status) : "";
|
||||
const pqLabel = quantumReady ? `READY${pqAlgo ? ` (${pqAlgo})` : ""}` : ttt ? "ABSENT" : "UNKNOWN";
|
||||
|
||||
const safeCreatedAt = createdAt ? escapeHtml(createdAt) : "";
|
||||
const safeTraceId = traceId ? escapeHtml(traceId) : "";
|
||||
const safeStyle = style ? escapeHtml(style) : "";
|
||||
|
|
@ -171,6 +195,7 @@ function renderTraceHeaderHtml({ verification, job }) {
|
|||
` <ul class="trace-checks">`,
|
||||
` <li>Output hash check: <strong>${escapeHtml(outputLabel)}</strong></li>`,
|
||||
` <li>Source hash check: <strong>${escapeHtml(sourceLabel)}</strong></li>`,
|
||||
` <li>Quantum-ready receipt: <strong>${escapeHtml(pqLabel)}</strong>${pqStatus ? ` <small>(${escapeHtml(pqStatus)})</small>` : ""}</li>`,
|
||||
` <li>Quality warnings: <strong>${warningsPresent ? "present" : "none recorded"}</strong></li>`,
|
||||
` </ul>`,
|
||||
`</div>`,
|
||||
|
|
@ -258,11 +283,30 @@ function renderTraceMarkdown({ shareId, job, publicBaseUrl, staticPublicBaseUrl
|
|||
const createdAt = job?.createdAt ? String(job.createdAt) : "";
|
||||
const status = job?.status ? String(job.status) : "";
|
||||
const warningsPresent = Boolean(job?.warnings && String(job.warnings).trim());
|
||||
const ttt = job?._ttt_trace_receipt || job?.tttTraceReceipt || null;
|
||||
const tttId = ttt && ttt.id ? String(ttt.id) : "";
|
||||
const tttHash = ttt && ttt.content_hash ? String(ttt.content_hash) : "";
|
||||
const pqReady = Boolean(ttt && ttt.quantum_ready === true);
|
||||
const pqAlgo = ttt && ttt.pq_algo ? String(ttt.pq_algo) : "";
|
||||
const pqStatus = ttt && ttt.pq_status ? String(ttt.pq_status) : "";
|
||||
|
||||
const traceId = String(job?.id || "").trim();
|
||||
const tracePrefixRaw = traceId ? traceId.split("-")[0] : "";
|
||||
const tracePrefix = /^[0-9a-f]{8}$/i.test(tracePrefixRaw) ? tracePrefixRaw.toLowerCase() : "";
|
||||
const triageSelectorUrl = tracePrefix
|
||||
? `${primaryBase}/static/hosted/review/trace-bundles/${encodeURIComponent(tracePrefix)}/index.html`
|
||||
: "";
|
||||
const triageSelectorUrlRaw = tracePrefix
|
||||
? `${primaryBase}/static/hosted/review/trace-bundles/${encodeURIComponent(tracePrefix)}/index.md`
|
||||
: "";
|
||||
|
||||
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 packHtmlUrl = `${primaryBase}/static/pack/${encodeURIComponent(shareId)}`;
|
||||
const reviewHtmlUrl = `${primaryBase}/static/review/${encodeURIComponent(shareId)}`;
|
||||
const marketingHtmlUrl = `${primaryBase}/static/marketing/${encodeURIComponent(shareId)}`;
|
||||
const sourceUrl = job?.sourceSha256
|
||||
? `${primaryBase}/static/source/${job.sourceSha256}${path.extname(job.sourcePath || "").toLowerCase()}`
|
||||
: "";
|
||||
|
|
@ -272,6 +316,9 @@ function renderTraceMarkdown({ shareId, job, publicBaseUrl, staticPublicBaseUrl
|
|||
const directTraceUrl = `${directBase}/r/${encodeURIComponent(shareId)}/trace`;
|
||||
const directDownloadUrl = `${directBase}/r/${encodeURIComponent(shareId)}/download`;
|
||||
const directPackUrl = `${directBase}/r/${encodeURIComponent(shareId)}/pack.md`;
|
||||
const directPackHtmlUrl = `${directBase}/r/${encodeURIComponent(shareId)}/pack`;
|
||||
const directReviewHtmlUrl = `${directBase}/r/${encodeURIComponent(shareId)}/review-pack`;
|
||||
const directMarketingHtmlUrl = `${directBase}/r/${encodeURIComponent(shareId)}/marketing`;
|
||||
|
||||
const lastResortBase = normalizeBaseUrl(publicBaseUrl);
|
||||
const lastResortDossierUrl = lastResortBase ? `${lastResortBase}/r/${encodeURIComponent(shareId)}` : "";
|
||||
|
|
@ -289,6 +336,9 @@ function renderTraceMarkdown({ shareId, job, publicBaseUrl, staticPublicBaseUrl
|
|||
"- 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.",
|
||||
pqReady
|
||||
? `- This trace also has a **Quantum-ready** signed receipt record (${pqAlgo || "PQ"}; ${pqStatus || "hybrid"}).`
|
||||
: "- This trace does not claim any post-quantum proof unless the header says QUANTUM READY.",
|
||||
"",
|
||||
"## What this trace does not prove",
|
||||
"",
|
||||
|
|
@ -301,6 +351,11 @@ function renderTraceMarkdown({ shareId, job, publicBaseUrl, staticPublicBaseUrl
|
|||
`- Dossier (rendered): ${dossierUrl}`,
|
||||
`- Dossier (download Markdown): ${downloadUrl}`,
|
||||
`- Single-file pack (review + dossier + trace): ${packUrl}`,
|
||||
`- Pack (HTML view; for restrictive sandboxes): ${packHtmlUrl}`,
|
||||
`- Review pack (HTML view; links-only): ${reviewHtmlUrl}`,
|
||||
`- Marketing excerpt (HTML view): ${marketingHtmlUrl}`,
|
||||
triageSelectorUrl ? `- Offline bundles (triage selector): ${triageSelectorUrl}` : null,
|
||||
triageSelectorUrlRaw ? `- Offline bundles (raw Markdown): ${triageSelectorUrlRaw}` : null,
|
||||
sourceUrl ? `- Source (PDF): ${sourceUrl}` : null,
|
||||
`- This trace page: ${traceUrl}`,
|
||||
mirrorBase ? "" : null,
|
||||
|
|
@ -308,6 +363,7 @@ function renderTraceMarkdown({ shareId, job, publicBaseUrl, staticPublicBaseUrl
|
|||
mirrorBase ? "" : null,
|
||||
mirrorBase ? `- Dossier: ${mirrorBase}/static/dossier/${encodeURIComponent(shareId)}` : null,
|
||||
mirrorBase ? `- Pack: ${mirrorBase}/static/pack/${encodeURIComponent(shareId)}.md` : null,
|
||||
mirrorBase ? `- Pack (HTML view): ${mirrorBase}/static/pack/${encodeURIComponent(shareId)}` : null,
|
||||
mirrorBase ? `- Trace: ${mirrorBase}/static/trace/${encodeURIComponent(shareId)}` : null,
|
||||
mirrorBase && sourceUrl ? `- Source: ${mirrorBase}/static/source/${job.sourceSha256}${path.extname(job.sourcePath || "").toLowerCase()}` : null,
|
||||
"",
|
||||
|
|
@ -316,6 +372,9 @@ function renderTraceMarkdown({ shareId, job, publicBaseUrl, staticPublicBaseUrl
|
|||
`- Dossier: ${directDossierUrl}`,
|
||||
`- Download: ${directDownloadUrl}`,
|
||||
`- Pack: ${directPackUrl}`,
|
||||
`- Pack (HTML view): ${directPackHtmlUrl}`,
|
||||
`- Review pack (HTML view): ${directReviewHtmlUrl}`,
|
||||
`- Marketing excerpt (HTML view): ${directMarketingHtmlUrl}`,
|
||||
`- Trace: ${directTraceUrl}`,
|
||||
lastResortBase && lastResortBase !== directBase ? "" : null,
|
||||
lastResortBase && lastResortBase !== directBase ? "## Last resort (alternate host)" : null,
|
||||
|
|
@ -338,6 +397,9 @@ function renderTraceMarkdown({ shareId, job, publicBaseUrl, staticPublicBaseUrl
|
|||
`- Source sha256: \`${job.sourceSha256 || ""}\``,
|
||||
`- Style: \`${job.style || ""}\``,
|
||||
`- Source bytes: \`${String(job.sourceBytes ?? "")}\``,
|
||||
tttId ? `- Signed trace receipt ID: \`${tttId}\`` : null,
|
||||
tttHash ? `- Signed trace receipt hash: \`${tttHash}\`` : null,
|
||||
pqStatus ? `- PQ status: \`${pqStatus}\`` : null,
|
||||
"",
|
||||
"## How to verify (locally)",
|
||||
"",
|
||||
|
|
@ -705,6 +767,140 @@ function staticPublicBaseUrlForRequest(req, fallbackPublicBaseUrl) {
|
|||
return publicBaseFromRequest(req, fallbackPublicBaseUrl);
|
||||
}
|
||||
|
||||
function tttRegistryBaseUrl() {
|
||||
const explicit = String(process.env.TTT_REGISTRY_BASE_URL || "").trim();
|
||||
return explicit ? explicit.replace(/\/+$/g, "") : "";
|
||||
}
|
||||
|
||||
function tttRegistryApiToken() {
|
||||
return String(process.env.TTT_API_TOKEN || "").trim();
|
||||
}
|
||||
|
||||
async function fetchJson(url, { method = "GET", headers, body, timeoutMs = 4500 } = {}) {
|
||||
const controller = new AbortController();
|
||||
const t = setTimeout(() => controller.abort(), timeoutMs);
|
||||
try {
|
||||
const resp = await fetch(url, { method, headers, body, signal: controller.signal });
|
||||
const text = await resp.text();
|
||||
let data = null;
|
||||
try {
|
||||
data = JSON.parse(text);
|
||||
} catch {
|
||||
data = { raw: text };
|
||||
}
|
||||
return { ok: resp.ok, status: resp.status, data };
|
||||
} finally {
|
||||
clearTimeout(t);
|
||||
}
|
||||
}
|
||||
|
||||
function traceReceiptRecordId(traceId) {
|
||||
const id = String(traceId || "").trim();
|
||||
if (!looksLikeUuid(id)) return "";
|
||||
return `if://trace/${id}/v1`;
|
||||
}
|
||||
|
||||
function buildTraceReceiptEvidence({ job, shareId, staticPublicBaseUrl }) {
|
||||
const base = normalizeBaseUrl(staticPublicBaseUrl || process.env.STATIC_SOURCE_PUBLIC_BASE_URL || "https://infrafabric.io");
|
||||
|
||||
const sid = String(shareId || "").trim();
|
||||
const traceId = String(job?.id || "").trim();
|
||||
|
||||
const sourceExt = String(job?.sourcePath ? path.extname(job.sourcePath) : "").toLowerCase() || ".pdf";
|
||||
const sourceUrl = job?.sourceSha256 ? `${base}/static/source/${job.sourceSha256}${sourceExt}` : "";
|
||||
|
||||
return {
|
||||
share_id: sid,
|
||||
trace_id: traceId,
|
||||
created_at: job?.createdAt || "",
|
||||
style: job?.style || "",
|
||||
source_sha256: job?.sourceSha256 || "",
|
||||
output_sha256: job?.outputSha256 || "",
|
||||
urls: {
|
||||
pack_md: `${base}/static/pack/${encodeURIComponent(sid)}.md`,
|
||||
pack_html: `${base}/static/pack/${encodeURIComponent(sid)}`,
|
||||
review_html: `${base}/static/review/${encodeURIComponent(sid)}`,
|
||||
marketing_html: `${base}/static/marketing/${encodeURIComponent(sid)}`,
|
||||
dossier_html: `${base}/static/dossier/${encodeURIComponent(sid)}`,
|
||||
dossier_md: `${base}/static/dossier/${encodeURIComponent(sid)}/download`,
|
||||
trace_html: `${base}/static/trace/${encodeURIComponent(sid)}`,
|
||||
source_pdf: sourceUrl,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function summarizeTttRecord(record) {
|
||||
const rec = record && typeof record === "object" ? record : null;
|
||||
if (!rec) return null;
|
||||
const pqStatus = String(rec.pq_status || "").trim();
|
||||
const pqAlgo = String(rec.pq_algo || "").trim();
|
||||
const signaturePqPresent = Boolean(rec.signature_pq);
|
||||
return {
|
||||
id: String(rec.id || "").trim(),
|
||||
content_hash: String(rec.content_hash || "").trim(),
|
||||
signer: String(rec.signer || "").trim(),
|
||||
pq_status: pqStatus,
|
||||
pq_algo: pqAlgo,
|
||||
pq_signature_present: signaturePqPresent,
|
||||
quantum_ready: signaturePqPresent && pqStatus !== "classical-only",
|
||||
};
|
||||
}
|
||||
|
||||
async function fetchTttRecordById(recordId) {
|
||||
const base = tttRegistryBaseUrl();
|
||||
if (!base || !recordId) return { ok: false, status: 0, record: null };
|
||||
const u = `${base}/v1/citation?id=${encodeURIComponent(recordId)}`;
|
||||
try {
|
||||
const { ok, status, data } = await fetchJson(u, { method: "GET" });
|
||||
const verified = Boolean(data && data.verified === true);
|
||||
const record = verified && data && data.record ? data.record : null;
|
||||
return { ok: ok && verified, status, record };
|
||||
} catch {
|
||||
return { ok: false, status: 0, record: null };
|
||||
}
|
||||
}
|
||||
|
||||
async function upsertTttTraceReceipt({ job, shareId, staticPublicBaseUrl }) {
|
||||
const base = tttRegistryBaseUrl();
|
||||
if (!base) return { ok: false, status: 0, record: null, mode: "disabled" };
|
||||
|
||||
const rid = traceReceiptRecordId(job?.id);
|
||||
if (!rid) return { ok: false, status: 0, record: null, mode: "invalid_trace_id" };
|
||||
|
||||
// Best effort: read-only GET first (no token required).
|
||||
const existing = await fetchTttRecordById(rid);
|
||||
if (existing.ok && existing.record) return { ok: true, status: 200, record: existing.record, mode: "found" };
|
||||
|
||||
const token = tttRegistryApiToken();
|
||||
if (!token) return { ok: false, status: 0, record: null, mode: "no_token" };
|
||||
|
||||
const evidence = buildTraceReceiptEvidence({ job, shareId, staticPublicBaseUrl });
|
||||
const claim = `IF.TTT trace receipt for shareId=${shareId} trace_id=${job.id}`;
|
||||
const payload = {
|
||||
id: rid,
|
||||
claim,
|
||||
evidence,
|
||||
timestamp: job?.createdAt || undefined,
|
||||
};
|
||||
|
||||
const url = `${base}/v1/citation`;
|
||||
try {
|
||||
const { ok, status, data } = await fetchJson(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
timeoutMs: 6500,
|
||||
});
|
||||
if (!ok || !data || !data.record) return { ok: false, status, record: null, mode: "create_failed" };
|
||||
return { ok: true, status, record: data.record, mode: "created" };
|
||||
} catch {
|
||||
return { ok: false, status: 0, record: null, mode: "create_failed" };
|
||||
}
|
||||
}
|
||||
|
||||
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 || ""));
|
||||
}
|
||||
|
|
@ -1046,11 +1242,27 @@ function main() {
|
|||
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);
|
||||
|
||||
// Best-effort: attach a registry-signed trace receipt record so we can render
|
||||
// black/white “QUANTUM READY” without over-claiming. If the registry is
|
||||
// unreachable, we still render the classic hash receipt.
|
||||
let tttTraceReceipt = job.tttTraceReceipt || null;
|
||||
if (!tttTraceReceipt || !tttTraceReceipt.id || !tttTraceReceipt.content_hash) {
|
||||
const ttt = await upsertTttTraceReceipt({ job, shareId, staticPublicBaseUrl });
|
||||
if (ttt.ok && ttt.record) {
|
||||
tttTraceReceipt = summarizeTttRecord(ttt.record);
|
||||
if (tttTraceReceipt) {
|
||||
job.tttTraceReceipt = tttTraceReceipt;
|
||||
writeJob(jobsDir, job);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const verification = await computeVerificationStatus({ job, projectRoot, outputsDir, uploadsDir });
|
||||
const jobForRender = { ...job, _verification: verification, _ttt_trace_receipt: tttTraceReceipt };
|
||||
|
||||
const md = renderTraceMarkdown({ shareId, job: jobForRender, publicBaseUrl, staticPublicBaseUrl });
|
||||
const html = markdown.render(md);
|
||||
|
||||
|
|
@ -1059,7 +1271,10 @@ function main() {
|
|||
`<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">Review pack (HTML)</a>`,
|
||||
`<a href="/r/${encodeURIComponent(shareId)}/review-pack.md">Review pack (MD)</a>`,
|
||||
`<a href="/r/${encodeURIComponent(shareId)}/pack">Single-file pack (HTML)</a>`,
|
||||
`<a href="/r/${encodeURIComponent(shareId)}/pack.md">Single-file pack (MD)</a>`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" · ");
|
||||
|
|
@ -1083,6 +1298,9 @@ function main() {
|
|||
res.redirect(302, staticFile.urlPath);
|
||||
});
|
||||
|
||||
// NOTE: These routes intentionally come in pairs:
|
||||
// - `*.md` is the raw, download-friendly artifact
|
||||
// - same path without `.md` is the HTML view (for HTML-only sandboxes)
|
||||
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");
|
||||
|
|
@ -1106,6 +1324,33 @@ function main() {
|
|||
.send(md);
|
||||
});
|
||||
|
||||
app.get("/r/:shareId/review-pack", (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 html = markdown.render(md);
|
||||
const topLinks = [
|
||||
`<a href="/r/${encodeURIComponent(shareId)}">Back to dossier</a>`,
|
||||
`<a href="/r/${encodeURIComponent(shareId)}/review-pack.md">Raw Markdown</a>`,
|
||||
`<a href="/r/${encodeURIComponent(shareId)}/pack">Single-file pack (HTML)</a>`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" · ");
|
||||
res.status(200).type("text/html; charset=utf-8").send(renderMarkdownPage({ title: "Review pack", html, topLinksHtml: topLinks }));
|
||||
});
|
||||
|
||||
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");
|
||||
|
|
@ -1139,6 +1384,46 @@ function main() {
|
|||
res.status(200).type("text/markdown; charset=utf-8").send(md);
|
||||
});
|
||||
|
||||
app.get("/r/:shareId/pack", (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,
|
||||
});
|
||||
const html = markdown.render(md);
|
||||
const topLinks = [
|
||||
`<a href="/r/${encodeURIComponent(shareId)}">Back to dossier</a>`,
|
||||
`<a href="/r/${encodeURIComponent(shareId)}/pack.md">Raw Markdown</a>`,
|
||||
`<a href="/r/${encodeURIComponent(shareId)}/review-pack">Review pack (HTML)</a>`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" · ");
|
||||
res.status(200).type("text/html; charset=utf-8").send(renderMarkdownPage({ title: "Single-file pack", html, topLinksHtml: topLinks }));
|
||||
});
|
||||
|
||||
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");
|
||||
|
|
@ -1169,6 +1454,43 @@ function main() {
|
|||
res.status(200).type("text/markdown; charset=utf-8").send(md);
|
||||
});
|
||||
|
||||
app.get("/r/:shareId/marketing", (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,
|
||||
});
|
||||
const html = markdown.render(md);
|
||||
const topLinks = [
|
||||
`<a href="/r/${encodeURIComponent(shareId)}">Back to dossier</a>`,
|
||||
`<a href="/r/${encodeURIComponent(shareId)}/marketing.md">Raw Markdown</a>`,
|
||||
`<a href="/r/${encodeURIComponent(shareId)}/pack">Single-file pack (HTML)</a>`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" · ");
|
||||
res.status(200).type("text/html; charset=utf-8").send(renderMarkdownPage({ title: "Marketing excerpt", html, topLinksHtml: topLinks }));
|
||||
});
|
||||
|
||||
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");
|
||||
|
|
@ -1189,7 +1511,11 @@ function main() {
|
|||
`<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">Review pack (HTML)</a>`,
|
||||
`<a href="/r/${encodeURIComponent(shareId)}/review-pack.md">Review pack (MD)</a>`,
|
||||
`<a href="/r/${encodeURIComponent(shareId)}/pack">Single-file pack (HTML)</a>`,
|
||||
`<a href="/r/${encodeURIComponent(shareId)}/pack.md">Single-file pack (MD)</a>`,
|
||||
`<a href="/r/${encodeURIComponent(shareId)}/marketing">Marketing excerpt (HTML)</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>` : "",
|
||||
]
|
||||
|
|
@ -1286,6 +1612,20 @@ function main() {
|
|||
job.warnings = warnings ? warnings.trim() : "";
|
||||
job.outputSha256 = await sha256File(absOutputPath);
|
||||
job.status = job.warnings ? "done_with_warnings" : "done";
|
||||
|
||||
// Best-effort: create a registry-signed trace receipt record (PQ-capable).
|
||||
// This must never block publishing; failures degrade gracefully.
|
||||
try {
|
||||
const staticPublicBaseUrl = normalizeBaseUrl(process.env.STATIC_SOURCE_PUBLIC_BASE_URL || "https://infrafabric.io");
|
||||
const ttt = await upsertTttTraceReceipt({ job, shareId, staticPublicBaseUrl });
|
||||
if (ttt.ok && ttt.record) {
|
||||
const summary = summarizeTttRecord(ttt.record);
|
||||
if (summary) job.tttTraceReceipt = summary;
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
writeJob(jobsDir, job);
|
||||
} catch (e) {
|
||||
job.status = "error";
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -130,14 +130,32 @@ def _revoice_preflight(*, style: str, md_path: Path, source_path: Path) -> str:
|
|||
|
||||
|
||||
def _extract_first_claim(md: str) -> str:
|
||||
claims: list[str] = []
|
||||
for line in md.splitlines():
|
||||
m = re.match(r"^- The source claims: [“\"](?P<q>.+?)[”\"]\s*$", line.strip())
|
||||
if m:
|
||||
claim = m.group("q").strip()
|
||||
if len(claim) > 160:
|
||||
return claim[:157].rstrip() + "…"
|
||||
return claim
|
||||
return ""
|
||||
claims.append(m.group("q").strip())
|
||||
|
||||
if not claims:
|
||||
return ""
|
||||
|
||||
def is_low_signal(claim: str) -> bool:
|
||||
c = (claim or "").strip()
|
||||
lower = c.lower()
|
||||
if "datasheet" in lower:
|
||||
return True
|
||||
if "all rights reserved" in lower or "copyright" in lower:
|
||||
return True
|
||||
# Very short fragments tend to be headers/footers or OCR junk.
|
||||
if len(c) < 40:
|
||||
return True
|
||||
return False
|
||||
|
||||
# Prefer the first non-noise claim; fall back to the first claim if all are noisy.
|
||||
chosen = next((c for c in claims if not is_low_signal(c)), claims[0])
|
||||
if len(chosen) > 160:
|
||||
return chosen[:157].rstrip() + "…"
|
||||
return chosen
|
||||
|
||||
|
||||
def _extract_first_dave_factor(md: str) -> str:
|
||||
|
|
@ -177,7 +195,8 @@ def _write_marketing(
|
|||
day_upper = day.day.upper()
|
||||
next_label = f"{next_day.day.upper()} — {next_day.edition} {next_day.hashtag}" if next_day else "Next week: new drops."
|
||||
dave_factor = _extract_first_dave_factor(dossier_md) or "The control drifts into a status update, and the status update becomes the control."
|
||||
claim = _extract_first_claim(dossier_md) or "(no short claim extracted)"
|
||||
claim = _extract_first_claim(dossier_md)
|
||||
quote_bullet = f"- The source claims: “{claim}”" if claim else "- (Add one short measurable source quote from the dossier’s Claims Register.)"
|
||||
|
||||
lines = [
|
||||
f"# Thread Pack — {day_upper} ({day.edition} Edition)",
|
||||
|
|
@ -207,7 +226,7 @@ def _write_marketing(
|
|||
"",
|
||||
"## Post 3 — The Source Claims (quote-budget)",
|
||||
"",
|
||||
f"- The source claims: “{claim}”",
|
||||
quote_bullet,
|
||||
"",
|
||||
"## Post 4 — Sponsor Bumper (mid-thread)",
|
||||
"",
|
||||
|
|
@ -602,14 +621,14 @@ def main() -> int:
|
|||
)
|
||||
|
||||
# Week index + full pack.
|
||||
m = re.search(r"(v\\d+(?:\\.\\d+)*)", args.style)
|
||||
m = re.search(r"(v\d+(?:\.\d+)*)", args.style)
|
||||
week_title = m.group(1) if m else args.style
|
||||
index_path = build_dir / "index.md"
|
||||
_write_week_index(out_path=index_path, week_title=week_title, base_url=args.base_url, days=ordered, source_links=source_links)
|
||||
|
||||
week_pack_path = build_dir / "week.pack.md"
|
||||
body_parts = [
|
||||
"# InfraFabric External Review Pack — Full Week (v1.9)",
|
||||
f"# InfraFabric External Review Pack — Full Week ({week_title})",
|
||||
"",
|
||||
"This file embeds all daily packs for sandboxed review environments. Review one day at a time.",
|
||||
"",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue