Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,25 @@ se-trace validate-runtime examples/legal-summarization.cots.json

# Walk the evidence chains and surface recursive (model-generated) items (RFC-0002)
se-trace recursive examples/legal-summarization.cots.json

# Render the Context Graph (Mermaid + HTML, RFC-0004)
se-trace context-graph examples/legal-summarization.cots.json -o ./out

# Replay the audit trail (RFC-0005)
se-trace audit examples/legal-summarization.cots.json

# COTS Explorer: one self-contained HTML page with all four views (Advancement Plan Phase 5)
se-trace explore examples/legal-summarization.cots.json -o ./out
```

## What is in this repository

- `src/inspect.ts` - the inspect command. Prints a human-readable summary of a COTS.
- `src/cite.ts` - the cite command. Prints the evidence chain for a specific decision.
- `src/visualize.ts` - the visualize command. Renders a COTS as a Mermaid diagram + HTML page.
- `src/context-graph.ts` - the context-graph command. Renders the Context + Evidence parts as a typed-edge graph (RFC-0004).
- `src/audit.ts` - the audit command. Replays the audit trail and flags append-only/linkage violations (RFC-0005).
- `src/explore.ts` - the COTS Explorer. One self-contained HTML page combining Reasoning Roadmap, Context Graph, summary, and audit trail.
- `src/cli/se-trace.ts` - the CLI entry point, including `validate-runtime` (RFC-0001) and `recursive` (RFC-0002 chain-walking).
- `tests/tools.test.ts` - the test suite.

Expand Down
18 changes: 18 additions & 0 deletions src/cli/se-trace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
* - visualize <file> Render a COTS as a Reasoning Roadmap (Mermaid + HTML).
* - context-graph <file> Render a COTS's Context + Evidence as a Context Graph (RFC-0004).
* - audit <file> [decision-id] Replay a decision's audit trail (RFC-0005).
* - explore <file> Render the COTS Explorer: one self-contained HTML page
* (Reasoning Roadmap + Context Graph + summary + audit trail).
*/

import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
Expand All @@ -17,6 +19,7 @@ import { cite } from "../cite.js";
import { toMermaid, toHtml } from "../visualize.js";
import { toContextGraphMermaid, toContextGraphHtml } from "../context-graph.js";
import { replayAudit } from "../audit.js";
import { toExplorerHtml } from "../explore.js";
import type { Cots } from "../cots.js";

// Lightweight conformance check (subset of the canonical test suite). For
Expand Down Expand Up @@ -212,4 +215,19 @@ program
if (report.issues.length > 0) process.exit(1);
});

program
.command("explore")
.description("Render the COTS Explorer: a single self-contained HTML page with all four views (Advancement Plan Phase 5)")
.argument("<path>", "Path to a COTS JSON file")
.option("-o, --out <dir>", "Output directory", ".")
.action((path: string, opts: { out: string }) => {
const cots = loadCots(path);
const baseName = basename(path, extname(path));
const html = toExplorerHtml(cots);
const htmlPath = resolve(opts.out, `${baseName}.explorer.html`);
mkdirSync(dirname(htmlPath), { recursive: true });
writeFileSync(htmlPath, html);
console.log(`COTS Explorer: ${htmlPath}`);
});

program.parse();
97 changes: 97 additions & 0 deletions src/explore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/**
* COTS Explorer: render a single self-contained HTML page combining the
* Reasoning Roadmap, the Context Graph, the inspect summary, and the audit
* trail of a COTS. Static output; no server, no external state.
*
* Phase 5 of the Advancement Plan: the demo-able surface on top of
* se-trace visualize / context-graph / inspect / audit.
*/

import type { Cots } from "./cots.js";
import { toMermaid } from "./visualize.js";
import { toContextGraphMermaid } from "./context-graph.js";
import { inspect } from "./inspect.js";
import { replayAudit } from "./audit.js";

const escapeHtml = (s: string): string =>
s
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");

export const toExplorerHtml = (cots: Cots): string => {
const roadmap = toMermaid(cots);
const contextGraph = toContextGraphMermaid(cots);
const summary = escapeHtml(inspect(cots));
const audit = replayAudit(cots);
const auditBlock = escapeHtml(audit.lines.join("\n"));
const auditOk = audit.issues.length === 0;

return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>COTS Explorer -- ${escapeHtml(cots.cots_id)}</title>
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; margin: 0; color: #1f2937; background: #f9fafb; }
header { background: #111827; color: #f9fafb; padding: 1rem 2rem; }
header h1 { font-size: 1.25rem; margin: 0; }
header .meta { color: #9ca3af; font-size: 0.8rem; margin-top: 0.25rem; }
nav { display: flex; gap: 0.5rem; padding: 0.75rem 2rem; background: #ffffff; border-bottom: 1px solid #e5e7eb; position: sticky; top: 0; }
nav button { border: 1px solid #d1d5db; background: #f9fafb; border-radius: 0.375rem; padding: 0.375rem 0.875rem; cursor: pointer; font-size: 0.875rem; }
nav button.active { background: #111827; color: #f9fafb; border-color: #111827; }
main { padding: 1.5rem 2rem; }
section { display: none; }
section.active { display: block; }
.card { background: #ffffff; border: 1px solid #e5e7eb; border-radius: 0.5rem; padding: 1.25rem; margin-bottom: 1rem; }
.mermaid { overflow-x: auto; }
pre { background: #f3f4f6; border-radius: 0.375rem; padding: 1rem; overflow-x: auto; font-size: 0.8rem; line-height: 1.5; white-space: pre-wrap; }
.badge { display: inline-block; padding: 0.125rem 0.5rem; border-radius: 9999px; font-size: 0.75rem; font-weight: 600; }
.badge.ok { background: #dcfce7; color: #166534; }
.badge.bad { background: #fee2e2; color: #991b1b; }
</style>
</head>
<body>
<header>
<h1>COTS Explorer</h1>
<div class="meta">COTS ${escapeHtml(cots.cots_id)} &middot; produced ${escapeHtml(cots.produced_at)} by ${escapeHtml(cots.produced_by.system_name)} v${escapeHtml(cots.produced_by.system_version)} &middot; schema ${escapeHtml(cots.cots_version)}</div>
</header>
<nav>
<button data-tab="roadmap" class="active">Reasoning Roadmap</button>
<button data-tab="context">Context Graph</button>
<button data-tab="summary">Summary</button>
<button data-tab="audit">Audit Trail ${auditOk ? '<span class="badge ok">OK</span>' : '<span class="badge bad">' + audit.issues.length + ' issues</span>'}</button>
</nav>
<main>
<section id="tab-roadmap" class="active">
<div class="card"><div class="mermaid">${escapeHtml(roadmap)}</div></div>
</section>
<section id="tab-context">
<div class="card"><div class="mermaid">${escapeHtml(contextGraph)}</div></div>
</section>
<section id="tab-summary">
<div class="card"><pre>${summary}</pre></div>
</section>
<section id="tab-audit">
<div class="card"><pre>${auditBlock}</pre></div>
</section>
</main>
<script>
mermaid.initialize({ startOnLoad: true, theme: 'default' });
const buttons = document.querySelectorAll('nav button');
buttons.forEach((b) => {
b.addEventListener('click', () => {
buttons.forEach((x) => x.classList.remove('active'));
b.classList.add('active');
document.querySelectorAll('main section').forEach((s) => s.classList.remove('active'));
document.getElementById('tab-' + b.dataset.tab).classList.add('active');
});
});
</script>
</body>
</html>
`;
};
60 changes: 60 additions & 0 deletions tests/tools.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { cite } from "../src/cite.js";
import { toMermaid, toHtml } from "../src/visualize.js";
import { toContextGraphMermaid, toContextGraphHtml } from "../src/context-graph.js";
import { replayAudit } from "../src/audit.js";
import { toExplorerHtml } from "../src/explore.js";
import type { Cots } from "../src/cots.js";

