feat: 增加求解器验证书 Web (#7)

Co-authored-by: yige <yige@yigedeMacBook-Neo.local>
This commit is contained in:
2026-08-30 11:02:51 -04:00
committed by GitHub
parent 27a6934c63
commit af338dc13b
20 changed files with 3777 additions and 1 deletions

View File

@@ -0,0 +1,99 @@
#!/usr/bin/env swift
import AppKit
import Foundation
import Vision
enum AuditError: Error, CustomStringConvertible {
case usage
case malformedPlan(String)
case unsafePath(String)
case unreadableImage(String)
var description: String {
switch self {
case .usage:
return "usage: audit-screenshot-privacy.swift <input-root> <screenshot-plan.json> <ocr-output.json>"
case .malformedPlan(let detail):
return "malformed screenshot plan: \(detail)"
case .unsafePath(let path):
return "screenshot path escapes input root: \(path)"
case .unreadableImage(let path):
return "cannot decode screenshot: \(path)"
}
}
}
func requiredString(_ object: [String: Any], _ key: String) throws -> String {
guard let value = object[key] as? String, !value.isEmpty else {
throw AuditError.malformedPlan("missing \(key)")
}
return value
}
do {
guard CommandLine.arguments.count == 4 else { throw AuditError.usage }
let inputRoot = URL(fileURLWithPath: CommandLine.arguments[1]).standardizedFileURL
let planURL = URL(fileURLWithPath: CommandLine.arguments[2]).standardizedFileURL
let outputURL = URL(fileURLWithPath: CommandLine.arguments[3]).standardizedFileURL
let planData = try Data(contentsOf: planURL)
guard
let plan = try JSONSerialization.jsonObject(with: planData) as? [String: Any],
let scenarios = plan["scenarios"] as? [[String: Any]]
else {
throw AuditError.malformedPlan("scenarios must be an array")
}
var entries: [[String: Any]] = []
for scenario in scenarios {
let scenarioId = try requiredString(scenario, "scenarioId")
guard let screenshots = scenario["screenshots"] as? [[String: Any]] else {
throw AuditError.malformedPlan("\(scenarioId).screenshots must be an array")
}
for screenshot in screenshots {
let relativePath = try requiredString(screenshot, "file")
let imageURL = inputRoot.appendingPathComponent(relativePath).standardizedFileURL
let rootPrefix = inputRoot.path.hasSuffix("/") ? inputRoot.path : inputRoot.path + "/"
guard imageURL.path.hasPrefix(rootPrefix) else { throw AuditError.unsafePath(relativePath) }
let recognizedLines: [[String: Any]] = try autoreleasepool {
guard
let image = NSImage(contentsOf: imageURL),
let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil)
else {
throw AuditError.unreadableImage(relativePath)
}
let request = VNRecognizeTextRequest()
request.recognitionLevel = .accurate
request.recognitionLanguages = ["zh-Hans", "en-US"]
request.usesLanguageCorrection = true
try VNImageRequestHandler(cgImage: cgImage, options: [:]).perform([request])
return (request.results ?? []).compactMap { observation in
guard let text = observation.topCandidates(1).first?.string else { return nil }
let box = observation.boundingBox
return [
"text": text,
"boundingBox": [box.origin.x, box.origin.y, box.size.width, box.size.height],
]
}
}
entries.append([
"scenarioId": scenarioId,
"file": relativePath,
"lines": recognizedLines,
])
FileHandle.standardError.write(Data("OCR \(entries.count): \(relativePath)\n".utf8))
}
}
let output: [String: Any] = [
"schemaVersion": "solver.screenshot-privacy-ocr/v1",
"generatedAt": ISO8601DateFormatter().string(from: Date()),
"engine": "Apple Vision VNRecognizeTextRequest accurate zh-Hans+en-US",
"entries": entries,
]
let outputData = try JSONSerialization.data(withJSONObject: output, options: [.prettyPrinted, .sortedKeys])
try outputData.write(to: outputURL, options: .atomic)
} catch {
FileHandle.standardError.write(Data("\(error)\n".utf8))
exit(1)
}

View File

