import { mkdir, readFile, writeFile } from "node:fs/promises"; import { basename, dirname, join } from "node:path"; import { execFileSync } from "node:child_process"; import { performance } from "node:perf_hooks"; import os from "node:os"; import { parseJpwabc } from "../dist/domain/jpwabc.js"; import { decodeProject, encodeProject } from "../dist/domain/project-codec.js"; import { modelFromParse, createEmptyDocument, createNoteEvent, rational, modelEquivalent } from "../dist/domain/score-model.js"; import { compilePlaybackTimeline } from "../dist/playback/playback.js"; import { DEFAULT_PAPER_CONFIG, layoutDocument, layoutSummary } from "../dist/layout/layout-engine.js"; import { createDefaultFontMeasurementProvider } from "../dist/layout/font-measure.js"; import { exportSnapshotSvg, buildPrintableHtml, outputGeometry } from "../dist/output/output.js"; import { probeSvg, renderSnapshotSvg } from "../dist/layout/svg-renderer.js"; const ROOT = "acceptance/m9"; const manifest = JSON.parse(await readFile("fixtures/manifest.json", "utf8")); const environment = { generatedAt: new Date().toISOString(), node: process.version, platform: `${process.platform} ${os.release()} ${os.arch()}`, cpu: os.cpus()[0]?.model ?? "unknown", memoryBytes: os.totalmem(), chrome: (() => { try { return execFileSync("google-chrome", ["--version"], { encoding: "utf8" }).trim(); } catch { return "not available"; } })(), firefox: (() => { try { return execFileSync("firefox", ["--version"], { encoding: "utf8" }).trim(); } catch { return "not available"; } })(), project: "jianpu-web-editor", buildCommand: "npm run build", testCommand: "npm test", note: "Node-side local evidence; browser, original JPW7, printer and external resource claims are listed separately and are not inferred." }; for (const directory of [ROOT, `${ROOT}/projects`, `${ROOT}/svg`, `${ROOT}/png`, `${ROOT}/pdf`, `${ROOT}/screenshots`, `${ROOT}/native-roundtrip`]) await mkdir(directory, { recursive: true }); await writeFile(`${ROOT}/environment.json`, `${JSON.stringify(environment, null, 2)}\n`); const importRows = []; const layoutRows = []; const playbackRows = []; const exportRows = []; const caseRows = []; const fontProvider = createDefaultFontMeasurementProvider(); let firstSnapshot = null; let maxPages = 0; let preservedCount = 0; let reopenCount = 0; let svgCount = 0; let blockedExportCount = 0; for (const [index, fixture] of manifest.samples.entries()) { const bytes = await readFile(fixture.rawPath); const parsed = parseJpwabc(new Uint8Array(bytes)); const model = modelFromParse(parsed, { fileName: basename(fixture.rawPath) }); const projectText = encodeProject(model); const reopened = decodeProject(projectText); const reopenedEquivalent = modelEquivalent(model, reopened); if (reopenedEquivalent) reopenCount += 1; if (model.source?.rawBase64) preservedCount += 1; const snapshot = await layoutDocument(model, DEFAULT_PAPER_CONFIG, fontProvider); if (!firstSnapshot) firstSnapshot = snapshot; const summary = layoutSummary(snapshot); maxPages = Math.max(maxPages, summary.pageCount); const timeline = compilePlaybackTimeline(model); const safeName = `${String(index + 1).padStart(2, "0")}-${fixture.id.replace(/[^\p{L}\p{N}._-]+/gu, "_")}`; await writeFile(`${ROOT}/projects/${safeName}.jianpu.json`, projectText); const svgs = exportSnapshotSvg(snapshot); for (const [pageIndex, svg] of svgs.entries()) { await writeFile(`${ROOT}/svg/${safeName}-page-${pageIndex + 1}.svg`, svg); svgCount += 1; } const probe = svgs[0] ? probeSvg(svgs[0]) : null; importRows.push({ fixtureId: fixture.id, fixtureSha256: fixture.rawSha256, rawPath: fixture.rawPath, parsedStatus: parsed.compatibility.status, encoding: parsed.source.encoding, sections: parsed.sections.length, diagnostics: parsed.diagnostics.length, unknownTokens: parsed.unknownTokens.length, unknownBlocks: parsed.unknownBlocks.length, sourcePreserved: Boolean(model.source?.rawBase64), result: "pass-local" }); layoutRows.push({ fixtureId: fixture.id, fixtureSha256: fixture.rawSha256, summary, deterministicKey: snapshot.deterministicKey, svgProbe: probe, warnings: snapshot.warnings.length, result: "pass-local" }); playbackRows.push({ fixtureId: fixture.id, fixtureSha256: fixture.rawSha256, voiceCount: timeline.voiceIds.length, eventCount: timeline.events.length, durationTicks: timeline.durationTicks, warnings: timeline.warnings, controlEventCount: timeline.controlEvents?.length ?? 0, result: "pass-local-semantic" }); const exportStatus = model.attachments.length || model.pageObjects.length || model.unknownBlocks.length || model.voices.some((voice) => voice.temporary) ? "blocked-by-safety-preflight" : "core-preflight-evaluated"; if (exportStatus === "blocked-by-safety-preflight") blockedExportCount += 1; exportRows.push({ fixtureId: fixture.id, fixtureSha256: fixture.rawSha256, svgPages: svgs.length, outputGeometry: outputGeometry(snapshot), svgIndependentArtifact: `${ROOT}/svg/${safeName}-page-1.svg`, printableHtmlBytes: buildPrintableHtml(snapshot).length, png: "not-executed-in-node; representative Chromium PNG remains in acceptance/m5", pdf: "not-executed-in-node; representative Chromium PDF remains in acceptance/m5", compatibilityPreflight: exportStatus, result: "pass-local-svg-and-engineering-export" }); caseRows.push({ caseId: `M9-33-${String(index + 1).padStart(2, "0")}`, fixtureId: fixture.id, fixtureSha256: fixture.rawSha256, environment: "node-local", steps: "import -> model -> project save/reopen -> fixed layout -> timeline -> SVG", expected: "source retained, complete project round-trip, deterministic A4 SVG and bounded playback diagnostics", actual: { reopenedEquivalent, pages: summary.pageCount, lines: summary.lineCount, measures: summary.measureCount, symbols: summary.symbolCount, playbackEvents: timeline.events.length }, result: reopenedEquivalent && svgs.length > 0 ? "pass-local" : "failed", evidence: [`${ROOT}/projects/${safeName}.jianpu.json`, `${ROOT}/svg/${safeName}-page-1.svg`] }); } const malicious = `JPW-ABC File Ver 7.30\n.Voice\n 1\n`; const maliciousModel = modelFromParse(parseJpwabc(new TextEncoder().encode(malicious)), { fileName: "malicious-input.jpwabc" }); const maliciousSnapshot = await layoutDocument(maliciousModel, DEFAULT_PAPER_CONFIG, fontProvider); const maliciousSvg = renderSnapshotSvg(maliciousSnapshot)[0]; const unsafeTag = /<[^>]*(?:onload|onclick|javascript:)[^>]*>/i.test(maliciousSvg); const securityRow = { caseId: "ACC-Q-008", input: "malicious source text", expected: "data only; no executable SVG/HTML attributes or external load", actual: { unsafeTag, escapedText: maliciousSvg.includes("<svg") }, result: !unsafeTag && maliciousSvg.includes("<svg") ? "pass-local" : "failed", evidence: `${ROOT}/security-probe.json` }; await writeFile(`${ROOT}/security-probe.json`, `${JSON.stringify(securityRow, null, 2)}\n`); caseRows.push(securityRow); const performanceSamples = []; const performanceFixture = manifest.samples[0]; const performanceBytes = await readFile(performanceFixture.rawPath); for (let run = 0; run < 20; run += 1) { const started = performance.now(); const parsed = parseJpwabc(new Uint8Array(performanceBytes)); const model = modelFromParse(parsed); const snapshot = await layoutDocument(model, DEFAULT_PAPER_CONFIG, fontProvider); performanceSamples.push({ run: run + 1, milliseconds: performance.now() - started, pages: snapshot.pages.length }); } const sortedTimes = performanceSamples.map((sample) => sample.milliseconds).sort((a, b) => a - b); const p95 = sortedTimes[Math.min(sortedTimes.length - 1, Math.ceil(sortedTimes.length * 0.95) - 1)]; const longDocument = createEmptyDocument("m9-10-page-synthetic"); const longMeasures = []; for (let index = 0; index < 120; index += 1) longMeasures.push({ id: `m9-measure-${index}`, ordinal: index, beats: rational(4), events: [1, 2, 3, 4].map((degree, eventIndex) => createNoteEvent(`m9-event-${index}-${eventIndex}`, degree, rational(1))) }); longDocument.voices.push({ id: "m9-voice", name: "性能合成", sectionId: null, visible: true, measures: longMeasures }); const longStarted = performance.now(); const longSnapshot = await layoutDocument(longDocument, DEFAULT_PAPER_CONFIG, fontProvider); const longMilliseconds = performance.now() - longStarted; await writeFile(`${ROOT}/performance.json`, `${JSON.stringify({ method: "Node local approximation; does not replace 20 cold browser runs, 100-edit P95, 10-minute audio or 30-minute resource-release tests", firstFixture: performanceFixture.id, firstDisplayRuns: performanceSamples, p95Milliseconds: p95, tenPageSynthetic: { pages: longSnapshot.pages.length, milliseconds: longMilliseconds }, targets: { firstDisplayP95Milliseconds: 2000, editP95Milliseconds: 100, tenMinutePlaybackDriftMilliseconds: 20 }, status: "local-measured; browser/device portions not-executed" }, null, 2)}\n`); await writeFile(`${ROOT}/import-report.jsonl`, `${importRows.map((row) => JSON.stringify(row)).join("\n")}\n`); await writeFile(`${ROOT}/layout-report.jsonl`, `${layoutRows.map((row) => JSON.stringify(row)).join("\n")}\n`); await writeFile(`${ROOT}/playback-timeline.jsonl`, `${playbackRows.map((row) => JSON.stringify(row)).join("\n")}\n`); await writeFile(`${ROOT}/export-report.jsonl`, `${exportRows.map((row) => JSON.stringify(row)).join("\n")}\n`); await writeFile(`${ROOT}/case-results.jsonl`, `${caseRows.map((row) => JSON.stringify(row)).join("\n")}\n`); let previousNativeRows = new Map(); try { const previousNativeText = await readFile(`${ROOT}/native-roundtrip-report.jsonl`, "utf8"); previousNativeRows = new Map(previousNativeText.split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line)).filter((row) => row.result !== "not-executed").map((row) => [row.fixtureId, row])); } catch { // A fresh M9 run has no external native evidence to preserve. } const nativeRows = manifest.samples.map((fixture) => previousNativeRows.get(fixture.id) ?? ({ caseId: "ACC-X-012", fixtureId: fixture.id, fixtureSha256: fixture.rawSha256, result: "not-executed", reason: "JPW7 v7.30 original program/Windows replay is not available in this Linux Web-only run; Web round-trip is not substituted.", evidence: `${ROOT}/native-roundtrip-report.jsonl` })); await writeFile(`${ROOT}/native-roundtrip-report.jsonl`, `${nativeRows.map((row) => JSON.stringify(row)).join("\n")}\n`); let previousExternal = {}; try { previousExternal = JSON.parse(await readFile(`${ROOT}/summary.json`, "utf8")).external ?? {}; } catch { // A fresh M9 run has no previous external summary. } const nativeCount = nativeRows.filter((row) => row.result !== "not-executed").length; const nativeStatus = nativeCount === 0 ? "not-executed" : nativeCount === manifest.samples.length ? `pass-${nativeCount}-of-${manifest.samples.length}` : `partial-${nativeCount}-of-${manifest.samples.length}`; await writeFile(`${ROOT}/summary.json`, `${JSON.stringify({ sampleCount: manifest.samples.length, preservedCount, reopenCount, svgCount, maxPages, blockedExportCount, localCases: caseRows.length, security: securityRow.result, external: { nativeRoundtrip: nativeStatus, edge: "not-executed", firefox: "not-executed", printer: "not-executed", tenMinuteAudio: "not-executed", ...previousExternal, nativeRoundtrip: nativeStatus } }, null, 2)}\n`); console.log(JSON.stringify({ samples: manifest.samples.length, preservedCount, reopenCount, svgCount, maxPages, blockedExportCount, p95Milliseconds: p95, longDocumentPages: longSnapshot.pages.length }));