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

@@ -2,7 +2,7 @@
"name": "DesireCore",
"$schema": "http://desirecore/schemas/agent-seed.json",
"id": "7da73b7f-bb08-4e7b-a3cf-5d4af6e22c7f",
"version": "1.10.0",
"version": "1.11.0",
"requiredClientVersion": "10.0.100",
"description": "系统中枢调度器负责任务分发、Agent 编排与全局状态监控",
"author": "DesireCore Team",

3
web/solver-report/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
deployment-validation.json
validation.json
dist/

View File

@@ -0,0 +1,60 @@
# Solver Report Web
Version-controlled source and build tooling for the public Solver Agent Team evidence report.
This directory owns the Web application, interaction behavior, build contract, validators, and deployment verification scripts. Scenario packs, runtime receipts, and original screenshots remain external build inputs so the core DesireCore Agent bootstrap does not grow by hundreds of megabytes.
## Image viewer
The screenshot viewer supports:
- fit-to-window and 1:1 pixel modes;
- bounded zoom buttons and `+` / `-` / `0` / `1` keyboard shortcuts, including 1:1 source pixels on mobile;
- `Ctrl`/`Command` + wheel or trackpad pinch zoom around the pointer;
- double-click zoom and mouse/touch drag panning;
- two-pointer touch pinch zoom;
- an original-file download link and accessible live zoom status.
Each newly opened image starts in fit mode. Closing and reopening the viewer does not retain stale zoom or pan state.
## Build
The build consumes an evidence directory with `scenario-packs/`, `evidence/`, and `report-web/dist/` output. Rendering dependencies are resolved from a DesireCore application checkout.
Public builds also require `evidence/public-release-attestation.json`. The [Draft-07 Schema](./public-release-attestation.schema.json) binds the review to the content roots of every public message and all 174 screenshots, together with the review methods, evidence ownership, and public-release verdict. Any message or screenshot change invalidates the old attestation and fails the build.
The site consumes `evidence/screenshots/public-screenshot-plan.json`, a derivation plan whose `sourceSha256` values bind every public image to the immutable raw evidence. Raw screenshots containing local paths must never be used as public originals.
```bash
export SOLVER_REPORT_INPUT_ROOT=/absolute/path/to/scenario-book
export DESIRECORE_APP_ROOT=/absolute/path/to/desirecore
node scripts/build-report-web.mjs
node scripts/validate-report-web.mjs
```
Optional environment variables:
- `SOLVER_REPORT_OUTPUT_ROOT`: override the generated directory. To make recursive cleanup safe, it must remain below `<SOLVER_REPORT_INPUT_ROOT>/report-web/` and must not overlap the Agent source or DesireCore application checkout.
- `LATEST_PLATFORM_REGRESSION_FILE`: override the latest regression summary.
- `SOLVER_REPORT_PUBLIC_RELEASE_ATTESTATION_FILE`: override the public-release attestation path.
- `SOLVER_REPORT_VALIDATION_FILE`: override the validation report path.
- `REPORT_BASE_URL`: public URL used by `validate-deployed-report.mjs`.
- `SOLVER_REPORT_DEPLOYMENT_VALIDATION_FILE`: override deployment validation output.
Run the source contract tests with `node --test tests/*.test.mjs`.
Before signing a public-release attestation, review every original screenshot at readable resolution. Contact sheets are only a hash-labelled coverage index; they are not sufficient for reading small credentials. On macOS, run the original-resolution Apple Vision OCR gate as a second, independent check:
```bash
export SOLVER_REPORT_SCREENSHOT_OCR_FILE=/private/tmp/solver-report-ocr.json
export SOLVER_REPORT_SCREENSHOT_OCR_VALIDATION_FILE=/private/tmp/solver-report-ocr-validation.json
npm run privacy:ocr
```
The validator requires all 174 planned files in order, recomputes every source hash, and applies the same fail-closed sensitive-text policy to recognized text. OCR and full-resolution visual review are both required before updating the attestation. Review-only artifacts never enter the site output or Agent bootstrap.
## Deployment boundary
The builder projects only allowlisted public provenance fields and rejects local paths, email addresses, private URLs, common key formats, and named credentials. Deployment validation also requires `build.json#gates.publicReleasePrivacy=pass` and an attestation digest matching the manifest.
Generate a new immutable release, verify every `integrity.json` entry before switching, and atomically update the server's `current` symlink. Never overwrite an existing release or relabel historical evidence as output from a newer platform commit.

View File

@@ -0,0 +1,60 @@
# 求解器验证书 Web
这里版本化管理“求解器智能体团队验证书”的 Web 源码与构建工具。
本目录负责 Web 应用、交互行为、构建契约、静态验证器和部署验证脚本。场景包、运行回执和原始截图继续作为外部构建输入,避免让 DesireCore 核心 Agent 的 bootstrap 体积增加数百 MB。
## 图片查看器
截图预览支持:
- 适应窗口与 1:1 原始像素模式;
- 有上下限的缩放按钮,以及 `+` / `-` / `0` / `1` 键盘快捷键(移动端也能达到截图的 1:1 原始像素);
- `Ctrl`/`Command` + 滚轮或触控板捏合,以指针位置为中心缩放;
- 双击缩放、鼠标/单指拖动平移;
- 双指触控捏合缩放;
- 原图下载入口和可访问的实时缩放状态。
每次打开新图片都从“适应窗口”开始;关闭后重新打开不会保留旧的缩放或平移状态。
## 构建
构建输入目录需要包含 `scenario-packs/``evidence/`,产物默认写到 `report-web/dist/`。Markdown、公式和图片处理依赖从 DesireCore 应用 checkout 解析。
公网构建还必须提供 `evidence/public-release-attestation.json`。它按 [Draft-07 Schema](./public-release-attestation.schema.json) 绑定全部公开消息与 174 张截图的内容根哈希,并明确记录审查方法、证据归属和公开发布结论。消息或截图发生任何变化,旧证明都会失效,构建直接失败。
站点只消费 `evidence/screenshots/public-screenshot-plan.json`。该派生计划通过每张图的 `sourceSha256` 将公开脱敏图绑定到不可变原始证据;含本地路径的原始截图绝不能直接作为公网原图。
```bash
export SOLVER_REPORT_INPUT_ROOT=/absolute/path/to/scenario-book
export DESIRECORE_APP_ROOT=/absolute/path/to/desirecore
node scripts/build-report-web.mjs
node scripts/validate-report-web.mjs
```
可选环境变量:
- `SOLVER_REPORT_OUTPUT_ROOT`:覆盖生成路径;为避免递归清理误伤,它必须位于 `<SOLVER_REPORT_INPUT_ROOT>/report-web/` 的子目录内,且不得与 Agent 源码或 DesireCore 应用目录重叠。
- `LATEST_PLATFORM_REGRESSION_FILE`:覆盖最新回归摘要路径。
- `SOLVER_REPORT_PUBLIC_RELEASE_ATTESTATION_FILE`:覆盖公开发布证明路径。
- `SOLVER_REPORT_VALIDATION_FILE`:覆盖静态验证结果路径。
- `REPORT_BASE_URL``validate-deployed-report.mjs` 使用的公网地址。
- `SOLVER_REPORT_DEPLOYMENT_VALIDATION_FILE`:覆盖部署验证结果路径。
源码契约测试:`node --test tests/*.test.mjs`
签署公开发布证明前,必须以可读分辨率逐张检查全部原始截图。联系表只用于核对场景 ID、截图哈希和覆盖范围不能替代小字凭据审查。在 macOS 上还要运行原图级 Apple Vision OCR 门禁,作为第二条独立检查路径:
```bash
export SOLVER_REPORT_SCREENSHOT_OCR_FILE=/private/tmp/solver-report-ocr.json
export SOLVER_REPORT_SCREENSHOT_OCR_VALIDATION_FILE=/private/tmp/solver-report-ocr-validation.json
npm run privacy:ocr
```
验证器要求 174 张计划内原图按顺序全部出现,重新计算每张源图哈希,并对识别文字复用 fail-closed 敏感信息策略。OCR 与全分辨率人工视觉审查必须同时通过后,才能更新公开发布证明;审查产物不会进入站点或 Agent bootstrap。
## 部署边界
构建器只允许公开 provenance 白名单字段,并会拒绝本机路径、邮箱、私有 URL、常见密钥和具名凭据。部署验证同时要求 `build.json#gates.publicReleasePrivacy=pass` 且证明哈希与 manifest 一致。
每次生成新的不可变 release切换前逐项验证 `integrity.json`,然后原子更新服务端 `current` 软链接。禁止覆盖既有 release也禁止把历史证据重新标记为新版平台产物。