@@ -0,0 +1,591 @@
import { createRequire } from 'node:module'
import { cpSync, existsSync, globSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs'
import { basename, dirname, extname, join, relative, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import {
assertPublicText,
canonical,
computePublicEvidenceRoots,
loadPublicReleaseAttestation,
projectPublicProvenance,
resolveSafeOutputRoot,
sha256,
} from './public-release-policy.mjs'
const codeRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const inputRoot = resolve(process.env.SOLVER_REPORT_INPUT_ROOT ?? process.env.BUILDER_ROOT ?? process.cwd())
const configuredAppRoot = process.env.DESIRECORE_APP_ROOT
if (!configuredAppRoot) throw new Error('DESIRECORE_APP_ROOT is required')
const appRoot = resolve(configuredAppRoot)
const requireFromApp = createRequire(join(appRoot, 'package.json'))
const { marked } = requireFromApp('marked')
const katex = requireFromApp('katex')
const sharp = requireFromApp('sharp')
const Ajv = requireFromApp('ajv').default
const addFormats = requireFromApp('ajv-formats').default
const ajv = new Ajv({ allErrors: true, strict: true })
addFormats(ajv)
const validatePublicReleaseAttestationSchema = ajv.compile(
JSON.parse(readFileSync(join(codeRoot, 'public-release-attestation.schema.json'), 'utf8'))
)
const outputRoot = resolveSafeOutputRoot({
inputRoot,
codeRoot,
appRoot,
requestedOutputRoot: process.env.SOLVER_REPORT_OUTPUT_ROOT ?? join(inputRoot, 'report-web', 'dist'),
})
const sourceRoot = join(codeRoot, 'source')
const sourceScreenshotPlan = JSON.parse(
readFileSync(join(inputRoot, 'evidence', 'screenshots', 'screenshot-plan.json'), 'utf8')
)
const publicScreenshotPlanFile =
process.env.SOLVER_REPORT_PUBLIC_SCREENSHOT_PLAN_FILE ??
join(inputRoot, 'evidence', 'screenshots', 'public-screenshot-plan.json')
const screenshotPlan = JSON.parse(readFileSync(publicScreenshotPlanFile, 'utf8'))
const screenshotValidation = JSON.parse(
readFileSync(join(inputRoot, 'evidence', 'screenshots', 'screenshot-validation.json'), 'utf8')
)
const screenshotPrivacyValidation = JSON.parse(
readFileSync(
process.env.SOLVER_REPORT_SCREENSHOT_PRIVACY_VALIDATION_FILE ??
join(inputRoot, 'evidence', 'screenshots', 'public-screenshot-privacy-validation.json'),
'utf8'
)
)
const historicalBuildManifest = JSON.parse(readFileSync(join(inputRoot, 'build-manifest.json'), 'utf8'))
const stabilityMatrix = JSON.parse(readFileSync(join(inputRoot, 'evidence', 'stability', 'stability-matrix.json'), 'utf8'))
if (!screenshotValidation.passed || !stabilityMatrix.passed)
throw new Error('M8 evidence gates must pass before Web build')
if (
screenshotPlan.schemaVersion !== 'solver.public-screenshot-plan/v1' ||
screenshotPlan.sourcePlanSha256 !== sha256(canonical(sourceScreenshotPlan)) ||
screenshotPlan.scenarios.length !== sourceScreenshotPlan.scenarios.length
) {
throw new Error('public screenshot plan is missing or differs from the source evidence plan')
}
for (const sourceScenario of sourceScreenshotPlan.scenarios) {
const publicScenario = screenshotPlan.scenarios.find((candidate) => candidate.scenarioId === sourceScenario.scenarioId)
if (
!publicScenario ||
publicScenario.runId !== sourceScenario.runId ||
publicScenario.conversationId !== sourceScenario.conversationId ||
publicScenario.screenshots.length !== sourceScenario.screenshots.length ||
publicScenario.screenshots.some(
(screenshot, index) => screenshot.sourceSha256 !== sourceScenario.screenshots[index].sha256
)
) {
throw new Error(`${sourceScenario.scenarioId}: public screenshot derivation differs from source evidence`)
}
}
const latestRegressionFile =
process.env.LATEST_PLATFORM_REGRESSION_FILE ??
join(inputRoot, 'evidence', 'latest-platform-regression-20260830', 'summary.json')
const latestPlatformRegression = JSON.parse(readFileSync(latestRegressionFile, 'utf8'))
if (
latestPlatformRegression.schemaVersion !== 'solver.latest-platform-regression/v1' ||
latestPlatformRegression.passed !== true
) {
throw new Error('latest platform regression summary is missing, unsupported, or not passed')
}
const historicalPlatformCommit = historicalBuildManifest.platform?.commit
const historicalAgentSkillVersion = historicalBuildManifest.skill?.version
const representativeStabilityPasses = stabilityMatrix.groups?.reduce(
(total, group) => total + 1 + (group.freshAndPerturbedRuns?.length ?? 0),
0
)
if (
historicalBuildManifest.schema_version !== 'solver-agent-team-build/v1' ||
typeof historicalPlatformCommit !== 'string' ||
typeof historicalAgentSkillVersion !== 'string' ||
!Number.isInteger(representativeStabilityPasses) ||
representativeStabilityPasses < 1
) {
throw new Error('historical build manifest cannot derive public evidence ownership')
}
if (
!historicalPlatformCommit.startsWith(latestPlatformRegression.historicalEvidence?.platformCommit ?? '') ||
latestPlatformRegression.historicalEvidence?.representativeStabilityPasses !== representativeStabilityPasses
) {
throw new Error('latest regression historical attribution differs from the source evidence')
}
const scenarioOrder = [
'rw.territory-assignment',
'rw.resource-allocation',
'rw.performance-target',
'rw.customer-task-scheduling',
'rw.workforce-shift',
'rw.insufficient-capacity-iis',
'of.api-baseline',
'of.diet',
'of.facility-location',
'of.weekly-workforce',
'of.soap-production',
'of.ad-traffic',
'of.min-cost-flow',
'of.max-flow',
'of.portfolio',
'of.iis-diagnosis',
'of.seven-day-shift',
'boundary.qp-unsupported',
'qcp.risk-budget',
'cp.single-machine-detection',
'exploratory.two-person-shift',
'validation.independent-pass',
'validation.independent-fail',
'recovery.cold-start',
]
const sectionIds = [
'scenario-and-decision',
'input-and-provenance',
'data-quality',
'model-definition',
'train-validation-test',
'solve-process',
'solve-result',
'independent-validation',
'assumptions-risks-boundary',
'reproduction-and-conclusion',
]
const readJsonLines = (file) =>
readFileSync(file, 'utf8')
.split('\n')
.filter(Boolean)
.map((line) => JSON.parse(line))
const escapeHtml = (value) =>
String(value ?? '')
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&#39;')
let activeFormulaStats = null
const renderer = new marked.Renderer()
renderer.html = ({ text }) => escapeHtml(text)
marked.use({
extensions: [
{
name: 'blockMath',
level: 'block',
start: (source) => source.indexOf('\\['),
tokenizer(source) {
const match = /^\\\[([\s\S]*?)\\\](?:\n|$)/.exec(source)
return match ? { type: 'blockMath', raw: match[0], text: match[1].trim() } : undefined
},
renderer(token) {
if (activeFormulaStats) {
activeFormulaStats.source += 1
activeFormulaStats.display += 1
}
const html = katex.renderToString(token.text, {
throwOnError: true,
strict: 'ignore',
trust: false,
output: 'htmlAndMathml',
displayMode: true,
})
if (activeFormulaStats) activeFormulaStats.rendered += 1
return `${html}\n`
},
},
{
name: 'inlineMath',
level: 'inline',
start: (source) => source.indexOf('\\('),
tokenizer(source) {
const match = /^\\\(([^\n]+?)\\\)/.exec(source)
return match ? { type: 'inlineMath', raw: match[0], text: match[1].trim() } : undefined
},
renderer(token) {
if (activeFormulaStats) activeFormulaStats.source += 1
const html = katex.renderToString(token.text, {
throwOnError: true,
strict: 'ignore',
trust: false,
output: 'htmlAndMathml',
displayMode: false,
})
if (activeFormulaStats) activeFormulaStats.rendered += 1
return html
},
},
],
})
const renderMarkdown = (markdown) => {
const html = marked.parse(markdown, { gfm: true, breaks: true, renderer })
if (typeof html !== 'string') throw new Error('marked returned non-string output')
return html
}
const splitFinalReport = (markdown, scenarioId) => {
const matches = [...markdown.matchAll(/^##\s+(10|[1-9])\.\s+(.+)\s*$/gm)]
if (matches.length !== 10 || matches.some((match, index) => Number(match[1]) !== index + 1)) {
throw new Error(`${scenarioId}: final report is not the required ten-part structure`)
}
return matches.map((match, index) => {
const start = match.index + match[0].length
const end = matches[index + 1]?.index ?? markdown.length
const source = markdown.slice(start, end).trim()
return {
id: sectionIds[index],
number: index + 1,
title: match[2].trim(),
markdown: source,
markdownSha256: sha256(source),
html: renderMarkdown(source),
}
})
}
const packById = new Map(
globSync(join(inputRoot, 'scenario-packs', '*', '2.0.0', 'scenario-pack.json')).map((file) => {
const pack = JSON.parse(readFileSync(file, 'utf8'))
return [pack.manifest.scenarioId, { file, pack }]
})
)
const caseRunById = new Map(
globSync(join(inputRoot, 'evidence', '**', 'case-run.json')).map((file) => {
const data = JSON.parse(readFileSync(file, 'utf8'))
return [data.runId, { file, data, runDir: join(dirname(file), 'run') }]
})
)
const parseSummaryJson = (event) => {
try {
return JSON.parse(event.data?.summary ?? '')
} catch {
return null
}
}
const buildChapter = async (planItem, order) => {
const source = caseRunById.get(planItem.runId)
if (!source) throw new Error(`${planItem.scenarioId}: accepted Run evidence missing: ${planItem.runId}`)
const packEntry = packById.get(planItem.scenarioId)
if (!packEntry) throw new Error(`${planItem.scenarioId}: Scenario Pack missing`)
const { pack } = packEntry
const messagesFile = join(source.runDir, 'messages.jsonl')
const sessionFile = join(source.runDir, 'sessions', 'session.jsonl')
const invocationsFile = join(source.runDir, 'receipts', 'tool-invocations.jsonl')
const messages = readJsonLines(messagesFile)
const session = readJsonLines(sessionFile)
const invocations = readJsonLines(invocationsFile)
const conversationMessages = messages.filter((item) => ['user', 'assistant'].includes(item.role))
const finalAssistant = conversationMessages.filter((item) => item.role === 'assistant' && item.content?.trim()).at(-1)
if (!finalAssistant) throw new Error(`${planItem.scenarioId}: final assistant message missing`)
activeFormulaStats = { source: 0, rendered: 0, display: 0 }
const renderedMessages = conversationMessages.map((message, index) => ({
kind: 'message',
sequence: index + 1,
role: message.role,
timestamp: message.timestamp,
markdown: message.content ?? '',
markdownSha256: sha256(message.content ?? ''),
html: message.content?.trim() ? renderMarkdown(message.content) : '',
empty: !message.content?.trim(),
}))
const toolEvents = invocations
.filter((item) => item.phase === 'finished')
.map((item, index) => ({
kind: 'tool',
sequence: index + 1,
timestamp: Date.parse(item.timestamp),
tool: item.tool_name,
status: item.status,
durationMs: item.duration_ms ?? null,
provenance: projectPublicProvenance(item.result_provenance),
}))
const sections = splitFinalReport(finalAssistant.content, planItem.scenarioId)
const timeline = [...renderedMessages, ...toolEvents].sort(
(left, right) => Number(left.timestamp ?? 0) - Number(right.timestamp ?? 0) || left.kind.localeCompare(right.kind)
)
const validationSummaries = session
.filter((event) => event.type === 'tool_use_summary' && event.data?.metadata?.validation_subject !== undefined)
.map(parseSummaryJson)
.filter(Boolean)
const validation = validationSummaries.at(-1) ?? null
const finished = invocations.filter((item) => item.phase === 'finished' && item.status === 'success')
const solve = finished.find((item) => item.tool_name === 'OptimizationSolve')
const validate = [...finished].reverse().find((item) => item.tool_name === 'OptimizationValidate')
const problemFamily =
solve?.result_provenance?.problem_family ??
validate?.result_provenance?.problem_family ??
pack.oracle?.expectedProblemFamily ??
pack.manifest.tags.find((tag) => ['lp', 'milp', 'qp', 'qcp', 'cp'].includes(tag)) ??
'validation'
const stability = stabilityMatrix.groups.find((group) => group.scenarioId === planItem.scenarioId) ?? null
const media = []
for (const screenshot of planItem.screenshots) {
const sourceFile = join(inputRoot, screenshot.file)
const name = basename(sourceFile)
const originalRel = `media/original/${planItem.scenarioId}/${name}`
const thumb756Rel = `media/thumb-756/${planItem.scenarioId}/${name.replace(/\.png$/, '.webp')}`
const thumb1512Rel = `media/thumb-1512/${planItem.scenarioId}/${name.replace(/\.png$/, '.webp')}`
const originalTarget = join(outputRoot, originalRel)
const thumb756Target = join(outputRoot, thumb756Rel)
const thumb1512Target = join(outputRoot, thumb1512Rel)
mkdirSync(dirname(originalTarget), { recursive: true })
mkdirSync(dirname(thumb756Target), { recursive: true })
mkdirSync(dirname(thumb1512Target), { recursive: true })
cpSync(sourceFile, originalTarget)
await sharp(sourceFile)
.resize({ width: 756, withoutEnlargement: true })
.webp({ lossless: true, effort: 4 })
.toFile(thumb756Target)
await sharp(sourceFile)
.resize({ width: 1512, withoutEnlargement: true })
.webp({ lossless: true, effort: 4 })
.toFile(thumb1512Target)
media.push({
stage:
media.length === 0
? 'conversation-start'
: media.length === planItem.screenshots.length - 1
? 'delivery-conclusion'
: 'conversation-process',
original: originalRel,
thumb756: thumb756Rel,
thumb1512: thumb1512Rel,
width: screenshot.width,
height: screenshot.height,
sha256: screenshot.sha256,
alt: `${pack.manifest.title}真实多轮会话第 ${media.length + 1} 段,展示${media.length === 0 ? '用户请求与智能体补问' : media.length === planItem.screenshots.length - 1 ? '验证结论与复现证据' : '工具过程或报告正文'}`,
})
}
const formulaStats = { ...activeFormulaStats }
activeFormulaStats = null
const chapter = {
schemaVersion: 'solver.web-report-chapter/v1',
scenarioId: planItem.scenarioId,
scenarioVersion: pack.manifest.version,
scenarioOrder: order,
title: pack.manifest.title,
scenarioFamily: pack.manifest.scenarioFamily,
claimBoundary: pack.manifest.claimBoundary,
tags: pack.manifest.tags,
story: pack.businessContract.plainLanguageStory,
decision: pack.businessContract.decision,
professionalSummary: pack.businessContract.professionalSummary,
objectives: pack.businessContract.objectives,
constraints: pack.businessContract.constraints,
baseline: pack.businessContract.baseline,
outOfScope: pack.businessContract.outOfScope,
problemFamily,
resultStatus: pack.oracle.expectedStatuses?.[0] ?? source.data.status,
validationVerdict: source.data.diagnostics?.observedValidationVerdict ?? null,
engineId: solve?.result_provenance?.engine_id ?? null,
engineRequirement: solve?.result_provenance
? {
problemFamily: solve.result_provenance.problem_family,
irFamily: solve.result_provenance.ir_family,
irVersion: solve.result_provenance.ir_version,
capabilitySha256: solve.result_provenance.capability_hash,
}
: null,
evidence: {
runId: planItem.runId,
conversationId: planItem.conversationId,
sourceMessagesSha256: sha256(readFileSync(messagesFile)),
sourceSessionSha256: sha256(readFileSync(sessionFile)),
sourceToolInvocationsSha256: sha256(readFileSync(invocationsFile)),
optimizationSpecSha256: solve?.result_provenance?.optimization_spec_hash ?? null,
compiledRequestSha256: solve?.result_provenance?.compiled_request_hash ?? null,
resultPayloadSha256: solve?.result_provenance?.result_payload_hash ?? null,
validationReportSha256: validate?.result_provenance?.validation_report_hash ?? null,
},
counts: {
rawConversationMessages: conversationMessages.length,
visibleConversationMessages: renderedMessages.filter((item) => !item.empty).length,
toolEvents: toolEvents.length,
screenshots: media.length,
},
formulaStats,
timeline,
sections,
validation,
stability: stability
? {
policy: '3 fresh + 1 natural-language perturbation',
effectivePasses: 4,
semanticConvergence: stability.assertions.semanticConvergence,
singleSolvePerRun: stability.assertions.singleSolvePerRun,
semanticSha256: stability.freshAndPerturbedRuns[0].optimizationSemanticHash,
engineRequirementSha256: stability.freshAndPerturbedRuns[0].engineRequirementHash,
}
: { policy: 'standard real Run accepted; representative families use 3+1 gate', effectivePasses: 1 },
media,
}
assertPublicText(chapter, `${planItem.scenarioId} chapter`)
chapter.contentSha256 = sha256(canonical(chapter))
return chapter
}
const publicEvidenceRoots = computePublicEvidenceRoots({
inputRoot,
screenshotPlan,
screenshotValidation,
screenshotPrivacyValidation,
caseRunById,
})
const publicAttestationFile =
process.env.SOLVER_REPORT_PUBLIC_RELEASE_ATTESTATION_FILE ??
join(inputRoot, 'evidence', 'public-release-attestation.json')
const { attestation: publicReleaseAttestation, sha256: publicReleaseAttestationSha256 } =
loadPublicReleaseAttestation(publicAttestationFile, publicEvidenceRoots)
if (!validatePublicReleaseAttestationSchema(publicReleaseAttestation)) {
throw new Error(`public release attestation schema validation failed: ${ajv.errorsText(validatePublicReleaseAttestationSchema.errors)}`)
}
if (
!historicalPlatformCommit.startsWith(publicReleaseAttestation.metadata.platformCommit) ||
publicReleaseAttestation.metadata.agentSkillVersion !== historicalAgentSkillVersion ||
publicReleaseAttestation.metadata.representativeStabilityPasses !== representativeStabilityPasses
) {
throw new Error('public release attestation metadata differs from derived source evidence')
}
assertPublicText(latestPlatformRegression, 'latest platform regression')
rmSync(outputRoot, { recursive: true, force: true })
mkdirSync(join(outputRoot, 'assets'), { recursive: true })
mkdirSync(join(outputRoot, 'data', 'chapters'), { recursive: true })
for (const name of ['index.html', 'report.css', 'report.js'])
cpSync(join(sourceRoot, name), join(outputRoot, name === 'index.html' ? name : `assets/${name}`))
cpSync(join(appRoot, 'node_modules', 'katex', 'dist', 'katex.min.css'), join(outputRoot, 'assets', 'katex.min.css'))
cpSync(join(appRoot, 'node_modules', 'katex', 'dist', 'fonts'), join(outputRoot, 'assets', 'fonts'), {
recursive: true,
})
const chapters = []
for (const [index, scenarioId] of scenarioOrder.entries()) {
const item = screenshotPlan.scenarios.find((candidate) => candidate.scenarioId === scenarioId)
if (!item) throw new Error(`screenshot plan missing ${scenarioId}`)
const chapter = await buildChapter(item, index + 1)
const fileName = `${String(index + 1).padStart(2, '0')}-${scenarioId}.json`
const jsName = fileName.replace(/\.json$/, '.js')
writeFileSync(join(outputRoot, 'data', 'chapters', fileName), `${JSON.stringify(chapter)}\n`)
writeFileSync(
join(outputRoot, 'data', 'chapters', jsName),
`window.__REPORT_CHAPTERS__=window.__REPORT_CHAPTERS__||{};window.__REPORT_CHAPTERS__[${JSON.stringify(scenarioId)}]=${JSON.stringify(chapter)};\n`
)
chapters.push({
scenarioId,
order: index + 1,
title: chapter.title,
story: chapter.story,
problemFamily: chapter.problemFamily,
resultStatus: chapter.resultStatus,
validationVerdict: chapter.validationVerdict,
engineId: chapter.engineId,
json: `data/chapters/${fileName}`,
script: `data/chapters/${jsName}`,
sha256: chapter.contentSha256,
bytes: statSync(join(outputRoot, 'data', 'chapters', fileName)).size,
screenshots: chapter.media.length,
})
}
const historicalEvidenceContentRootSha256 = sha256(
canonical(chapters.map((item) => ({ scenarioId: item.scenarioId, sha256: item.sha256 })))
)
const latestPlatformRegressionSha256 = sha256(canonical(latestPlatformRegression))
const contentRootSha256 = sha256(canonical({ historicalEvidenceContentRootSha256, latestPlatformRegressionSha256 }))
const manifest = {
schemaVersion: 'solver.web-report-manifest/v1',
buildId: `${new Date().toISOString().replace(/[-:.]/g, '').slice(0, 15)}Z-${contentRootSha256.slice(0, 12)}`,
title: '通用求解器智能体团队 · 真实场景验证书',
generatedAt: new Date().toISOString(),
contentRootSha256,
reportVersion: '3.3.0',
platformCommit: latestPlatformRegression.platform.commit,
agentSkillVersion: latestPlatformRegression.platform.agentSkillVersion,
evidencePlatformCommit: historicalPlatformCommit,
publicReleaseAttestationSha256,
publicEvidenceRoots,
historicalEvidenceContentRootSha256,
latestPlatformRegressionSha256,
latestPlatformRegression,
counts: {
chapters: chapters.length,
screenshots: screenshotPlan.scenarios.reduce((total, item) => total + item.screenshots.length, 0),
representativeStabilityPasses,
},
chapters,
rendering: {
markdown: { engine: 'marked', version: requireFromApp('marked/package.json').version },
math: { engine: 'katex', version: requireFromApp('katex/package.json').version, output: 'htmlAndMathml' },
},
}
writeFileSync(join(outputRoot, 'data', 'manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`)
writeFileSync(join(outputRoot, 'data', 'manifest.js'), `window.__REPORT_MANIFEST__=${JSON.stringify(manifest)};\n`)
writeFileSync(
join(outputRoot, 'data', 'latest-platform-regression.json'),
`${JSON.stringify(latestPlatformRegression, null, 2)}\n`
)
const mime = (file) =>
({
'.html': 'text/html',
'.css': 'text/css',
'.js': 'text/javascript',
'.json': 'application/json',
'.png': 'image/png',
'.webp': 'image/webp',
'.woff2': 'font/woff2',
})[extname(file)] ?? 'application/octet-stream'
const integrityEntriesBeforeBuild = globSync(join(outputRoot, '**', '*')).filter((file) => statSync(file).isFile()).length
const expectedIntegrityEntries = integrityEntriesBeforeBuild + 1
writeFileSync(
join(outputRoot, 'build.json'),
`${JSON.stringify(
{
schemaVersion: 'solver.web-report-build/v1',
buildId: manifest.buildId,
contentRootSha256,
counts: manifest.counts,
integrityEntries: expectedIntegrityEntries,
gates: {
scenarioPacks: 'pass',
stability: 'pass',
screenshots: 'pass',
publicReleasePrivacy: 'pass',
markdown: 'pass',
math: 'pass',
},
publicReleaseAttestationSha256,
publicEvidenceRoots,
},
null,
2
)}\n`
)
const integrityEntries = globSync(join(outputRoot, '**', '*'))
.filter((file) => statSync(file).isFile())
.map((file) => {
const bytes = readFileSync(file)
return { path: relative(outputRoot, file), mime: mime(file), bytes: bytes.length, sha256: sha256(bytes) }
})
.sort((left, right) => left.path.localeCompare(right.path))
const integrity = { schemaVersion: 'solver.web-report-integrity/v1', contentRootSha256, entries: integrityEntries }
writeFileSync(join(outputRoot, 'integrity.json'), `${JSON.stringify(integrity, null, 2)}\n`)
if (integrityEntries.length !== expectedIntegrityEntries) throw new Error('integrity entry count changed unexpectedly')
console.log(
JSON.stringify(
{
outputRoot,
buildId: manifest.buildId,
contentRootSha256,
counts: manifest.counts,
integrityEntries: integrityEntries.length,
},
null,
2
)
)

View File

@@ -0,0 +1,75 @@
import { createRequire } from 'node:module'
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
import { join, resolve } from 'node:path'
const inputRoot = resolve(process.env.SOLVER_REPORT_INPUT_ROOT ?? process.cwd())
const appRoot = process.env.DESIRECORE_APP_ROOT
if (!appRoot) throw new Error('DESIRECORE_APP_ROOT is required')
const reviewRoot = process.env.SOLVER_REPORT_PRIVACY_REVIEW_ROOT
if (!reviewRoot) throw new Error('SOLVER_REPORT_PRIVACY_REVIEW_ROOT is required')
const outputRoot = resolve(reviewRoot)
const requireFromApp = createRequire(join(resolve(appRoot), 'package.json'))
const sharp = requireFromApp('sharp')
const plan = JSON.parse(readFileSync(join(inputRoot, 'evidence', 'screenshots', 'screenshot-plan.json'), 'utf8'))
const entries = plan.scenarios.flatMap((scenario) =>
scenario.screenshots.map((screenshot, index) => ({
scenarioId: scenario.scenarioId,
index: index + 1,
file: screenshot.file,
sha256: screenshot.sha256,
}))
)
const columns = 5
const rows = 6
const tileWidth = 240
const tileHeight = 175
const imageHeight = 145
const perSheet = columns * rows
const escapeXml = (value) =>
String(value).replace(/[&<>"']/g, (char) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&apos;' })[char])
mkdirSync(outputRoot, { recursive: true })
const sheets = []
for (let offset = 0; offset < entries.length; offset += perSheet) {
const page = entries.slice(offset, offset + perSheet)
const tiles = []
for (const entry of page) {
const preview = await sharp(join(inputRoot, entry.file))
.resize({ width: tileWidth, height: imageHeight, fit: 'contain', background: '#0b1120' })
.png()
.toBuffer()
const label = `${entry.scenarioId} · ${entry.index} · ${entry.sha256.slice(0, 10)}`
const tile = await sharp({
create: { width: tileWidth, height: tileHeight, channels: 4, background: '#111a2d' },
})
.composite([
{ input: preview, left: 0, top: 0 },
{
input: Buffer.from(
`<svg width="${tileWidth}" height="30"><rect width="100%" height="100%" fill="#18243b"/><text x="8" y="19" fill="#dce7f4" font-family="Arial, sans-serif" font-size="10">${escapeXml(label)}</text></svg>`
),
left: 0,
top: imageHeight,
},
])
.png()
.toBuffer()
const position = tiles.length
tiles.push({ input: tile, left: (position % columns) * tileWidth, top: Math.floor(position / columns) * tileHeight })
}
const file = `contact-sheet-${String(sheets.length + 1).padStart(2, '0')}.png`
await sharp({
create: { width: columns * tileWidth, height: rows * tileHeight, channels: 4, background: '#080d18' },
})
.composite(tiles)
.png()
.toFile(join(outputRoot, file))
sheets.push({ file, entries: page })
}
writeFileSync(
join(outputRoot, 'review-index.json'),
`${JSON.stringify({ schemaVersion: 'solver.privacy-contact-sheets/v1', screenshots: entries.length, sheets }, null, 2)}\n`
)
console.log(JSON.stringify({ outputRoot, screenshots: entries.length, sheets: sheets.length }, null, 2))

View File

@@ -0,0 +1,143 @@
import { createRequire } from 'node:module'
import { cpSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'
import { basename, dirname, join, relative, resolve } from 'node:path'
import { canonical, publicTextViolations, sha256 } from './public-release-policy.mjs'
const inputRoot = resolve(process.env.SOLVER_REPORT_INPUT_ROOT ?? process.cwd())
const appRoot = process.env.DESIRECORE_APP_ROOT
const ocrFile = process.env.SOLVER_REPORT_SCREENSHOT_OCR_FILE
if (!appRoot) throw new Error('DESIRECORE_APP_ROOT is required')
if (!ocrFile) throw new Error('SOLVER_REPORT_SCREENSHOT_OCR_FILE is required')
const requireFromApp = createRequire(join(resolve(appRoot), 'package.json'))
const sharp = requireFromApp('sharp')
const planFile = join(inputRoot, 'evidence', 'screenshots', 'screenshot-plan.json')
const outputPlanFile = join(inputRoot, 'evidence', 'screenshots', 'public-screenshot-plan.json')
const outputRoot = join(inputRoot, 'evidence', 'screenshots', 'public-redacted')
const sourcePlan = JSON.parse(readFileSync(planFile, 'utf8'))
const ocr = JSON.parse(readFileSync(ocrFile, 'utf8'))
const sourceScreenshots = sourcePlan.scenarios.flatMap((scenario) =>
scenario.screenshots.map((screenshot) => ({ scenarioId: scenario.scenarioId, ...screenshot }))
)
if (ocr.schemaVersion !== 'solver.screenshot-privacy-ocr/v1' || ocr.entries.length !== sourceScreenshots.length) {
throw new Error('OCR result does not cover the source screenshot plan')
}
const clamp = (value, minimum, maximum) => Math.max(minimum, Math.min(maximum, value))
const overlaps = (left, right) =>
left.left <= right.left + right.width &&
right.left <= left.left + left.width &&
left.top <= right.top + right.height &&
right.top <= left.top + left.height
const mergeRegions = (regions) => {
const merged = []
for (const region of regions) {
const existing = merged.find((candidate) => overlaps(candidate, region))
if (!existing) {
merged.push({ ...region })
continue
}
const right = Math.max(existing.left + existing.width, region.left + region.width)
const bottom = Math.max(existing.top + existing.height, region.top + region.height)
existing.left = Math.min(existing.left, region.left)
existing.top = Math.min(existing.top, region.top)
existing.width = right - existing.left
existing.height = bottom - existing.top
}
return merged
}
let redactedScreenshots = 0
let redactionRegions = 0
const publicByFile = new Map()
for (const [index, source] of sourceScreenshots.entries()) {
const ocrEntry = ocr.entries[index]
const sourceFile = join(inputRoot, source.file)
if (
ocrEntry?.scenarioId !== source.scenarioId ||
ocrEntry?.file !== source.file ||
sha256(readFileSync(sourceFile)) !== source.sha256
) {
throw new Error(`${source.file}: OCR source identity differs from screenshot plan`)
}
const metadata = await sharp(sourceFile).metadata()
if (metadata.width !== source.width || metadata.height !== source.height) {
throw new Error(`${source.file}: source dimensions differ from screenshot plan`)
}
const proposedRegions = ocrEntry.lines
.filter((line) => publicTextViolations(line.text).length > 0)
.map((line) => {
const [x, y, width, height] = line.boundingBox
const left = clamp(Math.floor(x * source.width) - 12, 0, source.width - 1)
const top = clamp(Math.floor((1 - y - height) * source.height) - 8, 0, source.height - 1)
const right = clamp(Math.ceil((x + width) * source.width) + 12, left + 1, source.width)
const bottom = clamp(Math.ceil((1 - y) * source.height) + Math.ceil(height * source.height * 1.2) + 8, top + 1, source.height)
return { left, top, width: right - left, height: bottom - top, reason: 'private-path' }
})
const regions = mergeRegions(proposedRegions)
const targetRelative = `evidence/screenshots/public-redacted/${source.scenarioId}/${basename(source.file)}`
const targetFile = join(inputRoot, targetRelative)
if (relative(outputRoot, targetFile).startsWith('..')) throw new Error(`${targetRelative}: unsafe public screenshot target`)
mkdirSync(dirname(targetFile), { recursive: true })
if (regions.length === 0) cpSync(sourceFile, targetFile)
else {
await sharp(sourceFile)
.composite(
regions.map((region) => ({
input: {
create: {
width: region.width,
height: region.height,
channels: 4,
background: { r: 32, g: 34, b: 39, alpha: 1 },
},
},
left: region.left,
top: region.top,
}))
)
.png()
.toFile(targetFile)
redactedScreenshots += 1
redactionRegions += regions.length
}
publicByFile.set(source.file, {
file: targetRelative,
sha256: sha256(readFileSync(targetFile)),
width: source.width,
height: source.height,
sourceSha256: source.sha256,
redactions: regions,
})
}
const publicPlan = {
schemaVersion: 'solver.public-screenshot-plan/v1',
generatedAt: new Date().toISOString(),
sourcePlanSha256: sha256(canonical(sourcePlan)),
derivation: {
method: 'Apple Vision exact-line private-path masking',
redactedScreenshots,
redactionRegions,
},
scenarios: sourcePlan.scenarios.map((scenario) => ({
...scenario,
screenshots: scenario.screenshots.map((screenshot) => publicByFile.get(screenshot.file)),
})),
}
writeFileSync(outputPlanFile, `${JSON.stringify(publicPlan, null, 2)}\n`)
console.log(
JSON.stringify(
{
outputPlanFile,
screenshots: sourceScreenshots.length,
redactedScreenshots,
redactionRegions,
bytes: sourceScreenshots.reduce(
(total, screenshot) => total + statSync(join(inputRoot, publicByFile.get(screenshot.file).file)).size,
0
),
},
null,
2
)
)

View File

@@ -0,0 +1,257 @@
import { createHash } from 'node:crypto'
import { existsSync, readFileSync, realpathSync } from 'node:fs'
import { dirname, isAbsolute, join, posix, relative, resolve } from 'node:path'
export const sha256 = (value) => createHash('sha256').update(value).digest('hex')
export const canonical = (value) => {
if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`
if (value && typeof value === 'object') {
return `{${Object.keys(value)
.sort()
.map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`)
.join(',')}}`
}
return JSON.stringify(value)
}
const isDescendant = (parent, candidate) => {
const path = relative(parent, candidate)
return path !== '' && !path.startsWith('..') && !isAbsolute(path)
}
const overlaps = (left, right) =>
left === right || isDescendant(left, right) || isDescendant(right, left)
const resolvePhysicalPath = (candidate) => {
let ancestor = resolve(candidate)
while (!existsSync(ancestor)) {
const parent = dirname(ancestor)
if (parent === ancestor) throw new Error(`cannot resolve an existing ancestor for ${candidate}`)
ancestor = parent
}
return resolve(realpathSync(ancestor), relative(ancestor, resolve(candidate)))
}
export function resolveSafeOutputRoot({ inputRoot, codeRoot, appRoot, requestedOutputRoot }) {
const roots = {
inputRoot: resolvePhysicalPath(inputRoot),
codeRoot: resolvePhysicalPath(codeRoot),
appRoot: resolvePhysicalPath(appRoot),
outputRoot: resolvePhysicalPath(requestedOutputRoot),
}
const lexicalInputRoot = resolve(inputRoot)
const lexicalOutputRoot = resolve(requestedOutputRoot)
const lexicalManagedParent = join(lexicalInputRoot, 'report-web')
const physicalManagedParent = resolvePhysicalPath(lexicalManagedParent)
if (
!isDescendant(lexicalManagedParent, lexicalOutputRoot) ||
!isDescendant(roots.inputRoot, physicalManagedParent) ||
!isDescendant(physicalManagedParent, roots.outputRoot)
) {
throw new Error('SOLVER_REPORT_OUTPUT_ROOT must be a child of <SOLVER_REPORT_INPUT_ROOT>/report-web')
}
if (roots.outputRoot === roots.inputRoot || isDescendant(roots.outputRoot, roots.inputRoot)) {
throw new Error('report output must not equal or contain the evidence input root')
}
for (const [name, protectedRoot] of [
['Agent source', roots.codeRoot],
['DesireCore application', roots.appRoot],
]) {
if (overlaps(roots.outputRoot, protectedRoot)) {
throw new Error(`report output must not overlap ${name} root`)
}
}
return roots.outputRoot
}
export function resolveSafeRemoteEntryUrl(baseUrl, entryPath) {
if (
typeof entryPath !== 'string' ||
entryPath.length === 0 ||
entryPath.startsWith('/') ||
entryPath.includes('\\') ||
entryPath.includes('%') ||
entryPath.includes('?') ||
entryPath.includes('#') ||
entryPath.split('/').some((segment) => segment === '' || segment === '.' || segment === '..') ||
posix.normalize(entryPath) !== entryPath ||
/^[A-Za-z][A-Za-z0-9+.-]*:/.test(entryPath)
) {
throw new Error(`unsafe integrity entry path: ${String(entryPath)}`)
}
const base = new URL(baseUrl)
if (!['http:', 'https:'].includes(base.protocol)) throw new Error('report base URL must use HTTP or HTTPS')
const basePath = base.pathname.endsWith('/') ? base.pathname : `${base.pathname}/`
const resolved = new URL(entryPath, base)
if (resolved.origin !== base.origin || !resolved.pathname.startsWith(basePath)) {
throw new Error(`integrity entry escapes report base URL: ${entryPath}`)
}
return resolved
}
const PUBLIC_PROVENANCE_KEYS = new Set([
'adapter_id',
'capability_checked_at',
'capability_hash',
'compiled_request_hash',
'compiler_id',
'compiler_version',
'connector_id',
'decision_grade',
'engine_id',
'governance_mode',
'ir_family',
'ir_version',
'job_id',
'optimization_spec_hash',
'problem_family',
'request_id',
'result_payload_hash',
'validation_input_hash',
'validation_report_hash',
])
export function projectPublicProvenance(provenance) {
if (!provenance || typeof provenance !== 'object' || Array.isArray(provenance)) return null
return Object.fromEntries(
Object.entries(provenance).filter(
([key, value]) =>
PUBLIC_PROVENANCE_KEYS.has(key) &&
(value === null || ['string', 'number', 'boolean'].includes(typeof value))
)
)
}
const PUBLIC_TEXT_RULES = [
['private-path', /(?:\/Users\/|\/Volumes\/|[A-Za-z]:\\Users\\)/i],
['customer-codename', /(?:贝壳|beike|bei-ke)/i],
['email-address', /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i],
['private-key', /-----BEGIN (?:RSA |OPENSSH |EC )?PRIVATE KEY-----/i],
['provider-secret', /\b(?:sk-[A-Za-z0-9_-]{12,}|gh[pousr]_[A-Za-z0-9_]{12,}|xox[baprs]-[A-Za-z0-9-]{12,})\b/],
['bearer-token', /\bBearer\s+[A-Za-z0-9._~+/-]{12,}={0,2}\b/i],
[
'named-secret',
/\b(?:password|passwd|authorization|api[_ -]?key|access[_ -]?token|refresh[_ -]?token|client[_ -]?secret)\b\s*[:=]\s*["']?[^\s"',}]{6,}/i,
],
[
'private-url',
/https?:\/\/(?:localhost|127(?:\.\d{1,3}){3}|10(?:\.\d{1,3}){3}|192\.168(?:\.\d{1,3}){2}|172\.(?:1[6-9]|2\d|3[01])(?:\.\d{1,3}){2}|[^/\s"']+\.internal)\b/i,
],
]
export function publicTextViolations(value) {
const text = (typeof value === 'string' ? value : JSON.stringify(value)).replace(
/\b\d+@2x\.(?:png|webp)\b/gi,
'[reviewed-retina-image]'
)
return PUBLIC_TEXT_RULES.filter(([, pattern]) => pattern.test(text)).map(([rule]) => rule)
}
export function assertPublicText(value, label) {
const violations = publicTextViolations(value)
if (violations.length > 0) {
throw new Error(`${label}: public-release text policy rejected ${violations.join(',')}`)
}
}
const readJsonLines = (file) =>
readFileSync(file, 'utf8')
.split('\n')
.filter(Boolean)
.map((line) => JSON.parse(line))
export function computePublicEvidenceRoots({
inputRoot,
screenshotPlan,
screenshotValidation,
screenshotPrivacyValidation,
caseRunById,
}) {
const transcriptEntries = screenshotPlan.scenarios.map((item) => {
const source = caseRunById.get(item.runId)
if (!source) throw new Error(`${item.scenarioId}: source Run missing for public attestation`)
const messages = readJsonLines(join(source.runDir, 'messages.jsonl'))
.filter((message) => ['user', 'assistant'].includes(message.role))
.map((message) => ({ role: message.role, content: message.content ?? '', timestamp: message.timestamp ?? null }))
return { scenarioId: item.scenarioId, runId: item.runId, messages }
})
const screenshotEntries = screenshotPlan.scenarios.flatMap((item) =>
item.screenshots.map((screenshot) => {
const file = join(inputRoot, screenshot.file)
if (!existsSync(file)) throw new Error(`${item.scenarioId}: screenshot missing: ${screenshot.file}`)
const actualSha256 = sha256(readFileSync(file))
if (actualSha256 !== screenshot.sha256) {
throw new Error(`${item.scenarioId}: screenshot hash differs from reviewed plan: ${screenshot.file}`)
}
return {
scenarioId: item.scenarioId,
file: screenshot.file,
sha256: screenshot.sha256,
width: screenshot.width,
height: screenshot.height,
}
})
)
const screenshotPlanSha256 = sha256(canonical(screenshotPlan))
const publicScreenshotContentRootSha256 = sha256(canonical(screenshotEntries))
if (
screenshotPrivacyValidation?.schemaVersion !== 'solver.screenshot-privacy-validation/v1' ||
screenshotPrivacyValidation.passed !== true ||
screenshotPrivacyValidation.screenshots !== screenshotEntries.length ||
screenshotPrivacyValidation.screenshotPlanSha256 !== screenshotPlanSha256 ||
screenshotPrivacyValidation.screenshotContentRootSha256 !== publicScreenshotContentRootSha256 ||
screenshotPrivacyValidation.ocr?.screenshotsWithText !== screenshotEntries.length ||
screenshotPrivacyValidation.ocr?.sensitiveMatches !== 0
) {
throw new Error('public screenshot privacy validation is missing or differs from current public images')
}
return {
publicTranscriptContentRootSha256: sha256(canonical(transcriptEntries)),
publicScreenshotContentRootSha256,
screenshotPlanSha256,
screenshotValidationSha256: sha256(canonical(screenshotValidation)),
screenshotPrivacyValidationSha256: sha256(canonical(screenshotPrivacyValidation)),
}
}
const HASH_PATTERN = /^[a-f0-9]{64}$/
export function validatePublicReleaseAttestation(attestation, evidenceRoots) {
if (attestation?.schemaVersion !== 'solver.public-release-attestation/v1') {
throw new Error('public release attestation is missing or has an unsupported schema')
}
if (attestation.audience !== 'public' || attestation.review?.status !== 'passed') {
throw new Error('public release attestation must explicitly pass for a public audience')
}
const assertions = attestation.review?.assertions ?? {}
for (const key of ['transcriptsContainNoSensitiveData', 'screenshotsContainNoSensitiveData', 'customerIdentifiersExcluded']) {
if (assertions[key] !== true) throw new Error(`public release attestation assertion is not true: ${key}`)
}
if (!attestation.review?.reviewedAt || !attestation.review?.reviewer || !Array.isArray(attestation.review?.methods)) {
throw new Error('public release attestation review identity, time, and methods are required')
}
for (const [key, expected] of Object.entries(evidenceRoots)) {
const actual = attestation.evidence?.[key]
if (!HASH_PATTERN.test(actual ?? '') || actual !== expected) {
throw new Error(`public release attestation does not match current evidence: ${key}`)
}
}
const metadata = attestation.metadata ?? {}
if (
typeof metadata.platformCommit !== 'string' ||
typeof metadata.agentSkillVersion !== 'string' ||
!Number.isInteger(metadata.representativeStabilityPasses) ||
metadata.representativeStabilityPasses < 0
) {
throw new Error('public release attestation metadata is incomplete')
}
return attestation
}
export function loadPublicReleaseAttestation(file, evidenceRoots) {
if (!existsSync(file)) throw new Error(`public release attestation is required: ${file}`)
const bytes = readFileSync(file)
const attestation = validatePublicReleaseAttestation(JSON.parse(bytes), evidenceRoots)
return { attestation, sha256: sha256(bytes) }
}

View File

@@ -0,0 +1,100 @@
import { createHash } from 'node:crypto'
import { writeFileSync } from 'node:fs'
import { join } from 'node:path'
import { canonical, resolveSafeRemoteEntryUrl } from './public-release-policy.mjs'
const baseUrl = (process.env.REPORT_BASE_URL ?? 'https://build.desirecore.cc/solver-agent-team/').replace(/\/?$/, '/')
const inputRoot = process.env.SOLVER_REPORT_INPUT_ROOT ?? process.cwd()
const validationFile =
process.env.SOLVER_REPORT_DEPLOYMENT_VALIDATION_FILE ?? join(inputRoot, 'report-web', 'deployment-validation.json')
async function fetchWithoutRedirect(url, init = {}) {
const response = await fetch(url, { ...init, redirect: 'manual' })
if (response.status >= 300 && response.status < 400) {
throw new Error(`redirect-rejected:${response.status}`)
}
return response
}
const integrityResponse = await fetchWithoutRedirect(new URL('integrity.json', baseUrl))
if (!integrityResponse.ok) throw new Error(`integrity.json: ${integrityResponse.status}`)
const integrity = await integrityResponse.json()
const failures = []
let checked = 0
let checkedBytes = 0
let cursor = 0
async function worker() {
while (cursor < integrity.entries.length) {
const index = cursor++
const entry = integrity.entries[index]
try {
const entryUrl = resolveSafeRemoteEntryUrl(baseUrl, entry.path)
const response = await fetchWithoutRedirect(entryUrl)
if (!response.ok) {
failures.push({ path: entry.path, issue: `http-${response.status}` })
continue
}
const bytes = Buffer.from(await response.arrayBuffer())
const digest = createHash('sha256').update(bytes).digest('hex')
if (bytes.length !== entry.bytes || digest !== entry.sha256) {
failures.push({ path: entry.path, issue: 'integrity-mismatch' })
}
checked += 1
checkedBytes += bytes.length
} catch (error) {
failures.push({ path: entry.path, issue: error instanceof Error ? error.message : String(error) })
}
}
}
await Promise.all(Array.from({ length: 12 }, () => worker()))
const rootResponse = await fetchWithoutRedirect(baseUrl)
const mediaResponse = await fetchWithoutRedirect(new URL('media/original/boundary.qp-unsupported/01@2x.png', baseUrl), {
method: 'HEAD',
})
const [buildResponse, manifestResponse] = await Promise.all([
fetchWithoutRedirect(new URL('build.json', baseUrl)),
fetchWithoutRedirect(new URL('data/manifest.json', baseUrl)),
])
const build = buildResponse.ok ? await buildResponse.json() : null
const manifest = manifestResponse.ok ? await manifestResponse.json() : null
if (build?.gates?.publicReleasePrivacy !== 'pass') failures.push({ path: 'build.json', issue: 'public-release-privacy-gate' })
if (
!build?.publicReleaseAttestationSha256 ||
build.publicReleaseAttestationSha256 !== manifest?.publicReleaseAttestationSha256
)
failures.push({ path: 'build.json', issue: 'public-release-attestation-mismatch' })
if (
!build ||
!manifest ||
build.buildId !== manifest.buildId ||
build.contentRootSha256 !== manifest.contentRootSha256 ||
integrity.contentRootSha256 !== manifest.contentRootSha256 ||
canonical(build.publicEvidenceRoots) !== canonical(manifest.publicEvidenceRoots) ||
canonical(build.counts) !== canonical(manifest.counts) ||
build.integrityEntries !== integrity.entries.length ||
!integrity.entries.some((entry) => entry.path === 'build.json')
)
failures.push({ path: 'build.json', issue: 'build-manifest-integrity-projection' })
const report = {
schemaVersion: 'solver.web-deployment-validation/v1',
testedAt: new Date().toISOString(), baseUrl,
contentRootSha256: integrity.contentRootSha256,
checkedEntries: checked, checkedBytes,
rootStatus: rootResponse.status,
csp: rootResponse.headers.get('content-security-policy'),
mediaStatus: mediaResponse.status,
mediaCacheControl: mediaResponse.headers.get('cache-control'),
failures,
passed:
failures.length === 0 &&
checked === integrity.entries.length &&
rootResponse.ok &&
mediaResponse.ok &&
buildResponse.ok &&
manifestResponse.ok,
}
writeFileSync(validationFile, `${JSON.stringify(report, null, 2)}\n`)
console.log(JSON.stringify(report, null, 2))
if (!report.passed) process.exitCode = 1

View File

@@ -0,0 +1,238 @@
import { existsSync, globSync, readFileSync, statSync, writeFileSync } from 'node:fs'
import { dirname, join, resolve } from 'node:path'
import {
assertPublicText,
canonical,
computePublicEvidenceRoots,
loadPublicReleaseAttestation,
sha256,
} from './public-release-policy.mjs'
const inputRoot = resolve(process.env.SOLVER_REPORT_INPUT_ROOT ?? process.env.BUILDER_ROOT ?? process.cwd())
const dist = process.env.SOLVER_REPORT_OUTPUT_ROOT ?? join(inputRoot, 'report-web', 'dist')
const validationFile =
process.env.SOLVER_REPORT_VALIDATION_FILE ?? join(inputRoot, 'report-web', 'validation.json')
const manifest = JSON.parse(readFileSync(join(dist, 'data', 'manifest.json'), 'utf8'))
const integrity = JSON.parse(readFileSync(join(dist, 'integrity.json'), 'utf8'))
const build = JSON.parse(readFileSync(join(dist, 'build.json'), 'utf8'))
const latestRegression = JSON.parse(readFileSync(join(dist, 'data', 'latest-platform-regression.json'), 'utf8'))
const failures = []
const readJsonLines = (file) =>
readFileSync(file, 'utf8')
.split('\n')
.filter(Boolean)
.map((line) => JSON.parse(line))
const caseRunById = new Map(
globSync(join(inputRoot, 'evidence', '**', 'case-run.json')).map((file) => {
const data = JSON.parse(readFileSync(file, 'utf8'))
return [data.runId, { file, data, runDir: join(dirname(file), 'run') }]
})
)
const screenshotPlan = JSON.parse(
readFileSync(
process.env.SOLVER_REPORT_PUBLIC_SCREENSHOT_PLAN_FILE ??
join(inputRoot, 'evidence', 'screenshots', 'public-screenshot-plan.json'),
'utf8'
)
)
const screenshotValidation = JSON.parse(
readFileSync(join(inputRoot, 'evidence', 'screenshots', 'screenshot-validation.json'), 'utf8')
)
const screenshotPrivacyValidation = JSON.parse(
readFileSync(
process.env.SOLVER_REPORT_SCREENSHOT_PRIVACY_VALIDATION_FILE ??
join(inputRoot, 'evidence', 'screenshots', 'public-screenshot-privacy-validation.json'),
'utf8'
)
)
const publicEvidenceRoots = computePublicEvidenceRoots({
inputRoot,
screenshotPlan,
screenshotValidation,
screenshotPrivacyValidation,
caseRunById,
})
const publicAttestationFile =
process.env.SOLVER_REPORT_PUBLIC_RELEASE_ATTESTATION_FILE ??
join(inputRoot, 'evidence', 'public-release-attestation.json')
const { attestation: publicReleaseAttestation, sha256: publicReleaseAttestationSha256 } =
loadPublicReleaseAttestation(publicAttestationFile, publicEvidenceRoots)
if (manifest.chapters.length !== 24) failures.push({ issue: 'chapter-count', actual: manifest.chapters.length })
if (manifest.counts.screenshots !== 174)
failures.push({ issue: 'screenshot-count', actual: manifest.counts.screenshots })
const historicalEvidenceContentRootSha256 = sha256(
canonical(manifest.chapters.map((item) => ({ scenarioId: item.scenarioId, sha256: item.sha256 })))
)
const latestPlatformRegressionSha256 = sha256(canonical(latestRegression))
const expectedContentRootSha256 = sha256(
canonical({ historicalEvidenceContentRootSha256, latestPlatformRegressionSha256 })
)
if (manifest.historicalEvidenceContentRootSha256 !== historicalEvidenceContentRootSha256)
failures.push({ issue: 'historical-evidence-root' })
if (manifest.latestPlatformRegressionSha256 !== latestPlatformRegressionSha256)
failures.push({ issue: 'latest-regression-hash' })
if (
manifest.contentRootSha256 !== expectedContentRootSha256 ||
integrity.contentRootSha256 !== expectedContentRootSha256
)
failures.push({ issue: 'report-content-root' })
if (latestRegression.schemaVersion !== 'solver.latest-platform-regression/v1' || latestRegression.passed !== true)
failures.push({ issue: 'latest-regression-status' })
if (manifest.latestPlatformRegression?.platform?.commit !== latestRegression.platform?.commit)
failures.push({ issue: 'latest-regression-manifest-projection' })
if (
!manifest.evidencePlatformCommit?.startsWith(publicReleaseAttestation.metadata.platformCommit) ||
!manifest.evidencePlatformCommit?.startsWith(latestRegression.historicalEvidence?.platformCommit)
)
failures.push({ issue: 'historical-platform-attribution' })
if (
manifest.platformCommit !== latestRegression.platform?.commit ||
manifest.agentSkillVersion !== latestRegression.platform?.agentSkillVersion ||
manifest.counts.representativeStabilityPasses !== publicReleaseAttestation.metadata.representativeStabilityPasses
)
failures.push({ issue: 'derived-release-metadata' })
if (
manifest.publicReleaseAttestationSha256 !== publicReleaseAttestationSha256 ||
canonical(manifest.publicEvidenceRoots) !== canonical(publicEvidenceRoots)
)
failures.push({ issue: 'public-release-attestation-projection' })
if (
build.buildId !== manifest.buildId ||
build.contentRootSha256 !== manifest.contentRootSha256 ||
build.publicReleaseAttestationSha256 !== manifest.publicReleaseAttestationSha256 ||
canonical(build.publicEvidenceRoots) !== canonical(manifest.publicEvidenceRoots) ||
canonical(build.counts) !== canonical(manifest.counts) ||
build.integrityEntries !== integrity.entries.length ||
build.gates?.publicReleasePrivacy !== 'pass' ||
!integrity.entries.some((entry) => entry.path === 'build.json')
)
failures.push({ issue: 'build-manifest-integrity-projection' })
try {
assertPublicText(latestRegression, 'latest platform regression')
} catch (error) {
failures.push({ issue: 'latest-regression-public-text-policy', detail: error.message })
}
let formulas = 0
let screenshotCount = 0
for (const index of manifest.chapters) {
const file = join(dist, index.json)
const script = join(dist, index.script)
if (!existsSync(file) || !existsSync(script)) {
failures.push({ scenarioId: index.scenarioId, issue: 'chapter-file-missing' })
continue
}
const chapter = JSON.parse(readFileSync(file, 'utf8'))
const withoutHash = structuredClone(chapter)
delete withoutHash.contentSha256
if (sha256(canonical(withoutHash)) !== chapter.contentSha256 || chapter.contentSha256 !== index.sha256) {
failures.push({ scenarioId: index.scenarioId, issue: 'chapter-hash' })
}
if (chapter.sections.length !== 10 || chapter.sections.some((section, i) => section.number !== i + 1)) {
failures.push({ scenarioId: index.scenarioId, issue: 'ten-part-report' })
}
if (chapter.formulaStats.source !== chapter.formulaStats.rendered) {
failures.push({ scenarioId: index.scenarioId, issue: 'formula-count', stats: chapter.formulaStats })
}
const renderedHtml = [
...chapter.timeline.filter((item) => item.kind === 'message').map((item) => item.html),
...chapter.sections.map((item) => item.html),
].join('\n')
const mathmlCount = (renderedHtml.match(/<math\b/g) ?? []).length
if (mathmlCount !== chapter.formulaStats.rendered || /katex-error/.test(renderedHtml)) {
failures.push({
scenarioId: index.scenarioId,
issue: 'mathml-render',
expected: chapter.formulaStats.rendered,
actual: mathmlCount,
})
}
const renderedWithoutCode = renderedHtml.replace(/<pre[\s\S]*?<\/pre>/g, '').replace(/<code[\s\S]*?<\/code>/g, '')
if (/\\\(|\\\)|\\\[|\\\]/.test(renderedWithoutCode)) {
failures.push({ scenarioId: index.scenarioId, issue: 'unrendered-latex-delimiter' })
}
formulas += chapter.formulaStats.rendered
screenshotCount += chapter.media.length
for (const image of chapter.media) {
const original = join(dist, image.original)
if (!existsSync(original) || sha256(readFileSync(original)) !== image.sha256) {
failures.push({ scenarioId: index.scenarioId, issue: 'original-image', path: image.original })
}
for (const thumb of [image.thumb756, image.thumb1512]) {
if (!existsSync(join(dist, thumb)))
failures.push({ scenarioId: index.scenarioId, issue: 'thumbnail-missing', path: thumb })
}
}
const source = caseRunById.get(chapter.evidence.runId)
if (!source) failures.push({ scenarioId: index.scenarioId, issue: 'source-run-missing' })
else {
const raw = readJsonLines(join(source.runDir, 'messages.jsonl')).filter((item) =>
['user', 'assistant'].includes(item.role)
)
const projected = chapter.timeline.filter((item) => item.kind === 'message')
if (
raw.length !== projected.length ||
raw.some((message, i) => message.role !== projected[i].role || (message.content ?? '') !== projected[i].markdown)
) {
failures.push({ scenarioId: index.scenarioId, issue: 'transcript-not-1-to-1' })
}
}
const serialized = JSON.stringify(chapter)
try {
assertPublicText(serialized, `${index.scenarioId} chapter`)
} catch (error) {
failures.push({ scenarioId: index.scenarioId, issue: 'public-text-policy', detail: error.message })
}
if (
['qp', 'qcp', 'cp'].includes(String(chapter.problemFamily).toLowerCase()) &&
/当前只支持\s*LP\/?MILP/i.test(serialized)
) {
failures.push({ scenarioId: index.scenarioId, issue: 'stale-capability-summary' })
}
}
if (screenshotCount !== 174) failures.push({ issue: 'chapter-screenshot-sum', actual: screenshotCount })
for (const entry of integrity.entries) {
const file = join(dist, entry.path)
if (!existsSync(file)) failures.push({ issue: 'integrity-file-missing', path: entry.path })
else {
const bytes = readFileSync(file)
if (bytes.length !== entry.bytes || sha256(bytes) !== entry.sha256)
failures.push({ issue: 'integrity-mismatch', path: entry.path })
}
}
for (const required of [
'index.html',
'assets/report.css',
'assets/report.js',
'assets/katex.min.css',
'data/manifest.js',
'data/latest-platform-regression.json',
]) {
if (!existsSync(join(dist, required))) failures.push({ issue: 'required-asset', path: required })
}
const report = {
schemaVersion: 'solver.web-report-validation/v1',
generatedAt: new Date().toISOString(),
buildId: manifest.buildId,
chapters: manifest.chapters.length,
screenshots: screenshotCount,
formulas,
integrityEntries: integrity.entries.length,
bytes: globSync(join(dist, '**', '*'))
.filter((file) => statSync(file).isFile())
.reduce((sum, file) => sum + statSync(file).size, 0),
latestPlatformRegression: {
commit: latestRegression.platform.commit,
scenarios: latestRegression.runtime.scenarios.length,
decisionWorkspace: latestRegression.runtime.decisionWorkspace.status,
},
passed: failures.length === 0,
failures,
}
writeFileSync(validationFile, `${JSON.stringify(report, null, 2)}\n`)
console.log(JSON.stringify(report, null, 2))
if (!report.passed) process.exitCode = 1

View File

@@ -0,0 +1,80 @@
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
import { join, resolve } from 'node:path'
import { assertPublicText, canonical, sha256 } from './public-release-policy.mjs'
const inputRoot = resolve(process.env.SOLVER_REPORT_INPUT_ROOT ?? process.cwd())
const planFile =
process.env.SOLVER_REPORT_SCREENSHOT_PLAN_FILE ??
join(inputRoot, 'evidence', 'screenshots', 'screenshot-plan.json')
const ocrFile = process.env.SOLVER_REPORT_SCREENSHOT_OCR_FILE
if (!ocrFile) throw new Error('SOLVER_REPORT_SCREENSHOT_OCR_FILE is required')
const validationFile = process.env.SOLVER_REPORT_SCREENSHOT_OCR_VALIDATION_FILE
if (!validationFile) throw new Error('SOLVER_REPORT_SCREENSHOT_OCR_VALIDATION_FILE is required')
const plan = JSON.parse(readFileSync(planFile, 'utf8'))
const ocr = JSON.parse(readFileSync(ocrFile, 'utf8'))
if (ocr.schemaVersion !== 'solver.screenshot-privacy-ocr/v1' || !Array.isArray(ocr.entries)) {
throw new Error('unsupported screenshot OCR result')
}
const planned = plan.scenarios.flatMap((scenario) =>
scenario.screenshots.map((screenshot) => ({ scenarioId: scenario.scenarioId, ...screenshot }))
)
if (planned.length !== 174 || ocr.entries.length !== planned.length) {
throw new Error(`screenshot OCR coverage mismatch: expected ${planned.length}, received ${ocr.entries.length}`)
}
const failures = []
const screenshotRootEntries = []
let recognizedLines = 0
let screenshotsWithText = 0
for (const [index, expected] of planned.entries()) {
const actual = ocr.entries[index]
const imageFile = join(inputRoot, expected.file)
if (!existsSync(imageFile)) failures.push({ file: expected.file, issue: 'missing-image' })
const actualSha256 = existsSync(imageFile) ? sha256(readFileSync(imageFile)) : null
if (
actual?.scenarioId !== expected.scenarioId ||
actual?.file !== expected.file ||
actualSha256 !== expected.sha256 ||
!Array.isArray(actual?.lines) ||
actual.lines.some((line) => typeof line?.text !== 'string' || !Array.isArray(line?.boundingBox))
) {
failures.push({ file: expected.file, issue: 'plan-or-hash-mismatch' })
continue
}
const text = actual.lines.map((line) => line.text).join('\n')
recognizedLines += actual.lines.length
if (actual.lines.length > 0) screenshotsWithText += 1
try {
assertPublicText(text, `${expected.scenarioId} screenshot OCR`)
} catch (error) {
failures.push({ file: expected.file, issue: 'sensitive-text-policy', detail: error.message })
}
screenshotRootEntries.push({
scenarioId: expected.scenarioId,
file: expected.file,
sha256: expected.sha256,
width: expected.width,
height: expected.height,
})
}
const report = {
schemaVersion: 'solver.screenshot-privacy-validation/v1',
auditedAt: new Date().toISOString(),
screenshotPlanSha256: sha256(canonical(plan)),
screenshotContentRootSha256: sha256(canonical(screenshotRootEntries)),
screenshots: planned.length,
ocr: {
engine: ocr.engine,
screenshotsWithText,
recognizedLines,
sensitiveMatches: failures.filter((failure) => failure.issue === 'sensitive-text-policy').length,
},
passed: failures.length === 0 && screenshotsWithText === planned.length,
failures,
}
writeFileSync(validationFile, `${JSON.stringify(report, null, 2)}\n`)
console.log(JSON.stringify(report, null, 2))
if (!report.passed) process.exitCode = 1

View File

@@ -0,0 +1,64 @@
#!/usr/bin/env python3
import hashlib
import json
import pathlib
import sys
root = pathlib.Path(sys.argv[1]).resolve()
integrity = json.loads((root / "integrity.json").read_text(encoding="utf-8"))
failures = []
for entry in integrity["entries"]:
raw_path = entry.get("path")
if (
not isinstance(raw_path, str)
or not raw_path
or "//" in raw_path
or "\\" in raw_path
or "%" in raw_path
or "?" in raw_path
or "#" in raw_path
):
failures.append({"path": str(raw_path), "issue": "unsafe-path"})
continue
relative_path = pathlib.PurePosixPath(raw_path)
if relative_path.is_absolute() or any(part in ("", ".", "..") for part in relative_path.parts):
failures.append({"path": raw_path, "issue": "unsafe-path"})
continue
path = (root / pathlib.Path(*relative_path.parts)).resolve()
if path == root or root not in path.parents:
failures.append({"path": raw_path, "issue": "path-escape"})
continue
if not path.is_file():
failures.append({"path": raw_path, "issue": "missing"})
continue
data = path.read_bytes()
digest = hashlib.sha256(data).hexdigest()
if len(data) != entry["bytes"] or digest != entry["sha256"]:
failures.append({"path": raw_path, "issue": "mismatch"})
manifest = json.loads((root / "data" / "manifest.json").read_text(encoding="utf-8"))
build = json.loads((root / "build.json").read_text(encoding="utf-8"))
if build.get("gates", {}).get("publicReleasePrivacy") != "pass":
failures.append({"path": "build.json", "issue": "public-release-privacy-gate"})
if build.get("publicReleaseAttestationSha256") != manifest.get("publicReleaseAttestationSha256"):
failures.append({"path": "build.json", "issue": "public-release-attestation-mismatch"})
if (
build.get("buildId") != manifest.get("buildId")
or build.get("contentRootSha256") != manifest.get("contentRootSha256")
or integrity.get("contentRootSha256") != manifest.get("contentRootSha256")
or build.get("publicEvidenceRoots") != manifest.get("publicEvidenceRoots")
or build.get("counts") != manifest.get("counts")
or build.get("integrityEntries") != len(integrity.get("entries", []))
or not any(entry.get("path") == "build.json" for entry in integrity.get("entries", []))
):
failures.append({"path": "build.json", "issue": "build-manifest-integrity-projection"})
result = {
"buildId": manifest["buildId"],
"contentRootSha256": manifest["contentRootSha256"],
"chapters": len(manifest["chapters"]),
"integrityEntries": len(integrity["entries"]),
"failures": failures,
"passed": not failures,
}
print(json.dumps(result, ensure_ascii=False))
raise SystemExit(0 if result["passed"] else 1)