const __dirname = dirname(fileURLToPath(import.meta.url));
Expand Down Expand Up @@ -192,6 +193,65 @@ describe("audit (RFC-0005)", () => {
});
});

describe("explore (COTS Explorer)", () => {
it("renders one self-contained HTML page with all four views", () => {
const cots = loadFixture("legal-summarization.cots.json");
const html = toExplorerHtml(cots);
expect(html).toContain("<!DOCTYPE html>");
expect(html).toContain("COTS Explorer");
expect(html).toContain('data-tab="roadmap"');
expect(html).toContain('data-tab="context"');
expect(html).toContain('data-tab="summary"');
expect(html).toContain('data-tab="audit"');
expect(html).toContain("graph LR");
expect(html.match(/graph LR/g)?.length).toBe(2);
expect(html).toContain("Audit trail: OK");
expect(html).toContain("mermaid");
});

it("escapes HTML in the COTS summary", () => {
const cots = loadFixture("legal-summarization.cots.json");
cots.decisions[0].decision_text = "<script>alert(1)</script>";
const html = toExplorerHtml(cots);
expect(html).not.toContain("<script>alert(1)</script>");
expect(html).toContain("&lt;script&gt;");
});

it("shows a badge with issue count when the audit trail has violations", () => {
const cots = loadFixture("legal-summarization.cots.json");
cots.decisions[0].audit_history.push({
id: "urn:source-engineering:audit:dup",
action: "appealed",
performed_by: "r",
performed_at: "2026-11-02T09:00:00Z",
notes: "decision_id=urn:source-engineering:decision:nope",
});
const html = toExplorerHtml(cots);
expect(html).toContain('badge bad');
});

it("writes <base>.explorer.html via the CLI", async () => {
const { execFileSync } = await import("node:child_process");
const { mkdtempSync, readFileSync, rmSync } = await import("node:fs");
const { tmpdir } = await import("node:os");
const { join } = await import("node:path");
const outDir = mkdtempSync(join(tmpdir(), "se-explore-"));
try {
const fixture = resolve(__dirname, "..", "examples", "legal-summarization.cots.json");
const out = execFileSync(
"node",
[resolve(__dirname, "..", "dist", "cli", "se-trace.js"), "explore", fixture, "-o", outDir],
{ encoding: "utf-8" }
);
expect(out).toContain("explorer.html");
const html = readFileSync(join(outDir, "legal-summarization.cots.explorer.html"), "utf-8");
expect(html).toContain("COTS Explorer");
} finally {
rmSync(outDir, { recursive: true, force: true });
}
});
});

describe("validate-runtime", () => {
it("reports a Layer 2 Validation record when present", async () => {
const cots = loadFixture("legal-summarization.cots.json");
Expand Down
Loading