diff --git a/README.md b/README.md
index d338cf68..13689437 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,4 @@
# deepevents.ai
deepevents.ai main codebase
+
+- [Collaborative LaTeX macro safety guard](collaborative-latex-macro-safety-guard/README.md)
diff --git a/collaborative-latex-macro-safety-guard/README.md b/collaborative-latex-macro-safety-guard/README.md
new file mode 100644
index 00000000..44c63258
--- /dev/null
+++ b/collaborative-latex-macro-safety-guard/README.md
@@ -0,0 +1,48 @@
+# Collaborative LaTeX Macro Safety Guard
+
+This module is a self-contained real-time collaborative research editor slice for SCIBASE.AI issue #12. It validates synchronized Markdown and LaTeX editor packets before live preview or named-version export.
+
+The guard uses synthetic data only. It does not connect to live users, private manuscripts, browser sessions, credentials, or external services.
+
+## Scope
+
+- Detect conflicting collaborator macro definitions before broadcast.
+- Block unsafe macro commands such as raw HTML helpers, external links, and image includes unless a trusted render packet is explicitly reviewed.
+- Detect recursive macro expansion cycles that can hang live preview or export rendering.
+- Block raw active HTML and unapproved external render resources inside Markdown/LaTeX blocks.
+- Hold locked sections when unapproved or external-resource macro changes affect the render packet.
+- Emit deterministic remediation actions for live preview, trusted KaTeX rendering, and named-version export gates.
+
+## Requirement Mapping
+
+| Issue #12 area | Implementation |
+| --- | --- |
+| Rich scientific formatting | Macro, equation, Markdown, and external render-resource checks |
+| Real-time collaboration | Multi-user macro conflict and shared render packet validation |
+| Locking/unlock modes | Locked section render holds for risky macro packets |
+| Version history and autosave | Named-version export decisions and reviewer remediation packets |
+
+## Files
+
+- `index.js` - dependency-free evaluator plus Markdown/SVG report renderers.
+- `sample-data.js` - synthetic ready, blocked, and needs-review editor packets.
+- `test.js` - Node assertion coverage for guard decisions and report renderers.
+- `demo.js` - writes deterministic JSON, Markdown, and SVG reviewer artifacts.
+- `scripts/render-demo-video.js` - optional ffmpeg-based MP4 renderer.
+- `reports/` - generated reviewer artifacts.
+
+## Validation
+
+```bash
+npm run check
+npm test
+npm run demo
+```
+
+Optional video render when ffmpeg is available:
+
+```bash
+npm run demo:video
+```
+
+The included demo artifacts are deterministic and based only on the synthetic fixtures in `sample-data.js`.
diff --git a/collaborative-latex-macro-safety-guard/demo.js b/collaborative-latex-macro-safety-guard/demo.js
new file mode 100644
index 00000000..d6b4fda9
--- /dev/null
+++ b/collaborative-latex-macro-safety-guard/demo.js
@@ -0,0 +1,27 @@
+const fs = require("node:fs");
+const path = require("node:path");
+const {
+ createGuardReport,
+ renderMarkdown,
+ renderSvg,
+} = require("./index");
+const { samplePackets } = require("./sample-data");
+
+const reportsDir = path.join(__dirname, "reports");
+fs.mkdirSync(reportsDir, { recursive: true });
+
+const report = createGuardReport(samplePackets);
+const jsonPath = path.join(reportsDir, "demo.json");
+const markdownPath = path.join(reportsDir, "demo.md");
+const svgPath = path.join(reportsDir, "demo.svg");
+
+fs.writeFileSync(jsonPath, `${JSON.stringify(report, null, 2)}\n`);
+fs.writeFileSync(markdownPath, renderMarkdown(report));
+fs.writeFileSync(svgPath, renderSvg(report));
+
+console.log(`Wrote ${path.relative(process.cwd(), jsonPath)}`);
+console.log(`Wrote ${path.relative(process.cwd(), markdownPath)}`);
+console.log(`Wrote ${path.relative(process.cwd(), svgPath)}`);
+console.log(
+ `Ready=${report.totals.ready} NeedsReview=${report.totals.needsReview} Blocked=${report.totals.blocked}`,
+);
diff --git a/collaborative-latex-macro-safety-guard/index.js b/collaborative-latex-macro-safety-guard/index.js
new file mode 100644
index 00000000..4f922c57
--- /dev/null
+++ b/collaborative-latex-macro-safety-guard/index.js
@@ -0,0 +1,447 @@
+const DEFAULT_POLICY = Object.freeze({
+ generatedAt: "2026-05-22T00:00:00.000Z",
+ allowedExternalHosts: ["doi.org", "api.datacite.org"],
+ unsafeMacroCommands: [
+ "\\href",
+ "\\includegraphics",
+ "\\htmlClass",
+ "\\htmlId",
+ "\\htmlStyle",
+ "\\url",
+ ],
+ rawHtmlPattern: /<\s*(script|iframe|object|embed|link|meta|style)\b/i,
+ externalUrlPattern: /https?:\/\/([^)\s"']+)/gi,
+ maxExpansionDepth: 8,
+});
+
+const REQUIREMENT_MAP = Object.freeze({
+ scientific_formatting:
+ "Rich Scientific Formatting: Markdown, LaTeX, equations, and cross references",
+ realtime_collaboration:
+ "Real-Time Collaboration: multi-user edits with live render safety",
+ locking:
+ "Real-Time Collaboration: controlled sections and lock/unlock modes",
+ version_history:
+ "Version History & Autosave: safe named-version export",
+});
+
+function evaluateMacroPacket(packet, policy = DEFAULT_POLICY) {
+ assertPacket(packet);
+
+ const checks = [];
+ const issues = [];
+ const addCheck = (code, passed, detail) => {
+ checks.push({ code, passed, detail });
+ };
+ const addIssue = (
+ severity,
+ code,
+ message,
+ requirement,
+ remediation,
+ target,
+ ) => {
+ issues.push({
+ severity,
+ code,
+ message,
+ requirement,
+ remediation,
+ target,
+ });
+ };
+
+ const macroGroups = groupMacros(packet.macros);
+ checkMacroConflicts(macroGroups, addCheck, addIssue);
+ checkUnsafeMacroCommands(packet, policy, addCheck, addIssue);
+ checkMacroRecursion(packet, policy, addCheck, addIssue);
+ checkBlockContent(packet, policy, addCheck, addIssue);
+ checkLockedSection(packet, addCheck, addIssue);
+
+ const blockingIssues = issues.filter((issue) => issue.severity === "error");
+ const warnings = issues.filter((issue) => issue.severity === "warning");
+ const status =
+ blockingIssues.length > 0
+ ? "blocked"
+ : warnings.length > 0
+ ? "needs_review"
+ : "ready";
+
+ return {
+ generatedAt: policy.generatedAt,
+ packetId: packet.packetId,
+ documentId: packet.documentId,
+ namedVersion: packet.namedVersion,
+ sectionId: packet.sectionId,
+ status,
+ summary: {
+ macros: packet.macros.length,
+ blocks: packet.blocks.length,
+ checks: checks.length,
+ passedChecks: checks.filter((check) => check.passed).length,
+ blockingIssues: blockingIssues.length,
+ warnings: warnings.length,
+ },
+ checks,
+ issues: issues.sort(compareIssues),
+ renderDecision: buildRenderDecision(status, issues),
+ requirementCoverage: REQUIREMENT_MAP,
+ };
+}
+
+function createGuardReport(packets, policy = DEFAULT_POLICY) {
+ const evaluations = packets
+ .map((packet) => evaluateMacroPacket(packet, policy))
+ .sort((a, b) => a.packetId.localeCompare(b.packetId));
+
+ return {
+ generatedAt: policy.generatedAt,
+ guard: "collaborative-latex-macro-safety-guard",
+ issue: "SCIBASE-AI/SCIBASE.AI#12",
+ totals: {
+ packets: evaluations.length,
+ ready: evaluations.filter((item) => item.status === "ready").length,
+ needsReview: evaluations.filter((item) => item.status === "needs_review")
+ .length,
+ blocked: evaluations.filter((item) => item.status === "blocked").length,
+ blockingIssues: evaluations.reduce(
+ (sum, item) => sum + item.summary.blockingIssues,
+ 0,
+ ),
+ warnings: evaluations.reduce((sum, item) => sum + item.summary.warnings, 0),
+ },
+ evaluations,
+ };
+}
+
+function renderMarkdown(report) {
+ const rows = report.evaluations
+ .map(
+ (item) =>
+ `| ${item.packetId} | ${item.namedVersion} | ${item.status} | ${item.summary.blockingIssues} | ${item.summary.warnings} |`,
+ )
+ .join("\n");
+ const issueRows =
+ report.evaluations
+ .flatMap((item) =>
+ item.issues.map(
+ (issue) =>
+ `- ${item.packetId} ${issue.code} (${issue.target}): ${issue.remediation}`,
+ ),
+ )
+ .join("\n") || "- No remediation required.";
+
+ return `# Collaborative LaTeX Macro Safety Guard
+
+Synthetic reviewer packet for ${report.issue}.
+
+## Summary
+
+| Packet | Named version | Status | Blocking issues | Warnings |
+| --- | --- | --- | ---: | ---: |
+${rows}
+
+## Gate Totals
+
+- Ready packets: ${report.totals.ready}
+- Needs review: ${report.totals.needsReview}
+- Blocked packets: ${report.totals.blocked}
+- Blocking issues: ${report.totals.blockingIssues}
+- Warnings: ${report.totals.warnings}
+
+## Remediation Queue
+
+${issueRows}
+
+## Requirement Mapping
+
+- Macro and equation safety: ${REQUIREMENT_MAP.scientific_formatting}
+- Multi-user macro edit checks: ${REQUIREMENT_MAP.realtime_collaboration}
+- Locked section render holds: ${REQUIREMENT_MAP.locking}
+- Named-version export gate: ${REQUIREMENT_MAP.version_history}
+`;
+}
+
+function renderSvg(report) {
+ const width = 960;
+ const height = 540;
+ const cardWidth = 280;
+ const colors = {
+ ready: "#157f57",
+ needs_review: "#b7791f",
+ blocked: "#b42318",
+ };
+ const cards = report.evaluations
+ .map((item, index) => {
+ const x = 40 + index * (cardWidth + 20);
+ const color = colors[item.status];
+ return `
+
+
+ ${escapeXml(item.packetId)}
+ ${escapeXml(item.sectionId)}
+ ${escapeXml(item.status)}
+ Checks: ${item.summary.passedChecks}/${item.summary.checks}
+ Blocks: ${item.summary.blockingIssues}
+`;
+ })
+ .join("\n");
+
+ return `
+`;
+}
+
+function checkMacroConflicts(macroGroups, addCheck, addIssue) {
+ for (const [name, macros] of [...macroGroups.entries()].sort()) {
+ const bodies = new Set(macros.map((macro) => macro.body));
+ const passed = bodies.size === 1;
+ addCheck("MACRO_CONFLICT", passed, `${name}; definitions=${bodies.size}`);
+ if (!passed) {
+ addIssue(
+ "error",
+ "MACRO_DEFINITION_CONFLICT",
+ "Collaborators submitted conflicting bodies for the same macro.",
+ REQUIREMENT_MAP.realtime_collaboration,
+ "Resolve the macro conflict before broadcasting the shared render state.",
+ name,
+ );
+ }
+ }
+}
+
+function checkUnsafeMacroCommands(packet, policy, addCheck, addIssue) {
+ for (const macro of packet.macros) {
+ const used = policy.unsafeMacroCommands.filter((command) =>
+ macro.body.includes(command),
+ );
+ const trusted = packet.trustedRenderMode && Boolean(macro.trustJustification);
+ const passed = used.length === 0 || trusted;
+ addCheck(
+ "MACRO_TRUST_COMMANDS",
+ passed,
+ `${macro.name}; commands=${used.join(",") || "none"}`,
+ );
+ if (used.length > 0) {
+ addIssue(
+ trusted ? "warning" : "error",
+ trusted ? "TRUSTED_MACRO_REVIEW_REQUIRED" : "UNSAFE_MACRO_COMMAND",
+ "Macro body uses a command that can escape plain equation rendering.",
+ REQUIREMENT_MAP.scientific_formatting,
+ trusted
+ ? "Keep the trusted-render justification with reviewer signoff."
+ : "Remove the unsafe command or move it behind an approved trusted-render packet.",
+ macro.name,
+ );
+ }
+
+ if (!macro.approved) {
+ addIssue(
+ "error",
+ "UNAPPROVED_MACRO_EDIT",
+ "Macro edit has not been approved for the shared named version.",
+ REQUIREMENT_MAP.version_history,
+ "Require approval before autosave promotes this macro into the named version.",
+ macro.name,
+ );
+ }
+ }
+}
+
+function checkMacroRecursion(packet, policy, addCheck, addIssue) {
+ const macroMap = new Map(packet.macros.map((macro) => [macro.name, macro]));
+ for (const macro of packet.macros) {
+ const cycle = findMacroCycle(macro, macroMap, policy.maxExpansionDepth);
+ const passed = cycle.length === 0;
+ addCheck("MACRO_RECURSION", passed, `${macro.name}; cycle=${cycle.join(" -> ") || "none"}`);
+ if (!passed) {
+ addIssue(
+ "error",
+ "RECURSIVE_MACRO_EXPANSION",
+ "Macro expansion forms a recursive cycle.",
+ REQUIREMENT_MAP.scientific_formatting,
+ "Break the macro cycle before live preview or export rendering.",
+ macro.name,
+ );
+ }
+ }
+}
+
+function checkBlockContent(packet, policy, addCheck, addIssue) {
+ for (const block of packet.blocks) {
+ const rawHtml = policy.rawHtmlPattern.test(block.text);
+ addCheck("RAW_HTML_BLOCK", !rawHtml, `${block.blockId}; rawHtml=${rawHtml}`);
+ if (rawHtml) {
+ addIssue(
+ "error",
+ "RAW_HTML_IN_COLLAB_BLOCK",
+ "Collaborative block contains raw active HTML.",
+ REQUIREMENT_MAP.scientific_formatting,
+ "Strip active HTML before live preview or named-version export.",
+ block.blockId,
+ );
+ }
+
+ const externalHosts = findExternalHosts(block.text, policy);
+ const disallowed = externalHosts.filter(
+ (host) => !policy.allowedExternalHosts.includes(host),
+ );
+ addCheck(
+ "EXTERNAL_RENDER_RESOURCE",
+ disallowed.length === 0,
+ `${block.blockId}; hosts=${externalHosts.join(",") || "none"}`,
+ );
+ if (disallowed.length > 0) {
+ addIssue(
+ "error",
+ "EXTERNAL_RENDER_RESOURCE_BLOCKED",
+ "Collaborative render block references an unapproved external resource.",
+ REQUIREMENT_MAP.scientific_formatting,
+ "Replace external resources with repository-managed figure or citation assets.",
+ block.blockId,
+ );
+ }
+ }
+}
+
+function checkLockedSection(packet, addCheck, addIssue) {
+ const lockedBlocks = packet.blocks.filter((block) => block.locked);
+ const hasRenderRisk = packet.macros.some(
+ (macro) => !macro.approved || macro.body.includes("\\href"),
+ );
+ const passed = lockedBlocks.length === 0 || !hasRenderRisk;
+ addCheck(
+ "LOCKED_SECTION_RENDER_HOLD",
+ passed,
+ `lockedBlocks=${lockedBlocks.length}; renderRisk=${hasRenderRisk}`,
+ );
+ if (!passed) {
+ addIssue(
+ "error",
+ "LOCKED_SECTION_RENDER_HOLD",
+ "Locked section contains unapproved or external-resource macro changes.",
+ REQUIREMENT_MAP.locking,
+ "Hold render/export until the section owner approves the macro packet.",
+ packet.sectionId,
+ );
+ }
+}
+
+function buildRenderDecision(status, issues) {
+ const blocking = issues.filter((issue) => issue.severity === "error");
+ const warnings = issues.filter((issue) => issue.severity === "warning");
+ return {
+ allowLivePreview: blocking.length === 0,
+ allowNamedVersionExport: blocking.length === 0,
+ allowTrustedKatexRender: blocking.length === 0 && warnings.length === 0,
+ requiresReviewerSignoff: warnings.length > 0,
+ remediationChecklist: issues.map((issue) => ({
+ code: issue.code,
+ target: issue.target,
+ action: issue.remediation,
+ })),
+ status,
+ };
+}
+
+function groupMacros(macros) {
+ const groups = new Map();
+ for (const macro of macros) {
+ if (!groups.has(macro.name)) {
+ groups.set(macro.name, []);
+ }
+ groups.get(macro.name).push(macro);
+ }
+ return groups;
+}
+
+function findMacroCycle(startMacro, macroMap, maxDepth) {
+ const visited = [];
+ let current = startMacro;
+ for (let depth = 0; depth <= maxDepth; depth += 1) {
+ visited.push(current.name);
+ const nextName = [...macroMap.keys()].find((name) =>
+ current.body.includes(name),
+ );
+ if (!nextName) {
+ return [];
+ }
+ if (visited.includes(nextName)) {
+ return [...visited, nextName];
+ }
+ current = macroMap.get(nextName);
+ }
+ return visited;
+}
+
+function findExternalHosts(text, policy) {
+ const hosts = [];
+ let match = policy.externalUrlPattern.exec(text);
+ while (match) {
+ hosts.push(match[1].toLowerCase());
+ match = policy.externalUrlPattern.exec(text);
+ }
+ policy.externalUrlPattern.lastIndex = 0;
+ return [...new Set(hosts)].sort();
+}
+
+function assertPacket(packet) {
+ if (!packet || typeof packet !== "object") {
+ throw new TypeError("packet must be an object");
+ }
+ for (const field of ["packetId", "documentId", "namedVersion", "sectionId"]) {
+ if (!packet[field]) {
+ throw new TypeError(`packet.${field} is required`);
+ }
+ }
+ if (!Array.isArray(packet.macros)) {
+ throw new TypeError("packet.macros must be an array");
+ }
+ if (!Array.isArray(packet.blocks)) {
+ throw new TypeError("packet.blocks must be an array");
+ }
+}
+
+function compareIssues(a, b) {
+ return (
+ severityRank(a.severity) - severityRank(b.severity) ||
+ a.code.localeCompare(b.code) ||
+ a.target.localeCompare(b.target)
+ );
+}
+
+function severityRank(severity) {
+ return severity === "error" ? 0 : 1;
+}
+
+function escapeXml(value) {
+ return String(value)
+ .replaceAll("&", "&")
+ .replaceAll("<", "<")
+ .replaceAll(">", ">")
+ .replaceAll('"', """);
+}
+
+module.exports = {
+ DEFAULT_POLICY,
+ REQUIREMENT_MAP,
+ createGuardReport,
+ evaluateMacroPacket,
+ renderMarkdown,
+ renderSvg,
+};
diff --git a/collaborative-latex-macro-safety-guard/package.json b/collaborative-latex-macro-safety-guard/package.json
new file mode 100644
index 00000000..85bdf58e
--- /dev/null
+++ b/collaborative-latex-macro-safety-guard/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "collaborative-latex-macro-safety-guard",
+ "version": "1.0.0",
+ "private": true,
+ "type": "commonjs",
+ "description": "Synthetic collaborative LaTeX macro safety guard for SCIBASE real-time editor packets.",
+ "scripts": {
+ "check": "node --check index.js && node --check sample-data.js && node --check demo.js && node --check test.js && node --check scripts/render-demo-video.js",
+ "test": "node test.js",
+ "demo": "node demo.js",
+ "demo:video": "node scripts/render-demo-video.js"
+ }
+}
diff --git a/collaborative-latex-macro-safety-guard/reports/demo.json b/collaborative-latex-macro-safety-guard/reports/demo.json
new file mode 100644
index 00000000..92bc4a82
--- /dev/null
+++ b/collaborative-latex-macro-safety-guard/reports/demo.json
@@ -0,0 +1,451 @@
+{
+ "generatedAt": "2026-05-22T00:00:00.000Z",
+ "guard": "collaborative-latex-macro-safety-guard",
+ "issue": "SCIBASE-AI/SCIBASE.AI#12",
+ "totals": {
+ "packets": 3,
+ "ready": 1,
+ "needsReview": 1,
+ "blocked": 1,
+ "blockingIssues": 10,
+ "warnings": 1
+ },
+ "evaluations": [
+ {
+ "generatedAt": "2026-05-22T00:00:00.000Z",
+ "packetId": "packet-safe-methods-v4",
+ "documentId": "shared-manuscript/neuro-methods",
+ "namedVersion": "Manuscript v4 Methods",
+ "sectionId": "methods-statistical-model",
+ "status": "ready",
+ "summary": {
+ "macros": 2,
+ "blocks": 2,
+ "checks": 11,
+ "passedChecks": 11,
+ "blockingIssues": 0,
+ "warnings": 0
+ },
+ "checks": [
+ {
+ "code": "MACRO_CONFLICT",
+ "passed": true,
+ "detail": "\\ci; definitions=1"
+ },
+ {
+ "code": "MACRO_CONFLICT",
+ "passed": true,
+ "detail": "\\mean; definitions=1"
+ },
+ {
+ "code": "MACRO_TRUST_COMMANDS",
+ "passed": true,
+ "detail": "\\mean; commands=none"
+ },
+ {
+ "code": "MACRO_TRUST_COMMANDS",
+ "passed": true,
+ "detail": "\\ci; commands=none"
+ },
+ {
+ "code": "MACRO_RECURSION",
+ "passed": true,
+ "detail": "\\mean; cycle=none"
+ },
+ {
+ "code": "MACRO_RECURSION",
+ "passed": true,
+ "detail": "\\ci; cycle=none"
+ },
+ {
+ "code": "RAW_HTML_BLOCK",
+ "passed": true,
+ "detail": "eq-primary-outcome; rawHtml=false"
+ },
+ {
+ "code": "EXTERNAL_RENDER_RESOURCE",
+ "passed": true,
+ "detail": "eq-primary-outcome; hosts=none"
+ },
+ {
+ "code": "RAW_HTML_BLOCK",
+ "passed": true,
+ "detail": "md-summary; rawHtml=false"
+ },
+ {
+ "code": "EXTERNAL_RENDER_RESOURCE",
+ "passed": true,
+ "detail": "md-summary; hosts=none"
+ },
+ {
+ "code": "LOCKED_SECTION_RENDER_HOLD",
+ "passed": true,
+ "detail": "lockedBlocks=0; renderRisk=false"
+ }
+ ],
+ "issues": [],
+ "renderDecision": {
+ "allowLivePreview": true,
+ "allowNamedVersionExport": true,
+ "allowTrustedKatexRender": true,
+ "requiresReviewerSignoff": false,
+ "remediationChecklist": [],
+ "status": "ready"
+ },
+ "requirementCoverage": {
+ "scientific_formatting": "Rich Scientific Formatting: Markdown, LaTeX, equations, and cross references",
+ "realtime_collaboration": "Real-Time Collaboration: multi-user edits with live render safety",
+ "locking": "Real-Time Collaboration: controlled sections and lock/unlock modes",
+ "version_history": "Version History & Autosave: safe named-version export"
+ }
+ },
+ {
+ "generatedAt": "2026-05-22T00:00:00.000Z",
+ "packetId": "packet-trusted-supplement-v2",
+ "documentId": "shared-manuscript/supplement",
+ "namedVersion": "Supplement Draft",
+ "sectionId": "appendix-derivations",
+ "status": "needs_review",
+ "summary": {
+ "macros": 1,
+ "blocks": 1,
+ "checks": 6,
+ "passedChecks": 6,
+ "blockingIssues": 0,
+ "warnings": 1
+ },
+ "checks": [
+ {
+ "code": "MACRO_CONFLICT",
+ "passed": true,
+ "detail": "\\annotate; definitions=1"
+ },
+ {
+ "code": "MACRO_TRUST_COMMANDS",
+ "passed": true,
+ "detail": "\\annotate; commands=\\htmlId"
+ },
+ {
+ "code": "MACRO_RECURSION",
+ "passed": true,
+ "detail": "\\annotate; cycle=none"
+ },
+ {
+ "code": "RAW_HTML_BLOCK",
+ "passed": true,
+ "detail": "appendix-equation; rawHtml=false"
+ },
+ {
+ "code": "EXTERNAL_RENDER_RESOURCE",
+ "passed": true,
+ "detail": "appendix-equation; hosts=none"
+ },
+ {
+ "code": "LOCKED_SECTION_RENDER_HOLD",
+ "passed": true,
+ "detail": "lockedBlocks=0; renderRisk=false"
+ }
+ ],
+ "issues": [
+ {
+ "severity": "warning",
+ "code": "TRUSTED_MACRO_REVIEW_REQUIRED",
+ "message": "Macro body uses a command that can escape plain equation rendering.",
+ "requirement": "Rich Scientific Formatting: Markdown, LaTeX, equations, and cross references",
+ "remediation": "Keep the trusted-render justification with reviewer signoff.",
+ "target": "\\annotate"
+ }
+ ],
+ "renderDecision": {
+ "allowLivePreview": true,
+ "allowNamedVersionExport": true,
+ "allowTrustedKatexRender": false,
+ "requiresReviewerSignoff": true,
+ "remediationChecklist": [
+ {
+ "code": "TRUSTED_MACRO_REVIEW_REQUIRED",
+ "target": "\\annotate",
+ "action": "Keep the trusted-render justification with reviewer signoff."
+ }
+ ],
+ "status": "needs_review"
+ },
+ "requirementCoverage": {
+ "scientific_formatting": "Rich Scientific Formatting: Markdown, LaTeX, equations, and cross references",
+ "realtime_collaboration": "Real-Time Collaboration: multi-user edits with live render safety",
+ "locking": "Real-Time Collaboration: controlled sections and lock/unlock modes",
+ "version_history": "Version History & Autosave: safe named-version export"
+ }
+ },
+ {
+ "generatedAt": "2026-05-22T00:00:00.000Z",
+ "packetId": "packet-unsafe-results-v8",
+ "documentId": "shared-manuscript/results-live",
+ "namedVersion": "Final Submission",
+ "sectionId": "results-figures",
+ "status": "blocked",
+ "summary": {
+ "macros": 5,
+ "blocks": 3,
+ "checks": 21,
+ "passedChecks": 12,
+ "blockingIssues": 10,
+ "warnings": 0
+ },
+ "checks": [
+ {
+ "code": "MACRO_CONFLICT",
+ "passed": true,
+ "detail": "\\dataset; definitions=1"
+ },
+ {
+ "code": "MACRO_CONFLICT",
+ "passed": false,
+ "detail": "\\hazard; definitions=2"
+ },
+ {
+ "code": "MACRO_CONFLICT",
+ "passed": true,
+ "detail": "\\loopA; definitions=1"
+ },
+ {
+ "code": "MACRO_CONFLICT",
+ "passed": true,
+ "detail": "\\loopB; definitions=1"
+ },
+ {
+ "code": "MACRO_TRUST_COMMANDS",
+ "passed": false,
+ "detail": "\\dataset; commands=\\href"
+ },
+ {
+ "code": "MACRO_TRUST_COMMANDS",
+ "passed": true,
+ "detail": "\\loopA; commands=none"
+ },
+ {
+ "code": "MACRO_TRUST_COMMANDS",
+ "passed": true,
+ "detail": "\\loopB; commands=none"
+ },
+ {
+ "code": "MACRO_TRUST_COMMANDS",
+ "passed": false,
+ "detail": "\\hazard; commands=\\htmlClass"
+ },
+ {
+ "code": "MACRO_TRUST_COMMANDS",
+ "passed": true,
+ "detail": "\\hazard; commands=none"
+ },
+ {
+ "code": "MACRO_RECURSION",
+ "passed": true,
+ "detail": "\\dataset; cycle=none"
+ },
+ {
+ "code": "MACRO_RECURSION",
+ "passed": false,
+ "detail": "\\loopA; cycle=\\loopA -> \\loopB -> \\loopA"
+ },
+ {
+ "code": "MACRO_RECURSION",
+ "passed": false,
+ "detail": "\\loopB; cycle=\\loopB -> \\loopA -> \\loopB"
+ },
+ {
+ "code": "MACRO_RECURSION",
+ "passed": true,
+ "detail": "\\hazard; cycle=none"
+ },
+ {
+ "code": "MACRO_RECURSION",
+ "passed": true,
+ "detail": "\\hazard; cycle=none"
+ },
+ {
+ "code": "RAW_HTML_BLOCK",
+ "passed": true,
+ "detail": "eq-hazard-model; rawHtml=false"
+ },
+ {
+ "code": "EXTERNAL_RENDER_RESOURCE",
+ "passed": true,
+ "detail": "eq-hazard-model; hosts=none"
+ },
+ {
+ "code": "RAW_HTML_BLOCK",
+ "passed": true,
+ "detail": "figure-caption; rawHtml=false"
+ },
+ {
+ "code": "EXTERNAL_RENDER_RESOURCE",
+ "passed": false,
+ "detail": "figure-caption; hosts=private.example.invalid/figure.png"
+ },
+ {
+ "code": "RAW_HTML_BLOCK",
+ "passed": false,
+ "detail": "unsafe-inline-html; rawHtml=true"
+ },
+ {
+ "code": "EXTERNAL_RENDER_RESOURCE",
+ "passed": false,
+ "detail": "unsafe-inline-html; hosts=private.example.invalid/embed"
+ },
+ {
+ "code": "LOCKED_SECTION_RENDER_HOLD",
+ "passed": false,
+ "detail": "lockedBlocks=2; renderRisk=true"
+ }
+ ],
+ "issues": [
+ {
+ "severity": "error",
+ "code": "EXTERNAL_RENDER_RESOURCE_BLOCKED",
+ "message": "Collaborative render block references an unapproved external resource.",
+ "requirement": "Rich Scientific Formatting: Markdown, LaTeX, equations, and cross references",
+ "remediation": "Replace external resources with repository-managed figure or citation assets.",
+ "target": "figure-caption"
+ },
+ {
+ "severity": "error",
+ "code": "EXTERNAL_RENDER_RESOURCE_BLOCKED",
+ "message": "Collaborative render block references an unapproved external resource.",
+ "requirement": "Rich Scientific Formatting: Markdown, LaTeX, equations, and cross references",
+ "remediation": "Replace external resources with repository-managed figure or citation assets.",
+ "target": "unsafe-inline-html"
+ },
+ {
+ "severity": "error",
+ "code": "LOCKED_SECTION_RENDER_HOLD",
+ "message": "Locked section contains unapproved or external-resource macro changes.",
+ "requirement": "Real-Time Collaboration: controlled sections and lock/unlock modes",
+ "remediation": "Hold render/export until the section owner approves the macro packet.",
+ "target": "results-figures"
+ },
+ {
+ "severity": "error",
+ "code": "MACRO_DEFINITION_CONFLICT",
+ "message": "Collaborators submitted conflicting bodies for the same macro.",
+ "requirement": "Real-Time Collaboration: multi-user edits with live render safety",
+ "remediation": "Resolve the macro conflict before broadcasting the shared render state.",
+ "target": "\\hazard"
+ },
+ {
+ "severity": "error",
+ "code": "RAW_HTML_IN_COLLAB_BLOCK",
+ "message": "Collaborative block contains raw active HTML.",
+ "requirement": "Rich Scientific Formatting: Markdown, LaTeX, equations, and cross references",
+ "remediation": "Strip active HTML before live preview or named-version export.",
+ "target": "unsafe-inline-html"
+ },
+ {
+ "severity": "error",
+ "code": "RECURSIVE_MACRO_EXPANSION",
+ "message": "Macro expansion forms a recursive cycle.",
+ "requirement": "Rich Scientific Formatting: Markdown, LaTeX, equations, and cross references",
+ "remediation": "Break the macro cycle before live preview or export rendering.",
+ "target": "\\loopA"
+ },
+ {
+ "severity": "error",
+ "code": "RECURSIVE_MACRO_EXPANSION",
+ "message": "Macro expansion forms a recursive cycle.",
+ "requirement": "Rich Scientific Formatting: Markdown, LaTeX, equations, and cross references",
+ "remediation": "Break the macro cycle before live preview or export rendering.",
+ "target": "\\loopB"
+ },
+ {
+ "severity": "error",
+ "code": "UNAPPROVED_MACRO_EDIT",
+ "message": "Macro edit has not been approved for the shared named version.",
+ "requirement": "Version History & Autosave: safe named-version export",
+ "remediation": "Require approval before autosave promotes this macro into the named version.",
+ "target": "\\dataset"
+ },
+ {
+ "severity": "error",
+ "code": "UNSAFE_MACRO_COMMAND",
+ "message": "Macro body uses a command that can escape plain equation rendering.",
+ "requirement": "Rich Scientific Formatting: Markdown, LaTeX, equations, and cross references",
+ "remediation": "Remove the unsafe command or move it behind an approved trusted-render packet.",
+ "target": "\\dataset"
+ },
+ {
+ "severity": "error",
+ "code": "UNSAFE_MACRO_COMMAND",
+ "message": "Macro body uses a command that can escape plain equation rendering.",
+ "requirement": "Rich Scientific Formatting: Markdown, LaTeX, equations, and cross references",
+ "remediation": "Remove the unsafe command or move it behind an approved trusted-render packet.",
+ "target": "\\hazard"
+ }
+ ],
+ "renderDecision": {
+ "allowLivePreview": false,
+ "allowNamedVersionExport": false,
+ "allowTrustedKatexRender": false,
+ "requiresReviewerSignoff": false,
+ "remediationChecklist": [
+ {
+ "code": "EXTERNAL_RENDER_RESOURCE_BLOCKED",
+ "target": "figure-caption",
+ "action": "Replace external resources with repository-managed figure or citation assets."
+ },
+ {
+ "code": "EXTERNAL_RENDER_RESOURCE_BLOCKED",
+ "target": "unsafe-inline-html",
+ "action": "Replace external resources with repository-managed figure or citation assets."
+ },
+ {
+ "code": "LOCKED_SECTION_RENDER_HOLD",
+ "target": "results-figures",
+ "action": "Hold render/export until the section owner approves the macro packet."
+ },
+ {
+ "code": "MACRO_DEFINITION_CONFLICT",
+ "target": "\\hazard",
+ "action": "Resolve the macro conflict before broadcasting the shared render state."
+ },
+ {
+ "code": "RAW_HTML_IN_COLLAB_BLOCK",
+ "target": "unsafe-inline-html",
+ "action": "Strip active HTML before live preview or named-version export."
+ },
+ {
+ "code": "RECURSIVE_MACRO_EXPANSION",
+ "target": "\\loopA",
+ "action": "Break the macro cycle before live preview or export rendering."
+ },
+ {
+ "code": "RECURSIVE_MACRO_EXPANSION",
+ "target": "\\loopB",
+ "action": "Break the macro cycle before live preview or export rendering."
+ },
+ {
+ "code": "UNAPPROVED_MACRO_EDIT",
+ "target": "\\dataset",
+ "action": "Require approval before autosave promotes this macro into the named version."
+ },
+ {
+ "code": "UNSAFE_MACRO_COMMAND",
+ "target": "\\dataset",
+ "action": "Remove the unsafe command or move it behind an approved trusted-render packet."
+ },
+ {
+ "code": "UNSAFE_MACRO_COMMAND",
+ "target": "\\hazard",
+ "action": "Remove the unsafe command or move it behind an approved trusted-render packet."
+ }
+ ],
+ "status": "blocked"
+ },
+ "requirementCoverage": {
+ "scientific_formatting": "Rich Scientific Formatting: Markdown, LaTeX, equations, and cross references",
+ "realtime_collaboration": "Real-Time Collaboration: multi-user edits with live render safety",
+ "locking": "Real-Time Collaboration: controlled sections and lock/unlock modes",
+ "version_history": "Version History & Autosave: safe named-version export"
+ }
+ }
+ ]
+}
diff --git a/collaborative-latex-macro-safety-guard/reports/demo.md b/collaborative-latex-macro-safety-guard/reports/demo.md
new file mode 100644
index 00000000..543689b8
--- /dev/null
+++ b/collaborative-latex-macro-safety-guard/reports/demo.md
@@ -0,0 +1,40 @@
+# Collaborative LaTeX Macro Safety Guard
+
+Synthetic reviewer packet for SCIBASE-AI/SCIBASE.AI#12.
+
+## Summary
+
+| Packet | Named version | Status | Blocking issues | Warnings |
+| --- | --- | --- | ---: | ---: |
+| packet-safe-methods-v4 | Manuscript v4 Methods | ready | 0 | 0 |
+| packet-trusted-supplement-v2 | Supplement Draft | needs_review | 0 | 1 |
+| packet-unsafe-results-v8 | Final Submission | blocked | 10 | 0 |
+
+## Gate Totals
+
+- Ready packets: 1
+- Needs review: 1
+- Blocked packets: 1
+- Blocking issues: 10
+- Warnings: 1
+
+## Remediation Queue
+
+- packet-trusted-supplement-v2 TRUSTED_MACRO_REVIEW_REQUIRED (\annotate): Keep the trusted-render justification with reviewer signoff.
+- packet-unsafe-results-v8 EXTERNAL_RENDER_RESOURCE_BLOCKED (figure-caption): Replace external resources with repository-managed figure or citation assets.
+- packet-unsafe-results-v8 EXTERNAL_RENDER_RESOURCE_BLOCKED (unsafe-inline-html): Replace external resources with repository-managed figure or citation assets.
+- packet-unsafe-results-v8 LOCKED_SECTION_RENDER_HOLD (results-figures): Hold render/export until the section owner approves the macro packet.
+- packet-unsafe-results-v8 MACRO_DEFINITION_CONFLICT (\hazard): Resolve the macro conflict before broadcasting the shared render state.
+- packet-unsafe-results-v8 RAW_HTML_IN_COLLAB_BLOCK (unsafe-inline-html): Strip active HTML before live preview or named-version export.
+- packet-unsafe-results-v8 RECURSIVE_MACRO_EXPANSION (\loopA): Break the macro cycle before live preview or export rendering.
+- packet-unsafe-results-v8 RECURSIVE_MACRO_EXPANSION (\loopB): Break the macro cycle before live preview or export rendering.
+- packet-unsafe-results-v8 UNAPPROVED_MACRO_EDIT (\dataset): Require approval before autosave promotes this macro into the named version.
+- packet-unsafe-results-v8 UNSAFE_MACRO_COMMAND (\dataset): Remove the unsafe command or move it behind an approved trusted-render packet.
+- packet-unsafe-results-v8 UNSAFE_MACRO_COMMAND (\hazard): Remove the unsafe command or move it behind an approved trusted-render packet.
+
+## Requirement Mapping
+
+- Macro and equation safety: Rich Scientific Formatting: Markdown, LaTeX, equations, and cross references
+- Multi-user macro edit checks: Real-Time Collaboration: multi-user edits with live render safety
+- Locked section render holds: Real-Time Collaboration: controlled sections and lock/unlock modes
+- Named-version export gate: Version History & Autosave: safe named-version export
diff --git a/collaborative-latex-macro-safety-guard/reports/demo.mp4 b/collaborative-latex-macro-safety-guard/reports/demo.mp4
new file mode 100644
index 00000000..a60a5e91
Binary files /dev/null and b/collaborative-latex-macro-safety-guard/reports/demo.mp4 differ
diff --git a/collaborative-latex-macro-safety-guard/reports/demo.svg b/collaborative-latex-macro-safety-guard/reports/demo.svg
new file mode 100644
index 00000000..969d7c49
--- /dev/null
+++ b/collaborative-latex-macro-safety-guard/reports/demo.svg
@@ -0,0 +1,43 @@
+
diff --git a/collaborative-latex-macro-safety-guard/sample-data.js b/collaborative-latex-macro-safety-guard/sample-data.js
new file mode 100644
index 00000000..69c385a6
--- /dev/null
+++ b/collaborative-latex-macro-safety-guard/sample-data.js
@@ -0,0 +1,135 @@
+const safePacket = {
+ packetId: "packet-safe-methods-v4",
+ documentId: "shared-manuscript/neuro-methods",
+ namedVersion: "Manuscript v4 Methods",
+ sectionId: "methods-statistical-model",
+ collaborators: ["orcid:0000-0002-1825-0097", "orcid:0000-0003-1555-4212"],
+ trustedRenderMode: false,
+ macros: [
+ {
+ name: "\\mean",
+ body: "\\operatorname{mean}",
+ author: "orcid:0000-0002-1825-0097",
+ scope: "section",
+ approved: true,
+ },
+ {
+ name: "\\ci",
+ body: "\\mathrm{CI}",
+ author: "orcid:0000-0003-1555-4212",
+ scope: "document",
+ approved: true,
+ },
+ ],
+ blocks: [
+ {
+ blockId: "eq-primary-outcome",
+ type: "latex",
+ text: "The primary endpoint is $\\mean(X) \\pm 95\\%\\ \\ci$.",
+ locked: false,
+ },
+ {
+ blockId: "md-summary",
+ type: "markdown",
+ text: "The same model is rendered in the methods preview.",
+ locked: false,
+ },
+ ],
+};
+
+const blockedPacket = {
+ packetId: "packet-unsafe-results-v8",
+ documentId: "shared-manuscript/results-live",
+ namedVersion: "Final Submission",
+ sectionId: "results-figures",
+ collaborators: ["orcid:0000-0001-1111-2222", "orcid:0000-0001-3333-4444"],
+ trustedRenderMode: false,
+ macros: [
+ {
+ name: "\\dataset",
+ body: "\\href{https://private.example.invalid/raw.csv}{dataset}",
+ author: "orcid:0000-0001-1111-2222",
+ scope: "document",
+ approved: false,
+ },
+ {
+ name: "\\loopA",
+ body: "\\loopB",
+ author: "orcid:0000-0001-3333-4444",
+ scope: "section",
+ approved: true,
+ },
+ {
+ name: "\\loopB",
+ body: "\\loopA",
+ author: "orcid:0000-0001-3333-4444",
+ scope: "section",
+ approved: true,
+ },
+ {
+ name: "\\hazard",
+ body: "\\htmlClass{warning}{hazard}",
+ author: "orcid:0000-0001-1111-2222",
+ scope: "section",
+ approved: true,
+ },
+ {
+ name: "\\hazard",
+ body: "\\textbf{hazard}",
+ author: "orcid:0000-0001-3333-4444",
+ scope: "section",
+ approved: true,
+ },
+ ],
+ blocks: [
+ {
+ blockId: "eq-hazard-model",
+ type: "latex",
+ text: "$\\hazard = \\loopA + \\dataset$",
+ locked: true,
+ },
+ {
+ blockId: "figure-caption",
+ type: "markdown",
+ text: "",
+ locked: true,
+ },
+ {
+ blockId: "unsafe-inline-html",
+ type: "markdown",
+ text: "",
+ locked: false,
+ },
+ ],
+};
+
+const reviewPacket = {
+ packetId: "packet-trusted-supplement-v2",
+ documentId: "shared-manuscript/supplement",
+ namedVersion: "Supplement Draft",
+ sectionId: "appendix-derivations",
+ collaborators: ["orcid:0000-0002-9999-7777"],
+ trustedRenderMode: true,
+ macros: [
+ {
+ name: "\\annotate",
+ body: "\\htmlId{derivation-note}{note}",
+ author: "orcid:0000-0002-9999-7777",
+ scope: "section",
+ approved: true,
+ trustJustification: "Maintainer-approved KaTeX trust macro for internal appendix anchor.",
+ },
+ ],
+ blocks: [
+ {
+ blockId: "appendix-equation",
+ type: "latex",
+ text: "$\\annotate = \\sum_i x_i$",
+ locked: false,
+ },
+ ],
+};
+
+module.exports = {
+ samplePackets: [safePacket, blockedPacket, reviewPacket],
+};
diff --git a/collaborative-latex-macro-safety-guard/scripts/render-demo-video.js b/collaborative-latex-macro-safety-guard/scripts/render-demo-video.js
new file mode 100644
index 00000000..8325dacf
--- /dev/null
+++ b/collaborative-latex-macro-safety-guard/scripts/render-demo-video.js
@@ -0,0 +1,85 @@
+const fs = require("node:fs");
+const path = require("node:path");
+const { spawnSync } = require("node:child_process");
+
+const reportsDir = path.join(__dirname, "..", "reports");
+const reportPath = path.join(reportsDir, "demo.json");
+const outputPath = path.join(reportsDir, "demo.mp4");
+
+if (!fs.existsSync(reportPath)) {
+ throw new Error("Run npm run demo before rendering the video.");
+}
+
+const report = JSON.parse(fs.readFileSync(reportPath, "utf8"));
+const ffmpeg = process.env.FFMPEG_PATH || "ffmpeg";
+const title = "SCIBASE LaTeX macro safety guard";
+const subtitle = `ready ${report.totals.ready} review ${report.totals.needsReview} blocked ${report.totals.blocked}`;
+const detail = `blocking issues ${report.totals.blockingIssues} warnings ${report.totals.warnings}`;
+const fontArg = getFontArg();
+const filters = [
+ "drawbox=x=0:y=0:w=iw:h=ih:color=0x111827@1:t=fill",
+ "drawbox=x=72:y=86:w=1136:h=548:color=0xffffff@0.94:t=fill",
+ "drawbox=x=72:y=86:w=1136:h=18:color=0xb7791f@1:t=fill",
+ drawText(title, 112, 160, 42, "0x111827", fontArg),
+ drawText("Real-time collaborative research editor", 112, 220, 24, "0x374151", fontArg),
+ drawText(subtitle, 112, 305, 34, "0x111827", fontArg),
+ drawText(detail, 112, 365, 26, "0xb42318", fontArg),
+ drawText(
+ "Validates synced Markdown and LaTeX packets before live preview or named-version export.",
+ 112,
+ 450,
+ 22,
+ "0x374151",
+ fontArg,
+ ),
+ drawText("Synthetic data only. No credentials, users, or external services.", 112, 500, 22, "0x374151", fontArg),
+].join(",");
+
+const result = spawnSync(
+ ffmpeg,
+ [
+ "-y",
+ "-f",
+ "lavfi",
+ "-i",
+ "color=c=0x111827:s=1280x720:d=4:r=25",
+ "-vf",
+ filters,
+ "-pix_fmt",
+ "yuv420p",
+ outputPath,
+ ],
+ { stdio: "inherit" },
+);
+
+if (result.status !== 0) {
+ throw new Error(`ffmpeg exited with status ${result.status}`);
+}
+
+console.log(`Wrote ${path.relative(process.cwd(), outputPath)}`);
+
+function drawText(text, x, y, size, color, fontArg) {
+ return `drawtext=${fontArg}:text='${escapeDrawtext(text)}':x=${x}:y=${y}:fontsize=${size}:fontcolor=${color}`;
+}
+
+function escapeDrawtext(value) {
+ return String(value)
+ .replaceAll("\\", "\\\\")
+ .replaceAll(":", "\\:")
+ .replaceAll("'", "\\'");
+}
+
+function getFontArg() {
+ const candidates = [
+ "C:/Windows/Fonts/arial.ttf",
+ "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
+ "/System/Library/Fonts/Supplemental/Arial.ttf",
+ ];
+ const font = candidates.find((candidate) => fs.existsSync(candidate));
+ if (!font) {
+ throw new Error(
+ `No supported TrueType font file found. Checked: ${candidates.join(", ")}`,
+ );
+ }
+ return `fontfile='${font.replaceAll(":", "\\:")}'`;
+}
diff --git a/collaborative-latex-macro-safety-guard/test.js b/collaborative-latex-macro-safety-guard/test.js
new file mode 100644
index 00000000..4ef742e8
--- /dev/null
+++ b/collaborative-latex-macro-safety-guard/test.js
@@ -0,0 +1,74 @@
+const assert = require("node:assert/strict");
+const {
+ createGuardReport,
+ evaluateMacroPacket,
+ renderMarkdown,
+ renderSvg,
+} = require("./index");
+const { samplePackets } = require("./sample-data");
+
+const ready = evaluateMacroPacket(samplePackets[0]);
+assert.equal(ready.status, "ready");
+assert.equal(ready.renderDecision.allowLivePreview, true);
+assert.equal(ready.renderDecision.allowNamedVersionExport, true);
+
+const blocked = evaluateMacroPacket(samplePackets[1]);
+assert.equal(blocked.status, "blocked");
+assert.equal(blocked.renderDecision.allowLivePreview, false);
+assert.ok(
+ blocked.issues.some((issue) => issue.code === "UNSAFE_MACRO_COMMAND"),
+);
+assert.ok(
+ blocked.issues.some((issue) => issue.code === "RECURSIVE_MACRO_EXPANSION"),
+);
+assert.ok(
+ blocked.issues.some((issue) => issue.code === "MACRO_DEFINITION_CONFLICT"),
+);
+assert.ok(
+ blocked.issues.some((issue) => issue.code === "RAW_HTML_IN_COLLAB_BLOCK"),
+);
+assert.ok(
+ blocked.issues.some(
+ (issue) => issue.code === "EXTERNAL_RENDER_RESOURCE_BLOCKED",
+ ),
+);
+assert.ok(
+ blocked.issues.some((issue) => issue.code === "LOCKED_SECTION_RENDER_HOLD"),
+);
+
+const review = evaluateMacroPacket(samplePackets[2]);
+assert.equal(review.status, "needs_review");
+assert.equal(review.renderDecision.allowLivePreview, true);
+assert.equal(review.renderDecision.allowTrustedKatexRender, false);
+assert.ok(
+ review.issues.some(
+ (issue) => issue.code === "TRUSTED_MACRO_REVIEW_REQUIRED",
+ ),
+);
+
+const report = createGuardReport(samplePackets);
+assert.deepEqual(report.totals, {
+ packets: 3,
+ ready: 1,
+ needsReview: 1,
+ blocked: 1,
+ blockingIssues: 10,
+ warnings: 1,
+});
+
+const markdown = renderMarkdown(report);
+assert.match(markdown, /Collaborative LaTeX Macro Safety Guard/);
+assert.match(markdown, /RECURSIVE_MACRO_EXPANSION/);
+assert.match(markdown, /Named-version export gate/);
+
+const svg = renderSvg(report);
+assert.match(svg, /