View File

@@ -0,0 +1,15 @@
{
"name": "@desirecore/solver-report-web",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"test": "node --test tests/*.test.mjs",
"build": "node scripts/build-report-web.mjs",
"privacy:contact-sheets": "node scripts/create-privacy-contact-sheets.mjs",
"privacy:ocr": "swift scripts/audit-screenshot-privacy.swift \"$SOLVER_REPORT_INPUT_ROOT\" \"${SOLVER_REPORT_SCREENSHOT_PLAN_FILE:-$SOLVER_REPORT_INPUT_ROOT/evidence/screenshots/screenshot-plan.json}\" \"$SOLVER_REPORT_SCREENSHOT_OCR_FILE\" && node scripts/validate-screenshot-privacy-ocr.mjs",
"privacy:redact": "node scripts/create-public-screenshots.mjs",
"validate": "node scripts/validate-report-web.mjs",
"validate:deployed": "node scripts/validate-deployed-report.mjs"
}
}

View File

@@ -0,0 +1,56 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://desirecore.dev/schemas/solver-public-release-attestation-v1.json",
"title": "Solver report public release attestation",
"description": "Binds a public-release privacy review to the exact transcript and screenshot evidence roots consumed by the report builder.",
"type": "object",
"additionalProperties": false,
"required": ["schemaVersion", "audience", "review", "evidence", "metadata"],
"properties": {
"schemaVersion": { "const": "solver.public-release-attestation/v1", "description": "Attestation contract version." },
"audience": { "const": "public", "description": "Confirms that the reviewed output is approved for a public audience." },
"review": {
"type": "object",
"additionalProperties": false,
"required": ["status", "reviewedAt", "reviewer", "methods", "assertions"],
"properties": {
"status": { "const": "passed", "description": "Fail-closed public privacy review result." },
"reviewedAt": { "type": "string", "format": "date-time", "description": "Time at which the bound evidence was reviewed." },
"reviewer": { "type": "string", "minLength": 1, "description": "Accountable reviewer or release process identity." },
"methods": { "type": "array", "minItems": 1, "items": { "type": "string" }, "description": "Review methods used for transcripts and screenshots." },
"assertions": {
"type": "object",
"additionalProperties": false,
"required": ["transcriptsContainNoSensitiveData", "screenshotsContainNoSensitiveData", "customerIdentifiersExcluded"],
"properties": {
"transcriptsContainNoSensitiveData": { "const": true, "description": "All published transcript text was reviewed for secrets and personal data." },
"screenshotsContainNoSensitiveData": { "const": true, "description": "Every screenshot hash in the bound root was visually reviewed for secrets and personal data." },
"customerIdentifiersExcluded": { "const": true, "description": "Customer names, codenames, and private identifiers are excluded." }
}
}
}
},
"evidence": {
"type": "object",
"additionalProperties": false,
"required": ["publicTranscriptContentRootSha256", "publicScreenshotContentRootSha256", "screenshotPlanSha256", "screenshotValidationSha256", "screenshotPrivacyValidationSha256"],
"properties": {
"publicTranscriptContentRootSha256": { "type": "string", "pattern": "^[a-f0-9]{64}$", "description": "Canonical root hash of every public user and Agent message." },
"publicScreenshotContentRootSha256": { "type": "string", "pattern": "^[a-f0-9]{64}$", "description": "Canonical root hash binding every reviewed screenshot path, digest, and dimension." },
"screenshotPlanSha256": { "type": "string", "pattern": "^[a-f0-9]{64}$", "description": "Canonical screenshot plan digest." },
"screenshotValidationSha256": { "type": "string", "pattern": "^[a-f0-9]{64}$", "description": "Canonical screenshot validation digest." },
"screenshotPrivacyValidationSha256": { "type": "string", "pattern": "^[a-f0-9]{64}$", "description": "Canonical digest of the full-resolution OCR privacy validation bound to every public screenshot." }
}
},
"metadata": {
"type": "object",
"additionalProperties": false,
"required": ["platformCommit", "agentSkillVersion", "representativeStabilityPasses"],
"properties": {
"platformCommit": { "type": "string", "minLength": 7, "description": "Platform commit that owns the historical evidence." },
"agentSkillVersion": { "type": "string", "minLength": 1, "description": "Solver Agent Skill version that produced the historical evidence." },
"representativeStabilityPasses": { "type": "integer", "minimum": 0, "description": "Number of representative stability passes supported by the evidence." }
}
}
}
}

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)

View File

@@ -0,0 +1,107 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover" />
<meta name="color-scheme" content="light" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src-elem 'self'; style-src-attr 'unsafe-inline'; font-src 'self'; img-src 'self' data:; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'none'"
/>
<link
rel="icon"
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='16' fill='%23101b35'/%3E%3Ctext x='32' y='44' text-anchor='middle' font-size='40' fill='%2358d4c7'%3EΣ%3C/text%3E%3C/svg%3E"
/>
<title>通用求解器智能体团队 · 真实场景验证书</title>
<link rel="stylesheet" href="assets/katex.min.css" />
<link rel="stylesheet" href="assets/report.css" />
</head>
<body>
<a class="skip-link" href="#chapter">跳到当前章节</a>
<div id="app" class="app-shell" aria-busy="true">
<aside class="rail" aria-label="验证书目录">
<div class="brand">
<span class="brand-mark">Σ</span>
<div><strong>Solver Guild</strong><small>真实场景验证书</small></div>
</div>
<div class="rail-summary" id="rail-summary"></div>
<label class="search"
><span>搜索章节</span><input id="chapter-search" type="search" placeholder="场景、问题族、引擎"
/></label>
<div class="filters" id="filters" aria-label="问题族筛选"></div>
<nav id="chapter-nav" class="chapter-nav" aria-label="场景章节"></nav>
<div class="rail-foot"><span id="build-id"></span><span>零埋点 · 可离线</span></div>
</aside>
<main class="main">
<header class="topbar">
<button id="menu-button" class="icon-button" aria-label="打开目录"></button>
<div class="progress">
<span id="progress-label">载入证据…</span>
<div><i id="progress-bar"></i></div>
</div>
<div class="top-actions">
<button id="latest-button">新版回归</button><button id="print-button">打印 / PDF</button
><button id="integrity-button">完整性</button>
</div>
</header>
<article id="chapter" class="chapter" tabindex="-1"></article>
<footer class="pager">
<button id="prev-button">← 上一章</button><span id="pager-label"></span
><button id="next-button">下一章 →</button>
</footer>
</main>
</div>
<dialog id="lightbox" class="lightbox" aria-labelledby="lightbox-title" aria-describedby="lightbox-meta">
<div class="lightbox-bar">
<div class="lightbox-heading">
<strong id="lightbox-title"></strong><small id="lightbox-meta"></small>
</div>
<button id="lightbox-close" aria-label="关闭原图">×</button>
</div>
<div class="lightbox-viewport">
<div
id="lightbox-stage"
class="lightbox-stage"
tabindex="0"
aria-label="可缩放和平移的截图预览"
aria-describedby="lightbox-zoom-hint"
>
<div id="lightbox-canvas" class="lightbox-canvas">
<img id="lightbox-image" alt="" draggable="false" />
</div>
</div>
<span id="lightbox-zoom-hint" class="lightbox-zoom-hint"
><span class="lightbox-zoom-hint-desktop">双击放大 · Ctrl/⌘ + 滚轮缩放 · 拖动平移</span
><span class="lightbox-zoom-hint-mobile">双指捏合缩放 · 单指拖动平移</span></span
>
</div>
<div class="lightbox-controls">
<button id="lightbox-prev">← 上一张</button><a id="lightbox-original" download>下载原图</a
><div class="lightbox-zoom-controls" role="group" aria-label="图片缩放">
<button id="lightbox-zoom-out" aria-label="缩小图片"></button
><output id="lightbox-zoom" aria-live="polite">适应窗口</output
><button id="lightbox-zoom-in" aria-label="放大图片"></button>
</div>
<button id="lightbox-fit" aria-label="让图片适应窗口">适应</button
><button id="lightbox-actual" aria-label="按原始像素显示图片">1:1</button
><button id="lightbox-next">下一张 →</button>
</div>
</dialog>
<dialog id="integrity-dialog" class="integrity-dialog">
<button class="dialog-close" aria-label="关闭">×</button>
<h2>证据完整性</h2>
<div id="integrity-content"></div>
</dialog>
<dialog id="latest-dialog" class="latest-dialog">
<button class="dialog-close" aria-label="关闭">×</button>
<div id="latest-content"></div>
</dialog>
<noscript
><p class="noscript">
本验证书需要 JavaScript 载入分章证据。所有数据均包含在离线包内,不会连接第三方服务。
</p></noscript
>
<script src="data/manifest.js"></script>
<script src="assets/report.js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,478 @@
;(() => {
'use strict'
const manifest = window.__REPORT_MANIFEST__
if (!manifest) throw new Error('report manifest missing')
window.__REPORT_CHAPTERS__ = window.__REPORT_CHAPTERS__ || {}
const $ = (selector) => document.querySelector(selector)
const esc = (value) =>
String(value ?? '').replace(
/[&<>"']/g,
(char) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[char]
)
const statusTone = (status) =>
['infeasible', 'fail', 'blocked_engine_unavailable'].includes(status) ? 'guard' : 'pass'
const statusLabel = (status) =>
({
optimal: '最优解',
infeasible: '不可行 · 已诊断',
pass: '验证通过',
fail: '验证失败 · 已阻断',
recovered: '恢复通过',
idle: '已完成',
})[status] ||
status ||
'已验证'
const familyLabel = (family) =>
({ lp: 'LP', milp: 'MILP', qp: 'QP', miqp: 'MIQP', qcp: 'QCP', miqcp: 'MIQCP', cp: 'CP' })[
String(family).toLowerCase()
] || String(family).toUpperCase()
const shortHash = (value) => (value ? `${value.slice(0, 10)}${value.slice(-8)}` : '不适用')
const formatTime = (value) => (value ? new Date(value).toLocaleString('zh-CN', { hour12: false }) : '未记录')
const state = { current: 0, filter: 'ALL', query: '', chapter: null, lightboxIndex: 0, visible: manifest.chapters }
const viewer = {
zoom: 1,
fitScale: 1,
mode: 'fit',
pointers: new Map(),
drag: null,
pinch: null,
resizeFrame: 0,
}
const MIN_VIEWER_ZOOM = 0.5
const MAX_VIEWER_ZOOM = 12
const clamp = (value, min, max) => Math.min(max, Math.max(min, value))
const loadScript = (src) =>
new Promise((resolve, reject) => {
const script = document.createElement('script')
script.src = src
script.onload = resolve
script.onerror = reject
document.head.appendChild(script)
})
async function loadChapter(index) {
const item = manifest.chapters[index]
if (!item) return
if (!window.__REPORT_CHAPTERS__[item.scenarioId]) {
if (location.protocol === 'file:') await loadScript(item.script)
else {
const response = await fetch(item.json)
if (!response.ok) throw new Error(`chapter load failed: ${response.status}`)
window.__REPORT_CHAPTERS__[item.scenarioId] = await response.json()
}
}
state.current = index
state.chapter = window.__REPORT_CHAPTERS__[item.scenarioId]
renderChapter()
renderNav()
updatePager()
updateHash()
$('#chapter').focus({ preventScroll: true })
window.scrollTo({ top: 0, behavior: 'instant' })
}
function updateHash() {
const desired = `#case-${state.current + 1}`
if (location.hash !== desired) history.replaceState(null, '', desired)
}
function hashIndex() {
const caseMatch = /^#case-(\d+)$/.exec(location.hash)
if (caseMatch) return Math.max(0, Math.min(manifest.chapters.length - 1, Number(caseMatch[1]) - 1))
const id = decodeURIComponent(location.hash.slice(1))
const index = manifest.chapters.findIndex((item) => item.scenarioId === id)
return index >= 0 ? index : 0
}
function renderShell() {
$('#rail-summary').innerHTML =
`<span><b>${manifest.counts.chapters}</b>场景</span><span><b>${manifest.counts.screenshots}</b>真机图</span><span><b>${manifest.counts.representativeStabilityPasses}</b>稳定运行</span>`
$('#build-id').textContent = manifest.buildId
const families = ['ALL', ...new Set(manifest.chapters.map((item) => familyLabel(item.problemFamily)))]
$('#filters').innerHTML = families
.map(
(family) =>
`<button class="filter${family === 'ALL' ? ' active' : ''}" data-family="${esc(family)}">${family === 'ALL' ? '全部' : family}</button>`
)
.join('')
$('#filters').addEventListener('click', (event) => {
const button = event.target.closest('[data-family]')
if (!button) return
state.filter = button.dataset.family
document.querySelectorAll('.filter').forEach((item) => item.classList.toggle('active', item === button))
applyNavFilter()
})
$('#chapter-search').addEventListener('input', (event) => {
state.query = event.target.value.trim().toLowerCase()
applyNavFilter()
})
$('#print-button').addEventListener('click', () => window.print())
$('#menu-button').addEventListener('click', () => $('.rail').classList.toggle('open'))
$('#prev-button').addEventListener('click', () =>
loadChapter((state.current - 1 + manifest.chapters.length) % manifest.chapters.length)
)
$('#next-button').addEventListener('click', () => loadChapter((state.current + 1) % manifest.chapters.length))
$('#latest-button').addEventListener('click', openLatestRegression)
$('#integrity-button').addEventListener('click', openIntegrity)
$('#integrity-dialog .dialog-close').addEventListener('click', () => $('#integrity-dialog').close())
$('#latest-dialog .dialog-close').addEventListener('click', () => $('#latest-dialog').close())
renderNav()
}
function renderLatestRegressionBanner() {
const regression = manifest.latestPlatformRegression
if (!regression) return ''
const scenarios = regression.runtime?.scenarios ?? []
const passed = scenarios.filter((item) => item.status === 'passed').length
return `<section class="regression-banner"><div class="regression-mark">NEW</div><div class="regression-copy"><small>最新平台定向回归 · ${esc(formatTime(regression.testedAt))}</small><strong>${esc(regression.platform.commit.slice(0, 10))} 已通过 ${passed}/${scenarios.length} 个真实场景与决策工作台真机检查</strong><span>下方 24 章仍保持原始证据提交 ${esc(manifest.evidencePlatformCommit)} 的归属,不把历史截图冒充为新版重跑。</span></div><button type="button" data-open-regression>查看范围与结论</button></section>`
}
function applyNavFilter() {
state.visible = manifest.chapters.filter((item) => {
const familyOk = state.filter === 'ALL' || familyLabel(item.problemFamily) === state.filter
const haystack = `${item.title} ${item.story} ${item.problemFamily} ${item.engineId}`.toLowerCase()
return familyOk && (!state.query || haystack.includes(state.query))
})
renderNav()
}
function renderNav() {
$('#chapter-nav').innerHTML =
state.visible
.map((item) => {
const actualIndex = manifest.chapters.indexOf(item)
return `<button class="nav-item${actualIndex === state.current ? ' active' : ''}" data-index="${actualIndex}"><span class="nav-index">${String(item.order).padStart(2, '0')}</span><span class="nav-copy"><strong>${esc(item.title)}</strong><small>${familyLabel(item.problemFamily)} · ${esc(item.engineId || '独立校核')}</small></span><i class="nav-status ${statusTone(item.resultStatus)}"></i></button>`
})
.join('') || '<p class="empty">没有匹配的章节</p>'
$('#chapter-nav')
.querySelectorAll('[data-index]')
.forEach((button) =>
button.addEventListener('click', () => {
$('.rail').classList.remove('open')
loadChapter(Number(button.dataset.index))
})
)
$('#chapter-nav .active')?.scrollIntoView({ block: 'nearest' })
}
function renderTimeline(chapter) {
const visible = chapter.timeline.filter((item) => item.kind === 'tool' || !item.empty)
const finalMessageSequence = Math.max(
...visible.filter((item) => item.kind === 'message' && item.role === 'assistant').map((item) => item.sequence)
)
return visible
.map((item) => {
if (item.kind === 'tool')
return `<div class="tool-event"><span>受治理工具</span><strong>${esc(item.tool)}</strong><span class="status ${item.status === 'success' ? 'pass' : 'guard'}">${esc(item.status)}</span>${item.durationMs == null ? '' : `<span>${item.durationMs} ms</span>`}</div>`
const isFinal = item.role === 'assistant' && item.sequence === finalMessageSequence
const body = isFinal
? `<details class="full-final"><summary>智能体最终完整报告(原始 Markdown 1:1 渲染)</summary><div class="markdown">${item.html}</div></details>`
: `<div class="message-body markdown">${item.html}</div>`
return `<div class="message ${item.role}"><div class="message-meta"><strong>${item.role === 'user' ? '用户' : '求解决策顾问'}</strong><span>消息 ${item.sequence} · SHA ${shortHash(item.markdownSha256)}</span></div>${body}</div>`
})
.join('')
}
function renderChapter() {
const c = state.chapter
const tone = statusTone(c.resultStatus)
const hard = c.validation?.satisfaction_report?.hard_constraints
const soft = c.validation?.satisfaction_report?.soft_constraints
$('#chapter').innerHTML = `${renderLatestRegressionBanner()}
<section class="chapter-hero">
<div class="eyebrow"><span>CHAPTER ${String(c.scenarioOrder).padStart(2, '0')}</span><span class="badge">${familyLabel(c.problemFamily)}</span><span class="badge">${statusLabel(c.resultStatus)}</span><span class="badge">${esc(c.claimBoundary)}</span></div>
<h1>${esc(c.title)}</h1><p class="story">${esc(c.story)}</p>
<div class="hero-grid"><div class="hero-card"><small>决策问题</small><strong>${esc(c.decision)}</strong></div><div class="hero-card"><small>专业结论</small><strong>${esc(c.professionalSummary)}</strong></div></div>
</section>
<section class="kpis">
<div class="kpi"><small>问题族</small><strong>${familyLabel(c.problemFamily)}</strong></div>
<div class="kpi"><small>实际引擎</small><strong>${esc(c.engineId || '独立校核')}</strong></div>
<div class="kpi"><small>结果 / 校核</small><strong class="status ${tone}">${statusLabel(c.resultStatus)} / ${esc(c.validationVerdict || '不适用')}</strong></div>
<div class="kpi"><small>真实消息 / 工具</small><strong>${c.counts.rawConversationMessages} / ${c.counts.toolEvents}</strong></div>
<div class="kpi"><small>真机截图</small><strong>${c.counts.screenshots} 张 · 2x</strong></div>
</section>
<section class="section-shell"><div class="section-head"><div><h2>场景故事与意义</h2><p>为什么这个问题具有代表性,以及哪些业务规则决定可行性。</p></div><span class="evidence-tag">PACK ${esc(c.scenarioVersion)} · ${shortHash(c.contentSha256)}</span></div>
<div class="hero-grid"><div class="validation-card"><small>目标</small><strong>${c.objectives.map(esc).join('')}</strong></div><div class="validation-card"><small>基线</small><strong>${esc(c.baseline)}</strong></div></div>
<div class="validation-card" style="margin-top:12px"><small>硬规则</small><strong>${c.constraints.map(esc).join('')}</strong></div>
</section>
<section class="section-shell"><div class="section-head"><div><h2>真实多轮对话</h2><p>普通消息逐条来自 messages.jsonl工具事件来自持久回执未用离线脚本改写对话。</p></div><span class="evidence-tag">RUN ${shortHash(c.evidence.runId)}</span></div><div class="timeline">${renderTimeline(c)}</div></section>
<section class="section-shell"><div class="section-head"><div><h2>智能体最终十部分报告</h2><p>十个 section ID、数量和顺序固定正文由最终消息 Markdown 原样构建。</p></div><span class="evidence-tag">${c.formulaStats.rendered} FORMULAS · HTML+MATHML</span></div><div class="report-grid">${c.sections.map((section) => `<section class="report-part" id="${section.id}"><div class="part-number">${section.number}</div><div class="part-body"><h3>${esc(section.title)}</h3><div class="markdown">${section.html}</div></div></section>`).join('')}</div></section>
<section class="section-shell"><div class="section-head"><div><h2>专业校核与稳定性</h2><p>求解结果、独立验证、等价 formulation 和重复运行分别留证。</p></div><span class="evidence-tag">${esc(c.stability.policy)}</span></div>
<div class="validation-grid"><div class="validation-card"><small>硬约束</small><strong>${hard ? `${hard.satisfied}/${hard.total} 满足,最大违反 ${hard.max_violation}` : '诊断/候选模式,详见第 8 节'}</strong></div><div class="validation-card"><small>软约束</small><strong>${soft ? `${soft.violated}/${soft.total} 违反,罚分 ${soft.weighted_penalty_sum}` : '无或不适用'}</strong></div><div class="validation-card"><small>稳定性</small><strong>${c.stability.effectivePasses} 次有效通过${c.stability.semanticConvergence ? ',语义收敛' : ''}</strong></div></div>
<div class="validation-grid" style="margin-top:12px"><div class="validation-card"><small>OptimizationSpec</small><strong>${shortHash(c.evidence.optimizationSpecSha256)}</strong></div><div class="validation-card"><small>Result payload</small><strong>${shortHash(c.evidence.resultPayloadSha256)}</strong></div><div class="validation-card"><small>Validation report</small><strong>${shortHash(c.evidence.validationReportSha256)}</strong></div></div>
</section>
<section class="section-shell"><div class="section-head"><div><h2>真机完整过程</h2><p>DesireCore 原生窗口 backing store应用尺寸不变点击可查看 3024×1824 原图。</p></div><span class="evidence-tag">DEVTOOLS CLOSED</span></div><div class="gallery">${c.media.map((shot, index) => `<button type="button" class="shot" data-shot="${index}" aria-haspopup="dialog" aria-label="查看原图:${esc(shot.alt)}"><picture><source srcset="${shot.thumb1512} 1512w, ${shot.thumb756} 756w" type="image/webp"><img src="${shot.thumb756}" loading="lazy" decoding="async" width="756" height="456" alt="${esc(shot.alt)}"></picture><span class="shot-caption"><span>${index + 1}/${c.media.length} · ${esc(shot.stage)}</span><span>${shortHash(shot.sha256)}</span></span></button>`).join('')}</div></section>`
$('#chapter')
.querySelectorAll('[data-shot]')
.forEach((item) => {
item.addEventListener('click', () => openLightbox(Number(item.dataset.shot)))
item.addEventListener('keydown', (event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault()
openLightbox(Number(item.dataset.shot))
}
})
})
$('#chapter [data-open-regression]')?.addEventListener('click', openLatestRegression)
document.title = `${String(c.scenarioOrder).padStart(2, '0')} · ${c.title}${manifest.title}`
$('#app').setAttribute('aria-busy', 'false')
}
function updatePager() {
$('#progress-label').textContent = `${state.current + 1} / ${manifest.chapters.length}`
$('#progress-bar').style.width = `${((state.current + 1) / manifest.chapters.length) * 100}%`
$('#pager-label').textContent = state.chapter.title
}
function currentShot() {
return state.chapter?.media?.[state.lightboxIndex] ?? null
}
function calculateFitScale() {
const shot = currentShot()
const stage = $('#lightbox-stage')
if (!shot || !stage.clientWidth || !stage.clientHeight) return 1
const inset = window.matchMedia('(max-width: 920px)').matches ? 16 : 36
return Math.min(
Math.max(1, stage.clientWidth - inset) / shot.width,
Math.max(1, stage.clientHeight - inset) / shot.height,
1
)
}
function updateViewerStatus() {
const stage = $('#lightbox-stage')
const image = $('#lightbox-image')
const actualScale = viewer.fitScale * viewer.zoom
const percent = Math.max(1, Math.round(actualScale * 100))
$('#lightbox-zoom').textContent =
viewer.mode === 'fit' ? `${percent}% · 适应` : viewer.mode === 'actual' ? '100% · 1:1' : `${percent}%`
$('#lightbox-zoom-out').disabled = viewer.zoom <= MIN_VIEWER_ZOOM + 0.001
$('#lightbox-zoom-in').disabled = viewer.zoom >= MAX_VIEWER_ZOOM - 0.001
$('#lightbox-fit').setAttribute('aria-pressed', String(viewer.mode === 'fit'))
$('#lightbox-actual').setAttribute('aria-pressed', String(viewer.mode === 'actual'))
const rect = image.getBoundingClientRect()
stage.classList.toggle('is-pannable', rect.width > stage.clientWidth + 1 || rect.height > stage.clientHeight + 1)
}
function setViewerZoom(nextZoom, options = {}) {
const shot = currentShot()
const stage = $('#lightbox-stage')
const image = $('#lightbox-image')
if (!shot || !image.complete || !image.naturalWidth) return
const before = image.getBoundingClientRect()
const stageRect = stage.getBoundingClientRect()
const focusClientX = options.clientX ?? stageRect.left + stage.clientWidth / 2
const focusClientY = options.clientY ?? stageRect.top + stage.clientHeight / 2
const imageRatioX = before.width ? clamp((focusClientX - before.left) / before.width, 0, 1) : 0.5
const imageRatioY = before.height ? clamp((focusClientY - before.top) / before.height, 0, 1) : 0.5
viewer.zoom = clamp(nextZoom, MIN_VIEWER_ZOOM, MAX_VIEWER_ZOOM)
viewer.mode = options.mode ?? 'manual'
const actualScale = viewer.fitScale * viewer.zoom
image.style.width = `${Math.max(1, Math.round(shot.width * actualScale))}px`
image.style.height = `${Math.max(1, Math.round(shot.height * actualScale))}px`
const after = image.getBoundingClientRect()
stage.scrollLeft += after.left + imageRatioX * after.width - focusClientX
stage.scrollTop += after.top + imageRatioY * after.height - focusClientY
updateViewerStatus()
}
function resetViewer(mode = 'fit') {
const shot = currentShot()
const image = $('#lightbox-image')
const stage = $('#lightbox-stage')
if (!shot || !image.complete || !image.naturalWidth) return
viewer.fitScale = calculateFitScale()
const nextZoom = mode === 'actual' ? 1 / viewer.fitScale : 1
viewer.zoom = clamp(nextZoom, MIN_VIEWER_ZOOM, MAX_VIEWER_ZOOM)
viewer.mode = mode === 'actual' && viewer.zoom !== nextZoom ? 'manual' : mode
const actualScale = viewer.fitScale * viewer.zoom
image.style.width = `${Math.max(1, Math.round(shot.width * actualScale))}px`
image.style.height = `${Math.max(1, Math.round(shot.height * actualScale))}px`
stage.scrollTo({ left: 0, top: 0 })
updateViewerStatus()
}
function zoomViewerBy(factor, event) {
setViewerZoom(viewer.zoom * factor, {
clientX: event?.clientX,
clientY: event?.clientY,
mode: 'manual',
})
}
function openLightbox(index) {
state.lightboxIndex = index
const shot = state.chapter.media[index]
$('#lightbox-title').textContent = `${state.chapter.title} · ${index + 1}/${state.chapter.media.length}`
$('#lightbox-meta').textContent = `${shot.width}×${shot.height} · SHA-256 ${shot.sha256}`
const image = $('#lightbox-image')
image.alt = shot.alt
image.onload = () => resetViewer('fit')
image.src = shot.original
$('#lightbox-original').href = shot.original
if (!$('#lightbox').open) $('#lightbox').showModal()
if (image.complete && image.naturalWidth) requestAnimationFrame(() => resetViewer('fit'))
}
$('#lightbox-close').addEventListener('click', () => $('#lightbox').close())
$('#lightbox-prev').addEventListener('click', () =>
openLightbox((state.lightboxIndex - 1 + state.chapter.media.length) % state.chapter.media.length)
)
$('#lightbox-next').addEventListener('click', () =>
openLightbox((state.lightboxIndex + 1) % state.chapter.media.length)
)
$('#lightbox-zoom-out').addEventListener('click', () => zoomViewerBy(1 / 1.25))
$('#lightbox-zoom-in').addEventListener('click', () => zoomViewerBy(1.25))
$('#lightbox-fit').addEventListener('click', () => resetViewer('fit'))
$('#lightbox-actual').addEventListener('click', () => resetViewer('actual'))
$('#lightbox-stage').addEventListener(
'wheel',
(event) => {
if (!event.ctrlKey && !event.metaKey) return
event.preventDefault()
zoomViewerBy(event.deltaY < 0 ? 1.18 : 1 / 1.18, event)
},
{ passive: false }
)
$('#lightbox-stage').addEventListener('dblclick', (event) => {
if (viewer.mode === 'fit') setViewerZoom(2, { clientX: event.clientX, clientY: event.clientY })
else resetViewer('fit')
})
$('#lightbox-stage').addEventListener('pointerdown', (event) => {
if (event.pointerType === 'mouse' && event.button !== 0) return
const stage = $('#lightbox-stage')
if (viewer.pointers.size >= 2) return
viewer.pointers.set(event.pointerId, { x: event.clientX, y: event.clientY })
stage.setPointerCapture?.(event.pointerId)
if (viewer.pointers.size === 2) {
const [first, second] = [...viewer.pointers.values()]
viewer.pinch = {
distance: Math.hypot(second.x - first.x, second.y - first.y),
zoom: viewer.zoom,
}
viewer.drag = null
stage.classList.remove('is-dragging')
} else if (stage.classList.contains('is-pannable')) {
viewer.drag = {
pointerId: event.pointerId,
x: event.clientX,
y: event.clientY,
left: stage.scrollLeft,
top: stage.scrollTop,
}
stage.classList.add('is-dragging')
}
})
$('#lightbox-stage').addEventListener('pointermove', (event) => {
if (!viewer.pointers.has(event.pointerId)) return
const stage = $('#lightbox-stage')
viewer.pointers.set(event.pointerId, { x: event.clientX, y: event.clientY })
if (viewer.pointers.size >= 2 && viewer.pinch) {
const [first, second] = [...viewer.pointers.values()]
const distance = Math.hypot(second.x - first.x, second.y - first.y)
setViewerZoom(viewer.pinch.zoom * (distance / Math.max(1, viewer.pinch.distance)), {
clientX: (first.x + second.x) / 2,
clientY: (first.y + second.y) / 2,
})
return
}
if (viewer.drag?.pointerId === event.pointerId) {
stage.scrollLeft = viewer.drag.left - (event.clientX - viewer.drag.x)
stage.scrollTop = viewer.drag.top - (event.clientY - viewer.drag.y)
}
})
const releaseViewerPointer = (event) => {
const stage = $('#lightbox-stage')
if (!viewer.pointers.has(event.pointerId)) return
viewer.pointers.delete(event.pointerId)
viewer.drag = null
viewer.pinch = null
stage.classList.remove('is-dragging')
if (viewer.pointers.size === 1 && stage.classList.contains('is-pannable')) {
const [[pointerId, point]] = [...viewer.pointers.entries()]
viewer.drag = {
pointerId,
x: point.x,
y: point.y,
left: stage.scrollLeft,
top: stage.scrollTop,
}
stage.classList.add('is-dragging')
}
}
$('#lightbox-stage').addEventListener('pointerup', releaseViewerPointer)
$('#lightbox-stage').addEventListener('pointercancel', releaseViewerPointer)
$('#lightbox-stage').addEventListener('lostpointercapture', releaseViewerPointer)
$('#lightbox').addEventListener('keydown', (event) => {
if (event.key === 'ArrowLeft') $('#lightbox-prev').click()
else if (event.key === 'ArrowRight') $('#lightbox-next').click()
else if (event.key === '+' || event.key === '=') zoomViewerBy(1.25)
else if (event.key === '-') zoomViewerBy(1 / 1.25)
else if (event.key === '0') resetViewer('fit')
else if (event.key === '1') resetViewer('actual')
else return
event.preventDefault()
})
$('#lightbox').addEventListener('close', () => {
viewer.pointers.clear()
viewer.drag = null
viewer.pinch = null
$('#lightbox-stage').classList.remove('is-dragging', 'is-pannable')
})
window.addEventListener('resize', () => {
if (!$('#lightbox').open) return
cancelAnimationFrame(viewer.resizeFrame)
viewer.resizeFrame = requestAnimationFrame(() => {
const previousActualScale = viewer.fitScale * viewer.zoom
viewer.fitScale = calculateFitScale()
const nextZoom =
viewer.mode === 'fit' ? 1 : viewer.mode === 'actual' ? 1 / viewer.fitScale : previousActualScale / viewer.fitScale
setViewerZoom(nextZoom, { mode: viewer.mode })
})
})
async function openIntegrity() {
let integrity
if (location.protocol === 'file:')
integrity = {
entries: [],
note: '离线模式下逐文件哈希见 integrity.json',
contentRootSha256: manifest.contentRootSha256,
}
else integrity = await (await fetch('integrity.json')).json()
$('#integrity-content').innerHTML =
`<div class="integrity-list"><div class="integrity-row"><strong>报告内容根哈希</strong><code>${esc(manifest.contentRootSha256)}</code></div><div class="integrity-row"><strong>历史证据根哈希</strong><code>${esc(manifest.historicalEvidenceContentRootSha256)}</code></div><div class="integrity-row"><strong>最新版回归哈希</strong><code>${esc(manifest.latestPlatformRegressionSha256)}</code></div><div class="integrity-row"><strong>章节</strong><span>${manifest.counts.chapters}</span></div><div class="integrity-row"><strong>截图</strong><span>${manifest.counts.screenshots}</span></div><div class="integrity-row"><strong>完整性条目</strong><span>${integrity.entries?.length || '见离线 integrity.json'}</span></div><div class="integrity-row"><strong>历史证据平台 / Skill</strong><span>${esc(manifest.evidencePlatformCommit)} / ${esc(manifest.agentSkillVersion)}</span></div><div class="integrity-row"><strong>最新回归平台</strong><span>${esc(manifest.latestPlatformRegression.platform.commit)}</span></div></div>`
$('#integrity-dialog').showModal()
}
function openLatestRegression() {
const regression = manifest.latestPlatformRegression
const scenarios = regression.runtime.scenarios
.map((item) => {
const verdict =
item.expectedValidationVerdict === 'fail' && item.observedValidationVerdict === 'fail'
? '违规候选按预期被阻断'
: `独立验证 ${item.observedValidationVerdict}`
return `<article class="regression-case"><div><span class="status pass">通过</span><small>${esc(item.scenarioId)}</small></div><h3>${esc(item.label)}</h3><p>${esc(verdict)} · Solve ${item.settledSolveCalls} 次 · 十部分报告 ${item.tenPartReport ? '完整' : '不完整'}</p><code>${esc(shortHash(item.caseRunSha256))}</code></article>`
})
.join('')
const workspace = regression.runtime.decisionWorkspace
$('#latest-content').innerHTML =
`<div class="regression-dialog-head"><span class="status pass">PASS</span><p>最新平台定向回归</p><h2>${esc(regression.platform.commit.slice(0, 10))} · DesireCore ${esc(regression.platform.version)}</h2><small>${esc(formatTime(regression.testedAt))} · ${esc(regression.platform.source)}</small></div><div class="regression-cases">${scenarios}<article class="regression-case"><div><span class="status pass">通过</span><small>Decision Workspace r${workspace.revision}</small></div><h3>人 + Agent 共管决策工作台</h3><p>业务、模型、证据三视图可用;定义通过,缺少模型映射时保持阻断;${workspace.viewport.join('×')} 无横向溢出。</p><code>review: ${esc(workspace.reviewState)}</code></article></div><section class="regression-boundary"><h3>证据边界</h3><ul>${regression.claimBoundary.map((item) => `<li>${esc(item)}</li>`).join('')}</ul><p><strong>历史全量证据:</strong>${regression.historicalEvidence.chapters} 章、${regression.historicalEvidence.screenshots} 张截图、${regression.historicalEvidence.representativeStabilityPasses} 次代表性稳定运行,归属于平台 ${esc(regression.historicalEvidence.platformCommit)}。</p></section>`
$('#latest-dialog').showModal()
}
window.addEventListener('hashchange', () => {
const index = hashIndex()
if (index !== state.current) loadChapter(index)
})
renderShell()
loadChapter(hashIndex()).catch((error) => {
$('#chapter').innerHTML =
`<section class="section-shell"><h1>报告载入失败</h1><pre>${esc(error.stack || error.message)}</pre></section>`
throw error
})
})()

View File

@@ -0,0 +1,132 @@
import assert from 'node:assert/strict'
import { mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync } from 'node:fs'
import { join } from 'node:path'
import { tmpdir } from 'node:os'
import test from 'node:test'
import {
assertPublicText,
projectPublicProvenance,
resolveSafeRemoteEntryUrl,
resolveSafeOutputRoot,
validatePublicReleaseAttestation,
} from '../scripts/public-release-policy.mjs'
test('输出目录只能位于证据 report-web 的真实专用子目录', (context) => {
const sandbox = mkdtempSync(join(tmpdir(), 'solver-report-path-policy-'))
context.after(() => rmSync(sandbox, { recursive: true, force: true }))
const roots = {
inputRoot: join(sandbox, 'evidence'),
codeRoot: join(sandbox, 'agent-desirecore'),
appRoot: join(sandbox, 'desirecore-app'),
}
for (const path of [...Object.values(roots), join(roots.inputRoot, 'report-web')]) mkdirSync(path, { recursive: true })
const safeOutput = join(roots.inputRoot, 'report-web', 'dist')
assert.equal(
resolveSafeOutputRoot({ ...roots, requestedOutputRoot: safeOutput }),
join(realpathSync(join(roots.inputRoot, 'report-web')), 'dist')
)
for (const requestedOutputRoot of [
'/',
sandbox,
roots.inputRoot,
join(roots.inputRoot, 'report-web'),
join(roots.codeRoot, 'web-output'),
join(sandbox, 'unmanaged-report-output'),
]) {
assert.throws(() => resolveSafeOutputRoot({ ...roots, requestedOutputRoot: requestedOutputRoot }))
}
const symlinkInputRoot = join(sandbox, 'symlink-evidence')
const externalTarget = join(sandbox, 'external-target')
mkdirSync(symlinkInputRoot)
mkdirSync(externalTarget)
symlinkSync(externalTarget, join(symlinkInputRoot, 'report-web'))
assert.throws(() =>
resolveSafeOutputRoot({
inputRoot: symlinkInputRoot,
codeRoot: roots.codeRoot,
appRoot: roots.appRoot,
requestedOutputRoot: join(symlinkInputRoot, 'report-web', 'dist'),
})
)
})
test('远端完整性条目不能改变 origin 或越过报告路径', () => {
assert.equal(
resolveSafeRemoteEntryUrl('https://build.example.com/solver-agent-team/', 'media/original/case/01@2x.png').href,
'https://build.example.com/solver-agent-team/media/original/case/01@2x.png'
)
for (const path of [
'https://127.0.0.1/private',
'http://evil.example/private',
'../admin',
'/absolute',
'media\\secret',
'media/%2e%2e/secret',
'media//secret',
'media/file?redirect=http://127.0.0.1',
]) {
assert.throws(() => resolveSafeRemoteEntryUrl('https://build.example.com/solver-agent-team/', path))
}
})
test('provenance 使用递归数据不可穿透的字段白名单', () => {
assert.deepEqual(
projectPublicProvenance({
engine_id: 'scip-build',
request_id: 'request-1',
endpoint: 'https://private.internal',
nested: { access_token: 'secret-value' },
}),
{ engine_id: 'scip-build', request_id: 'request-1' }
)
})
test('公开文本策略拒绝身份、凭据、私有路径和私有网络地址', () => {
assert.doesNotThrow(() => assertPublicText('SCIP 10.0.2 · request 853ca9c7-440b', 'safe'))
for (const value of [
'admin@example.com',
'/Users/reviewer/private/report.json',
'authorization: Bearer abcdefghijklmnop',
'password=not-for-public',
'http://192.168.1.10/private',
]) {
assert.throws(() => assertPublicText(value, 'unsafe'))
}
})
test('公开发布证明必须绑定全部证据根并显式通过', () => {
const evidenceRoots = Object.fromEntries(
[
'publicTranscriptContentRootSha256',
'publicScreenshotContentRootSha256',
'screenshotPlanSha256',
'screenshotValidationSha256',
'screenshotPrivacyValidationSha256',
].map((key, index) => [key, String(index + 1).repeat(64)])
)
const attestation = {
schemaVersion: 'solver.public-release-attestation/v1',
audience: 'public',
review: {
status: 'passed',
reviewedAt: '2026-08-30T09:00:00.000Z',
reviewer: 'release-review',
methods: ['text-scan', 'visual-review'],
assertions: {
transcriptsContainNoSensitiveData: true,
screenshotsContainNoSensitiveData: true,
customerIdentifiersExcluded: true,
},
},
evidence: evidenceRoots,
metadata: { platformCommit: 'be0cc2cce', agentSkillVersion: '4.4.4', representativeStabilityPasses: 20 },
}
assert.equal(validatePublicReleaseAttestation(attestation, evidenceRoots), attestation)
assert.throws(() =>
validatePublicReleaseAttestation(
{ ...attestation, evidence: { ...evidenceRoots, screenshotPlanSha256: '0'.repeat(64) } },
evidenceRoots
)
)
})

View File

@@ -0,0 +1,86 @@
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import { join } from 'node:path'
import test from 'node:test'
import { fileURLToPath } from 'node:url'
const root = join(fileURLToPath(new URL('..', import.meta.url)))
const html = readFileSync(join(root, 'source', 'index.html'), 'utf8')
const css = readFileSync(join(root, 'source', 'report.css'), 'utf8')
const javascript = readFileSync(join(root, 'source', 'report.js'), 'utf8')
const builder = readFileSync(join(root, 'scripts', 'build-report-web.mjs'), 'utf8')
const ocrValidator = readFileSync(join(root, 'scripts', 'validate-screenshot-privacy-ocr.mjs'), 'utf8')
const deploymentValidator = readFileSync(join(root, 'scripts', 'validate-deployed-report.mjs'), 'utf8')
const attestationSchema = JSON.parse(
readFileSync(join(root, 'public-release-attestation.schema.json'), 'utf8')
)
test('图片查看器暴露完整的可访问缩放控制', () => {
for (const id of [
'lightbox-stage',
'lightbox-canvas',
'lightbox-zoom-out',
'lightbox-zoom',
'lightbox-zoom-in',
'lightbox-fit',
'lightbox-actual',
]) {
assert.match(html, new RegExp(`id="${id}"`))
}
assert.match(html, /aria-live="polite"/)
assert.match(html, /aria-describedby="lightbox-zoom-hint"/)
assert.match(javascript, /<button type="button" class="shot"/)
assert.match(javascript, /aria-haspopup="dialog"/)
assert.match(javascript, /event\.preventDefault\(\)/)
})
test('图片查看器实现按钮、键盘、滚轮、双击和触控指针路径', () => {
assert.match(javascript, /lightbox-zoom-in/)
assert.match(javascript, /event\.key === '\+'/)
assert.match(javascript, /addEventListener\(\s*'wheel'/)
assert.match(javascript, /addEventListener\('dblclick'/)
assert.match(javascript, /addEventListener\('pointerdown'/)
assert.match(javascript, /viewer\.pointers\.size === 2/)
assert.match(javascript, /viewer\.pointers\.size >= 2/)
assert.match(javascript, /lostpointercapture/)
assert.match(javascript, /MIN_VIEWER_ZOOM/)
assert.match(javascript, /MAX_VIEWER_ZOOM/)
})
test('响应式样式约束全屏查看器且允许平移', () => {
assert.match(css, /\.lightbox-stage\.is-pannable/)
assert.match(css, /touch-action:\s*none/)
assert.match(css, /width:\s*100vw/)
assert.match(css, /height:\s*100dvh/)
})
test('构建器从 Agent 仓库读取代码、从显式目录读取证据', () => {
assert.match(builder, /SOLVER_REPORT_INPUT_ROOT/)
assert.match(builder, /SOLVER_REPORT_OUTPUT_ROOT/)
assert.match(builder, /const sourceRoot = join\(codeRoot, 'source'\)/)
assert.match(builder, /loadPublicReleaseAttestation/)
assert.match(builder, /ajv\.compile/)
assert.match(builder, /solver\.public-screenshot-plan\/v1/)
assert.match(builder, /screenshot\.sourceSha256 !== sourceScenario\.screenshots\[index\]\.sha256/)
assert.match(builder, /historicalBuildManifest\.skill\?\.version/)
assert.match(builder, /representativeStabilityPasses = stabilityMatrix\.groups/)
assert.doesNotMatch([html, css, javascript, builder].join('\n'), /\/Users\/|\/Volumes\//)
})
test('公开发布证明 Schema 自描述关键边界', () => {
assert.equal(attestationSchema.$schema, 'http://json-schema.org/draft-07/schema#')
assert.match(attestationSchema.description, /public-release privacy review/)
for (const field of Object.values(attestationSchema.properties)) assert.ok(field.description || field.type === 'object')
})
test('截图隐私审计绑定全部原图哈希并复用公开文本策略', () => {
assert.match(ocrValidator, /planned\.length !== 174/)
assert.match(ocrValidator, /actualSha256 !== expected\.sha256/)
assert.match(ocrValidator, /assertPublicText\(text/)
})
test('公网验证器拒绝 HTTP 重定向后才读取响应', () => {
assert.match(deploymentValidator, /redirect:\s*'manual'/)
assert.match(deploymentValidator, /response\.status >= 300 && response\.status < 400/)
assert.equal([...deploymentValidator.matchAll(/\bfetch\(/g)].length, 1)
})