refactor: 将验证书 Web 移出核心智能体 (#9)

Co-authored-by: yige <yige@yigedeMacBook-Neo.local>
This commit is contained in:
2026-08-31 01:22:53 -04:00
committed by GitHub
parent 3261135305
commit 3890514896
23 changed files with 11 additions and 4795 deletions

5
README.md Normal file
View File

@@ -0,0 +1,5 @@
# DesireCore Core Agent
This repository contains the core DesireCore Agent definition, persona, principles, and skills. [中文说明](./README.zh-CN.md).
The Solver Report Web is maintained independently in [desirecore-agent/solver-report-web](https://github.com/desirecore-agent/solver-report-web), under the **desirecore-agent organization**. Web source, build tools, dependencies, and report evidence do not belong in this Agent repository or its bootstrap archive. The former Web-only history is preserved in the new repository's `archive/web-history` branch.

5
README.zh-CN.md Normal file
View File

@@ -0,0 +1,5 @@
# DesireCore 核心智能体
本仓库仅管理 DesireCore 核心智能体定义、人格、原则和技能。[English](./README.md)。
求解器验证书 Web 由 **desirecore-agent 组织**下的 [desirecore-agent/solver-report-web](https://github.com/desirecore-agent/solver-report-web) 独立管理。Web 源码、构建工具、依赖和报告证据不属于本智能体仓库或其 bootstrap 压缩包。原 Web 专属历史保留在新仓库的 `archive/web-history` 分支。

View File

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

View File

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

View File

@@ -1,72 +0,0 @@
# 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.
## Human + Agent scenario decision trees
Every scenario derives a structured decision tree from its Scenario Pack, accepted Run, and independent validation receipts. The tree shows:
- the business facts, hard rules, and objective priorities confirmed by a human;
- Agent clarification, modeling, and compatible-engine solve or diagnosis steps;
- independent recomputation of variable domains, constraints, objectives, and evidence;
- fail-closed branches for missing facts, incomplete rule mapping, or failed verification;
- the observed delivery, conflict diagnosis, rejection, or recovery outcome.
Tree data follows [`decision-tree.schema.json`](./decision-tree.schema.json) and binds the Scenario Pack, messages, session, governed tool receipts, result payload, OptimizationSpec, and validation-report hashes. The observed path comes only from settled summaries and receipts in the accepted Run; oracle data is used only to detect expectation drift. Optimization, validation-only, and recovery scenarios use different topologies, so the report never invents modeling or solve steps that did not occur. The page can switch between all branches and the observed path, and can download an SVG snapshot of the current scenario. UI code remains a generic renderer and contains no scenario-specific business tree.
## 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

@@ -1,72 +0,0 @@
# 求解器验证书 Web
这里版本化管理“求解器智能体团队验证书”的 Web 源码与构建工具。
本目录负责 Web 应用、交互行为、构建契约、静态验证器和部署验证脚本。场景包、运行回执和原始截图继续作为外部构建输入,避免让 DesireCore 核心 Agent 的 bootstrap 体积增加数百 MB。
## 图片查看器
截图预览支持:
- 适应窗口与 1:1 原始像素模式;
- 有上下限的缩放按钮,以及 `+` / `-` / `0` / `1` 键盘快捷键(移动端也能达到截图的 1:1 原始像素);
- `Ctrl`/`Command` + 滚轮或触控板捏合,以指针位置为中心缩放;
- 双击缩放、鼠标/单指拖动平移;
- 双指触控捏合缩放;
- 原图下载入口和可访问的实时缩放状态。
每次打开新图片都从“适应窗口”开始;关闭后重新打开不会保留旧的缩放或平移状态。
## 人 + Agent 场景决策树
每个场景都会从 Scenario Pack、真实 Run 和独立验证回执派生一棵结构化决策树,展示:
- 人类确认业务事实、硬规则和目标优先级;
- Agent 补问、建模以及调用兼容引擎求解或诊断;
- 独立验证者重新检查变量域、约束、目标和证据;
- 信息不足、规则未映射或校核失败时的 fail-closed 阻断分支;
- 实际运行最终到达的交付、冲突诊断、拒绝或恢复结果。
树数据遵循 [`decision-tree.schema.json`](./decision-tree.schema.json),并绑定 Scenario Pack、消息、session、工具回执、结果 payload、OptimizationSpec 和验证报告哈希。实际路径只由已接受 Run 中的 settled summary 与受治理回执决定oracle 仅用于发现预期不一致;求解、仅验证和恢复场景使用不同拓扑,不为未发生的建模或求解步骤造假。页面可切换“完整分支/实际路径”,也可下载当前场景的 SVG 快照;视图代码只负责通用渲染,不包含具体场景业务树。
## 构建
构建输入目录需要包含 `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

@@ -1,97 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://desirecore.com/schemas/solver-report-decision-tree.schema.json",
"title": "Solver report human-Agent decision tree",
"description": "A scenario-specific, evidence-bound decision tree that explains which facts humans confirm, which reasoning Agents perform, which gates can block delivery, and which path was actually taken.",
"type": "object",
"additionalProperties": false,
"required": ["schemaVersion", "rootNodeId", "nodes", "edges", "observed", "evidenceBindings"],
"properties": {
"schemaVersion": {
"const": "solver.human-agent-decision-tree/v1",
"description": "Version of the stable decision-tree interchange contract."
},
"rootNodeId": {
"type": "string",
"minLength": 1,
"description": "ID of the first node on the scenario decision path."
},
"nodes": {
"type": "array",
"minItems": 1,
"description": "Decision, gate, action, and outcome nodes. Business wording is derived from scenario evidence rather than UI code.",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["id", "kind", "owner", "title", "detail", "status", "evidenceRef"],
"properties": {
"id": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$", "description": "Stable node identifier within one tree." },
"kind": {
"enum": ["context", "question", "action", "check", "outcome"],
"description": "Visual and semantic role of the node."
},
"owner": {
"enum": ["human", "agent", "shared", "validator", "platform"],
"description": "Party responsible for confirming or performing this step."
},
"title": { "type": "string", "minLength": 1, "description": "Plain-language step or question shown to non-algorithm users." },
"detail": { "type": "string", "minLength": 1, "description": "Scenario-specific explanation of the step, rule, or outcome." },
"status": {
"enum": ["pass", "guard", "blocked", "neutral"],
"description": "Observed state in this evidence run; guard and blocked nodes explain non-delivery paths."
},
"evidenceRef": {
"type": "string",
"pattern": "^#[a-z0-9-]+$",
"description": "In-page anchor where a reviewer can inspect the supporting evidence."
}
}
}
},
"edges": {
"type": "array",
"minItems": 1,
"description": "Directed branches between nodes. Exactly one selected path records what happened in the accepted run.",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["from", "to", "label", "tone", "selected"],
"properties": {
"from": { "type": "string", "description": "Source node ID." },
"to": { "type": "string", "description": "Destination node ID." },
"label": { "type": "string", "minLength": 1, "description": "Human-readable branch condition or observed answer." },
"tone": { "enum": ["continue", "guard", "success"], "description": "Branch appearance and governance meaning." },
"selected": { "type": "boolean", "description": "Whether this branch belongs to the observed evidence path." }
}
}
},
"observed": {
"type": "object",
"additionalProperties": false,
"required": ["workflowKind", "runStatus", "solveStatus", "validationVerdict"],
"description": "Observed workflow and verdicts parsed from the accepted Run, settled tool summaries, and receipts; oracle expectations never substitute for these values.",
"properties": {
"workflowKind": { "enum": ["optimization", "validation", "recovery"], "description": "Actual tool path used by this run." },
"runStatus": { "type": "string", "minLength": 1, "description": "Observed case-run acceptance status." },
"solveStatus": { "type": ["string", "null"], "description": "Observed settled solver status, or null when no solve was performed." },
"validationVerdict": { "type": ["string", "null"], "enum": ["pass", "fail", null], "description": "Observed independent validation verdict, or null when the workflow has no validation step." }
}
},
"evidenceBindings": {
"type": "object",
"additionalProperties": false,
"required": ["scenarioPackSha256", "runId", "sourceMessagesSha256", "sourceSessionSha256", "sourceToolInvocationsSha256", "optimizationSpecSha256", "resultPayloadSha256", "validationReportSha256"],
"description": "Immutable bindings that prevent a generic or stale tree from being presented as scenario evidence.",
"properties": {
"scenarioPackSha256": { "type": "string", "pattern": "^[a-f0-9]{64}$", "description": "SHA-256 of the source Scenario Pack." },
"runId": { "type": "string", "minLength": 1, "description": "Accepted run that supplied observed outcomes." },
"sourceMessagesSha256": { "type": "string", "pattern": "^[a-f0-9]{64}$", "description": "SHA-256 of the source conversation log." },
"sourceSessionSha256": { "type": "string", "pattern": "^[a-f0-9]{64}$", "description": "SHA-256 of the session log containing settled solve, validation, or recovery-ledger summaries." },
"sourceToolInvocationsSha256": { "type": "string", "pattern": "^[a-f0-9]{64}$", "description": "SHA-256 of the governed tool receipt log that proves which workflow actually ran." },
"optimizationSpecSha256": { "type": ["string", "null"], "pattern": "^[a-f0-9]{64}$", "description": "OptimizationSpec hash when a solver path exists." },
"resultPayloadSha256": { "type": ["string", "null"], "pattern": "^[a-f0-9]{64}$", "description": "Settled solver-result hash or recovery EvidenceLedger payload hash; null only for validation-only candidate workflows." },
"validationReportSha256": { "type": ["string", "null"], "pattern": "^[a-f0-9]{64}$", "description": "Independent validation report hash when recorded." }
}
}
}
}

View File

@@ -1,15 +0,0 @@
{
"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

@@ -1,56 +0,0 @@
{
"$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

@@ -1,99 +0,0 @@
#!/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

@@ -1,882 +0,0 @@
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 validateDecisionTreeSchema = ajv.compile(
JSON.parse(readFileSync(join(codeRoot, 'decision-tree.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 compactList = (items, limit = 3) => {
const visible = items.slice(0, limit)
return `${visible.join('')}${items.length > limit ? `;另有 ${items.length - limit}` : ''}`
}
const observedStatusLabel = (status) =>
({ optimal: '最优解', infeasible: '不可行', pass: '通过', fail: '拒绝' })[status] ?? String(status)
const buildDecisionTree = ({ pack, packSha256, evidence, workflowKind, observed, problemFamily, engineId }) => {
const hardChecks = pack.oracle?.hardChecks ?? []
const intent = {
id: 'intent',
kind: 'context',
owner: 'shared',
title: pack.businessContract.decision,
detail: pack.businessContract.plainLanguageStory,
status: 'neutral',
evidenceRef: '#scenario-and-decision',
}
let nodes
let edges
if (workflowKind === 'optimization') {
const isInfeasible = observed.solveStatus === 'infeasible'
const isRejected = observed.validationVerdict === 'fail'
nodes = [
intent,
{
id: 'facts', kind: 'question', owner: 'human', title: '业务事实、硬规则与优先级是否已确认?',
detail: `${compactList(pack.businessContract.objectives)}${compactList(pack.businessContract.constraints)}`,
status: 'pass', evidenceRef: '#input-and-provenance',
},
{
id: 'clarify', kind: 'action', owner: 'agent', title: '信息不足:向人类补问',
detail: '缺少决定性事实时保持阻断,不猜测参数、不静默补值。', status: 'guard',
evidenceRef: '#input-and-provenance',
},
{
id: 'model', kind: 'check', owner: 'agent',
title: `规则是否完整映射为 ${String(problemFamily).toUpperCase()} 决策模型?`,
detail: hardChecks.length ? `交付必须覆盖 ${compactList(hardChecks, 4)}` : pack.businessContract.professionalSummary,
status: 'pass', evidenceRef: '#model-definition',
},
{
id: 'mapping-block', kind: 'outcome', owner: 'shared', title: '模型映射不完整:停止求解',
detail: '由人类补充或修正规则Agent 不得以近似模型冒充原问题。', status: 'blocked',
evidenceRef: '#model-definition',
},
{
id: 'candidate', kind: 'action', owner: 'agent',
title: isInfeasible ? `使用 ${engineId} 做可行性诊断` : `使用 ${engineId} 生成候选方案`,
detail: `真实求解回执:${observedStatusLabel(observed.solveStatus)}${observed.solveStatus}${pack.businessContract.baseline}`,
status: isInfeasible ? 'guard' : 'pass', evidenceRef: '#solve-process',
},
{
id: 'verify', kind: 'question', owner: 'validator',
title: isInfeasible ? '不可行结论与冲突集合是否可追溯?' : '独立校核是否接受当前候选?',
detail: hardChecks.length ? `重新检查 ${compactList(hardChecks, 4)}` : '重新计算变量域、硬约束、目标与证据哈希。',
status: isRejected ? 'guard' : 'pass', evidenceRef: '#independent-validation',
},
{
id: 'verification-guard', kind: 'outcome', owner: 'validator', title: '校核证据不足:拒绝交付',
detail: '不采信求解器自报;保留候选、失败检查和哈希后返回 Agent 修正。', status: 'blocked',
evidenceRef: '#independent-validation',
},
{
id: 'outcome', kind: 'outcome', owner: 'human',
title: isRejected ? '阻断违规方案,不进入执行' : isInfeasible ? '交付冲突诊断,等待人类调整规则' : '建议方案交由业务负责人确认',
detail: isRejected ? '独立校核拒绝该候选;保留失败证据供人复核。' : isInfeasible ? '不偷偷删除任务、放宽容量或伪造可行解。' : `${pack.businessContract.objectives.join('')};执行前仍由人类确认适用边界。`,
status: isRejected ? 'blocked' : isInfeasible ? 'guard' : 'pass', evidenceRef: '#reproduction-and-conclusion',
},
]
edges = [
{ from: 'intent', to: 'facts', label: '进入事实确认', tone: 'continue', selected: true },
{ from: 'facts', to: 'clarify', label: '否:缺少决定性信息', tone: 'guard', selected: false },
{ from: 'facts', to: 'model', label: '是:事实门关闭', tone: 'continue', selected: true },
{ from: 'model', to: 'mapping-block', label: '否:存在未映射规则', tone: 'guard', selected: false },
{ from: 'model', to: 'candidate', label: '是:模型可执行', tone: 'continue', selected: true },
{ from: 'candidate', to: 'verify', label: `求解回执:${observedStatusLabel(observed.solveStatus)}`, tone: isInfeasible ? 'guard' : 'continue', selected: true },
{ from: 'verify', to: 'verification-guard', label: '证据不足或对账失败', tone: 'guard', selected: false },
{ from: 'verify', to: 'outcome', label: isRejected ? '校核回执:拒绝' : isInfeasible ? '校核回执:通过,冲突可追溯' : '校核回执:通过', tone: isRejected ? 'guard' : 'success', selected: true },
]
} else if (workflowKind === 'validation') {
const isRejected = observed.validationVerdict === 'fail'
nodes = [
intent,
{
id: 'candidate', kind: 'question', owner: 'human', title: '候选变量、目标声明与适用边界是否齐全?',
detail: `${compactList(pack.businessContract.constraints)}${pack.businessContract.baseline}`,
status: 'pass', evidenceRef: '#input-and-provenance',
},
{
id: 'clarify', kind: 'action', owner: 'agent', title: '候选信息不全:请求补充',
detail: '缺少变量、约束、目标口径或来源时不启动校核。', status: 'guard', evidenceRef: '#input-and-provenance',
},
{
id: 'verify', kind: 'check', owner: 'validator', title: '不重新求解,独立复算候选',
detail: hardChecks.length ? `核对 ${compactList(hardChecks, 4)}` : '核对变量域、硬约束、目标与基线。',
status: isRejected ? 'guard' : 'pass', evidenceRef: '#independent-validation',
},
{
id: 'verification-guard', kind: 'outcome', owner: 'validator', title: '校核证据不足:拒绝下结论',
detail: '验证报告或工具回执缺失时保持阻断。', status: 'blocked', evidenceRef: '#independent-validation',
},
{
id: 'outcome', kind: 'outcome', owner: 'human',
title: isRejected ? '阻断违规方案,不进入执行' : '候选通过校核,交由人类决定是否采用',
detail: isRejected ? '高目标值不能覆盖硬约束违规本次独立校核回执为拒绝fail。' : '本次独立校核回执为通过pass验证通过不等于自动批准执行。',
status: isRejected ? 'blocked' : 'pass', evidenceRef: '#reproduction-and-conclusion',
},
]
edges = [
{ from: 'intent', to: 'candidate', label: '进入候选校核', tone: 'continue', selected: true },
{ from: 'candidate', to: 'clarify', label: '否:候选信息不完整', tone: 'guard', selected: false },
{ from: 'candidate', to: 'verify', label: '是:只校核、不重算方案', tone: 'continue', selected: true },
{ from: 'verify', to: 'verification-guard', label: '验证证据缺失', tone: 'guard', selected: false },
{ from: 'verify', to: 'outcome', label: `校核回执:${observedStatusLabel(observed.validationVerdict)}`, tone: isRejected ? 'guard' : 'success', selected: true },
]
} else {
nodes = [
intent,
{
id: 'recovery-source', kind: 'question', owner: 'platform', title: '正常关闭标记与恢复前基线是否可读取?',
detail: pack.businessContract.baseline, status: 'pass', evidenceRef: '#input-and-provenance',
},
{
id: 'missing-state', kind: 'outcome', owner: 'platform', title: '恢复来源不完整:停止声称恢复成功',
detail: '缺少关闭标记、团队快照或账本时,必须进入人工排查。', status: 'blocked', evidenceRef: '#input-and-provenance',
},
{
id: 'restore', kind: 'action', owner: 'platform', title: '恢复团队、Skill、工具与既有证据',
detail: '该路径不建立、不编译、不求解优化模型。', status: 'pass', evidenceRef: '#solve-process',
},
{
id: 'ledger-check', kind: 'check', owner: 'validator', title: '恢复前后 EvidenceLedger 是否保持幂等?',
detail: compactList(pack.businessContract.constraints, 4), status: 'pass', evidenceRef: '#independent-validation',
},
{
id: 'ledger-guard', kind: 'outcome', owner: 'validator', title: '账本计数或证据变化:拒绝恢复结论',
detail: '发现重复已结算调用或身份漂移时保持阻断。', status: 'blocked', evidenceRef: '#independent-validation',
},
{
id: 'outcome', kind: 'outcome', owner: 'human', title: '恢复完成,不重复已结算求解',
detail: '本次 Run 已通过,且 EvidenceLedger 回执已绑定到这棵树。', status: 'pass', evidenceRef: '#reproduction-and-conclusion',
},
]
edges = [
{ from: 'intent', to: 'recovery-source', label: '进入恢复核对', tone: 'continue', selected: true },
{ from: 'recovery-source', to: 'missing-state', label: '否:恢复来源缺失', tone: 'guard', selected: false },
{ from: 'recovery-source', to: 'restore', label: '是:加载既有状态', tone: 'continue', selected: true },
{ from: 'restore', to: 'ledger-check', label: '读取 EvidenceLedger', tone: 'continue', selected: true },
{ from: 'ledger-check', to: 'ledger-guard', label: '否:计数或身份漂移', tone: 'guard', selected: false },
{ from: 'ledger-check', to: 'outcome', label: '是:无重复求解', tone: 'success', selected: true },
]
}
const tree = {
schemaVersion: 'solver.human-agent-decision-tree/v1', rootNodeId: 'intent', nodes, edges, observed,
evidenceBindings: {
scenarioPackSha256: packSha256,
runId: evidence.runId,
sourceMessagesSha256: evidence.sourceMessagesSha256,
sourceSessionSha256: evidence.sourceSessionSha256,
sourceToolInvocationsSha256: evidence.sourceToolInvocationsSha256,
optimizationSpecSha256: evidence.optimizationSpecSha256,
resultPayloadSha256: evidence.resultPayloadSha256,
validationReportSha256: evidence.validationReportSha256,
},
}
if (!validateDecisionTreeSchema(tree)) {
throw new Error(`decision tree schema validation failed: ${ajv.errorsText(validateDecisionTreeSchema.errors)}`)
}
const nodeIds = new Set(tree.nodes.map((node) => node.id))
if (nodeIds.size !== tree.nodes.length || !nodeIds.has(tree.rootNodeId)) throw new Error('decision tree node IDs invalid')
if (tree.edges.some((edge) => !nodeIds.has(edge.from) || !nodeIds.has(edge.to))) {
throw new Error('decision tree edge refers to an unknown node')
}
const selectedEdges = tree.edges.filter((edge) => edge.selected)
const selectedByFrom = new Map()
for (const edge of selectedEdges) selectedByFrom.set(edge.from, [...(selectedByFrom.get(edge.from) ?? []), edge])
if ([...selectedByFrom.values()].some((outgoing) => outgoing.length !== 1)) {
throw new Error('decision tree path must have exactly one selected outgoing edge per path node')
}
if (tree.nodes.some((node) => node.id !== tree.rootNodeId && !tree.edges.some((edge) => edge.to === node.id))) {
throw new Error('decision tree contains an orphan node')
}
let cursor = tree.rootNodeId
const visited = new Set()
while (selectedByFrom.has(cursor) && !visited.has(cursor)) {
visited.add(cursor)
cursor = selectedByFrom.get(cursor)[0].to
}
if (cursor !== 'outcome' || visited.size !== selectedEdges.length || selectedByFrom.has('outcome')) {
throw new Error('decision tree selected path must be acyclic, complete, and terminate at outcome')
}
return tree
}
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 parsedToolSummaries = session
.filter((event) => event.type === 'tool_use_summary')
.map((event) => ({ event, summary: parseSummaryJson(event) }))
.filter((entry) => entry.summary && typeof entry.summary === 'object' && !Array.isArray(entry.summary))
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 ledger = [...finished].reverse().find((item) => item.tool_name === 'OptimizationEvidenceLedger')
const compile = finished.find((item) => item.tool_name === 'OptimizationCompile')
const solveSummary = solve
? typeof solve.result_provenance?.result_payload_hash === 'string'
? parsedToolSummaries.find(
({ summary }) =>
typeof summary.status === 'string' &&
summary.result_payload_hash === solve.result_provenance?.result_payload_hash
)?.summary ?? null
: null
: null
const validationEntry = validate
? typeof validate.result_provenance?.validation_report_hash === 'string'
? [...parsedToolSummaries]
.reverse()
.find(
({ event, summary }) =>
['pass', 'fail'].includes(summary.verdict) &&
event.data?.metadata?.validation_report_hash === validate.result_provenance?.validation_report_hash
) ?? null
: null
: null
const validation = validationEntry?.summary ?? null
const ledgerSummary = ledger
? [...parsedToolSummaries]
.reverse()
.find(({ summary }) => summary.schema_version === 'solver.optimization-evidence-ledger/v1')?.summary ?? null
: null
if (source.data.status !== 'passed') throw new Error(`${planItem.scenarioId}: decision tree requires an accepted Run`)
const workflowKind = solve ? 'optimization' : validate ? 'validation' : ledger ? 'recovery' : null
if (!workflowKind) throw new Error(`${planItem.scenarioId}: no observed solve, validation, or recovery workflow`)
if (workflowKind !== 'optimization' && compile) {
throw new Error(`${planItem.scenarioId}: ${workflowKind} workflow cannot hide an observed OptimizationCompile step`)
}
if (workflowKind === 'optimization' && (!solveSummary || !validation)) {
throw new Error(`${planItem.scenarioId}: settled solve or validation summary missing`)
}
if (
workflowKind === 'optimization' &&
(!solve.result_provenance?.optimization_spec_hash ||
!solve.result_provenance?.result_payload_hash ||
!validate.result_provenance?.validation_report_hash)
) {
throw new Error(`${planItem.scenarioId}: optimization workflow provenance hashes missing`)
}
if (workflowKind === 'validation' && !validation) {
throw new Error(`${planItem.scenarioId}: observed validation summary missing`)
}
if (workflowKind === 'validation' && !validate.result_provenance?.validation_report_hash) {
throw new Error(`${planItem.scenarioId}: validation workflow report hash missing`)
}
if (workflowKind === 'recovery' && !ledgerSummary) {
throw new Error(`${planItem.scenarioId}: recovery EvidenceLedger summary missing`)
}
if (workflowKind === 'recovery' && !ledger.result_provenance?.result_payload_hash) {
throw new Error(`${planItem.scenarioId}: recovery EvidenceLedger result hash missing`)
}
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 resultStatus =
workflowKind === 'optimization'
? solveSummary.status
: workflowKind === 'validation'
? validation.verdict
: 'recovered'
const validationVerdict = validation?.verdict ?? null
if (pack.oracle.expectedStatuses?.length && !pack.oracle.expectedStatuses.includes(resultStatus)) {
throw new Error(`${planItem.scenarioId}: observed result ${resultStatus} differs from Scenario Pack oracle`)
}
if (
source.data.diagnostics?.observedValidationVerdict != null &&
source.data.diagnostics.observedValidationVerdict !== validationVerdict
) {
throw new Error(`${planItem.scenarioId}: validation summary differs from case-run diagnostics`)
}
const sourceMessagesSha256 = sha256(readFileSync(messagesFile))
const evidence = {
runId: planItem.runId,
conversationId: planItem.conversationId,
sourceMessagesSha256,
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 ?? ledger?.result_provenance?.result_payload_hash ?? null,
validationReportSha256: validate?.result_provenance?.validation_report_hash ?? null,
}
const decisionTree = buildDecisionTree({
pack,
packSha256: sha256(readFileSync(packEntry.file)),
evidence,
workflowKind,
observed: {
workflowKind,
runStatus: source.data.status,
solveStatus: solveSummary?.status ?? null,
validationVerdict,
},
problemFamily,
engineId: solve?.result_provenance?.engine_id ?? 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,
validationVerdict,
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,
decisionTree,
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,
})
cpSync(join(codeRoot, 'decision-tree.schema.json'), join(outputRoot, 'data', 'decision-tree.schema.json'))
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.4.0',
platformCommit: latestPlatformRegression.platform.commit,
agentSkillVersion: latestPlatformRegression.platform.agentSkillVersion,
evidencePlatformCommit: historicalPlatformCommit,
publicReleaseAttestationSha256,
publicEvidenceRoots,
historicalEvidenceContentRootSha256,
latestPlatformRegressionSha256,
latestPlatformRegression,
counts: {
chapters: chapters.length,
decisionTrees: 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',
decisionTrees: '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

@@ -1,75 +0,0 @@
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

@@ -1,143 +0,0 @@
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

@@ -1,257 +0,0 @@
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

@@ -1,100 +0,0 @@
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

@@ -1,388 +0,0 @@
import { existsSync, globSync, readFileSync, statSync, writeFileSync } from 'node:fs'
import { createRequire } from 'node:module'
import { dirname, join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import {
assertPublicText,
canonical,
computePublicEvidenceRoots,
loadPublicReleaseAttestation,
sha256,
} from './public-release-policy.mjs'
const codeRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const require = createRequire(import.meta.url)
const Ajv = require('ajv').default
const ajv = new Ajv({ allErrors: true, strict: true })
const validateDecisionTreeSchema = ajv.compile(
JSON.parse(readFileSync(join(codeRoot, 'decision-tree.schema.json'), 'utf8'))
)
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 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 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.decisionTrees !== 24)
failures.push({ issue: 'decision-tree-count', actual: manifest.counts.decisionTrees })
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?.decisionTrees !== 'pass' ||
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
let decisionTreeCount = 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
const source = caseRunById.get(chapter.evidence.runId)
const sourceMessagesFile = source ? join(source.runDir, 'messages.jsonl') : null
const sourceSessionFile = source ? join(source.runDir, 'sessions', 'session.jsonl') : null
const sourceInvocationsFile = source ? join(source.runDir, 'receipts', 'tool-invocations.jsonl') : null
const sourceInvocations = source ? readJsonLines(sourceInvocationsFile) : []
const sourceSession = source ? readJsonLines(sourceSessionFile) : []
const finishedReceipts = sourceInvocations.filter((item) => item.phase === 'finished' && item.status === 'success')
const finishedToolNames = finishedReceipts.map((item) => item.tool_name)
const parsedSummaries = sourceSession
.filter((event) => event.type === 'tool_use_summary')
.map((event) => {
try {
return { event, summary: JSON.parse(event.data?.summary ?? '') }
} catch {
return null
}
})
.filter((entry) => entry?.summary && typeof entry.summary === 'object' && !Array.isArray(entry.summary))
const solveReceipt = finishedReceipts.find((item) => item.tool_name === 'OptimizationSolve')
const validationReceipt = [...finishedReceipts].reverse().find((item) => item.tool_name === 'OptimizationValidate')
const ledgerReceipt = [...finishedReceipts].reverse().find((item) => item.tool_name === 'OptimizationEvidenceLedger')
const observedSolveSummary = solveReceipt
? typeof solveReceipt.result_provenance?.result_payload_hash === 'string'
? parsedSummaries.find(
({ summary }) => summary.result_payload_hash === solveReceipt.result_provenance?.result_payload_hash
)?.summary ?? null
: null
: null
const observedValidationSummary = validationReceipt
? typeof validationReceipt.result_provenance?.validation_report_hash === 'string'
? [...parsedSummaries]
.reverse()
.find(
({ event, summary }) =>
['pass', 'fail'].includes(summary.verdict) &&
event.data?.metadata?.validation_report_hash === validationReceipt.result_provenance?.validation_report_hash
)?.summary ?? null
: null
: null
const observedLedgerSummary = ledgerReceipt
? [...parsedSummaries]
.reverse()
.find(({ summary }) => summary.schema_version === 'solver.optimization-evidence-ledger/v1')?.summary ?? null
: null
const tree = chapter.decisionTree
const treeNodes = new Map(tree?.nodes?.map((node) => [node.id, node]) ?? [])
const packEntry = packById.get(index.scenarioId)
if (
!validateDecisionTreeSchema(tree) ||
tree?.schemaVersion !== 'solver.human-agent-decision-tree/v1' ||
treeNodes.size !== tree?.nodes?.length ||
!treeNodes.has(tree?.rootNodeId) ||
tree?.edges?.some((edge) => !treeNodes.has(edge.from) || !treeNodes.has(edge.to)) ||
!packEntry ||
tree?.evidenceBindings?.scenarioPackSha256 !== sha256(readFileSync(packEntry.file)) ||
tree?.evidenceBindings?.runId !== chapter.evidence.runId ||
tree?.evidenceBindings?.sourceMessagesSha256 !== chapter.evidence.sourceMessagesSha256 ||
tree?.evidenceBindings?.sourceSessionSha256 !== chapter.evidence.sourceSessionSha256 ||
tree?.evidenceBindings?.sourceToolInvocationsSha256 !== chapter.evidence.sourceToolInvocationsSha256 ||
tree?.evidenceBindings?.optimizationSpecSha256 !== chapter.evidence.optimizationSpecSha256 ||
tree?.evidenceBindings?.resultPayloadSha256 !== chapter.evidence.resultPayloadSha256 ||
tree?.evidenceBindings?.validationReportSha256 !== chapter.evidence.validationReportSha256 ||
!source ||
tree?.evidenceBindings?.sourceMessagesSha256 !== sha256(readFileSync(sourceMessagesFile)) ||
tree?.evidenceBindings?.sourceSessionSha256 !== sha256(readFileSync(sourceSessionFile)) ||
tree?.evidenceBindings?.sourceToolInvocationsSha256 !== sha256(readFileSync(sourceInvocationsFile)) ||
source.data.status !== 'passed' ||
tree?.observed?.runStatus !== source.data.status ||
!['optimization', 'validation', 'recovery'].includes(tree?.observed?.workflowKind) ||
(tree?.observed?.workflowKind === 'optimization' &&
(!finishedToolNames.includes('OptimizationSolve') ||
!finishedToolNames.includes('OptimizationValidate') ||
!observedSolveSummary ||
!observedValidationSummary ||
tree.observed.solveStatus !== observedSolveSummary.status ||
tree.observed.validationVerdict !== observedValidationSummary.verdict ||
!solveReceipt?.result_provenance?.optimization_spec_hash ||
tree.evidenceBindings.optimizationSpecSha256 !== solveReceipt.result_provenance.optimization_spec_hash ||
tree.evidenceBindings.resultPayloadSha256 !== solveReceipt?.result_provenance?.result_payload_hash ||
!validationReceipt?.result_provenance?.validation_report_hash ||
tree.evidenceBindings.validationReportSha256 !== validationReceipt?.result_provenance?.validation_report_hash ||
!tree.evidenceBindings.resultPayloadSha256 ||
chapter.resultStatus !== tree.observed.solveStatus ||
chapter.validationVerdict !== tree.observed.validationVerdict)) ||
(tree?.observed?.workflowKind === 'validation' &&
(finishedToolNames.includes('OptimizationCompile') ||
finishedToolNames.includes('OptimizationSolve') ||
tree.observed.solveStatus !== null ||
!finishedToolNames.includes('OptimizationValidate') ||
!observedValidationSummary ||
tree.observed.validationVerdict !== observedValidationSummary.verdict ||
!validationReceipt?.result_provenance?.validation_report_hash ||
tree.evidenceBindings.validationReportSha256 !== validationReceipt?.result_provenance?.validation_report_hash ||
treeNodes.has('model') ||
chapter.resultStatus !== tree.observed.validationVerdict)) ||
(tree?.observed?.workflowKind === 'recovery' &&
(finishedToolNames.includes('OptimizationCompile') ||
finishedToolNames.includes('OptimizationSolve') ||
finishedToolNames.includes('OptimizationValidate') ||
tree.observed.solveStatus !== null ||
tree.observed.validationVerdict !== null ||
!finishedToolNames.includes('OptimizationEvidenceLedger') ||
!observedLedgerSummary ||
!ledgerReceipt?.result_provenance?.result_payload_hash ||
tree.evidenceBindings.resultPayloadSha256 !== ledgerReceipt?.result_provenance?.result_payload_hash ||
treeNodes.has('model') ||
!tree.evidenceBindings.resultPayloadSha256 ||
chapter.resultStatus !== 'recovered'))
) {
failures.push({ scenarioId: index.scenarioId, issue: 'decision-tree-contract-or-evidence-binding' })
} else {
const selectedEdges = tree.edges.filter((edge) => edge.selected)
const selectedByFrom = new Map()
for (const edge of selectedEdges) selectedByFrom.set(edge.from, [...(selectedByFrom.get(edge.from) ?? []), edge])
let cursor = tree.rootNodeId
const visited = new Set()
while (selectedByFrom.get(cursor)?.length === 1 && !visited.has(cursor)) {
visited.add(cursor)
cursor = selectedByFrom.get(cursor)[0].to
}
if (
cursor !== 'outcome' ||
visited.size !== selectedEdges.length ||
[...selectedByFrom.values()].some((outgoing) => outgoing.length !== 1) ||
tree.nodes.some((node) => node.id !== tree.rootNodeId && !tree.edges.some((edge) => edge.to === node.id))
)
failures.push({ scenarioId: index.scenarioId, issue: 'decision-tree-selected-path' })
else decisionTreeCount += 1
}
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 })
}
}
if (!source) failures.push({ scenarioId: index.scenarioId, issue: 'source-run-missing' })
else {
const raw = readJsonLines(sourceMessagesFile).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 })
if (decisionTreeCount !== 24) failures.push({ issue: 'chapter-decision-tree-sum', actual: decisionTreeCount })
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/decision-tree.schema.json',
'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,
decisionTrees: decisionTreeCount,
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

@@ -1,80 +0,0 @@
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

@@ -1,66 +0,0 @@
#!/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("gates", {}).get("decisionTrees") != "pass":
failures.append({"path": "build.json", "issue": "decision-tree-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

@@ -1,107 +0,0 @@
<!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

@@ -1,583 +0,0 @@
;(() => {
'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.decisionTrees}</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('')
}
const ownerLabel = (owner) =>
({ human: '人类确认', agent: 'Agent 推导', shared: '人 + Agent', validator: '独立校核', platform: '平台执行' })[
owner
] || owner
const kindLabel = (kind) =>
({ context: '决策背景', question: '判断门', action: '执行', check: '校核门', outcome: '结果' })[kind] || kind
function decisionTreePath(tree) {
const nodes = new Map(tree.nodes.map((node) => [node.id, node]))
const selected = new Map(tree.edges.filter((edge) => edge.selected).map((edge) => [edge.from, edge]))
const path = []
const visited = new Set()
let current = tree.rootNodeId
while (nodes.has(current) && !visited.has(current)) {
visited.add(current)
path.push(nodes.get(current))
current = selected.get(current)?.to
}
return path
}
function renderTreeNode(node, branch = false) {
return `<article class="tree-node ${esc(node.status)}${branch ? ' branch' : ''}" data-tree-node="${esc(node.id)}">
<div class="tree-node-top"><span class="tree-owner ${esc(node.owner)}">${esc(ownerLabel(node.owner))}</span><span class="tree-kind">${esc(kindLabel(node.kind))}</span></div>
<h3>${esc(node.title)}</h3><p>${esc(node.detail)}</p>
<a href="${esc(node.evidenceRef)}">查看关联证据 →</a>
</article>`
}
function renderDecisionTree(tree) {
const path = decisionTreePath(tree)
const nodes = new Map(tree.nodes.map((node) => [node.id, node]))
const selectedEdges = new Map(tree.edges.filter((edge) => edge.selected).map((edge) => [edge.from, edge]))
const stages = path
.map((node, index) => {
const nextEdge = selectedEdges.get(node.id)
const alternatives = tree.edges
.filter((edge) => edge.from === node.id && !edge.selected)
.map(
(edge) =>
`<div class="tree-alternative"><span>${esc(edge.label)}</span>${renderTreeNode(nodes.get(edge.to), true)}</div>`
)
.join('')
return `<div class="tree-stage" role="listitem">${renderTreeNode(node)}${alternatives ? `<div class="tree-alternatives">${alternatives}</div>` : ''}${index < path.length - 1 ? `<div class="tree-connector ${esc(nextEdge.tone)}"><span>${esc(nextEdge.label)}</span><i aria-hidden="true">→</i></div>` : ''}</div>`
})
.join('')
return `<div class="decision-tree" data-decision-tree>
<div class="tree-toolbar"><div class="tree-legend" aria-label="决策树职责图例"><span class="human">人类确认</span><span class="agent">Agent 推导</span><span class="validator">独立校核</span><span class="blocked">阻断分支</span></div><div class="tree-actions"><button type="button" data-tree-mode aria-pressed="false">只看实际路径</button><button type="button" data-tree-export>下载 SVG</button></div></div>
<div class="tree-scroll" tabindex="0" aria-label="${esc(`场景决策树:${state.chapter.title}`)}"><div class="tree-flow" role="list">${stages}</div></div>
<div class="tree-proof"><span>当前实际路径以实线连接;旁路展示系统何时必须补问或阻断。</span><code>PACK ${esc(shortHash(tree.evidenceBindings.scenarioPackSha256))} · RUN ${esc(shortHash(tree.evidenceBindings.runId))}</code></div>
</div>`
}
const svgTextLines = (value, max = 18, lines = 3) => {
const chars = [...String(value)]
const result = []
while (chars.length && result.length < lines) result.push(chars.splice(0, max).join(''))
if (chars.length) result[result.length - 1] = `${result[result.length - 1].slice(0, -1)}`
return result
}
function downloadDecisionTreeSvg(c) {
const path = decisionTreePath(c.decisionTree)
const width = Math.max(1200, path.length * 230 + 100)
const height = 420
const cards = path
.map((node, index) => {
const x = 50 + index * 230
const title = svgTextLines(node.title)
.map(
(line, lineIndex) =>
`<text x="${x + 16}" y="${112 + lineIndex * 22}" font-size="15" font-weight="700" fill="#172238">${esc(line)}</text>`
)
.join('')
const detail = svgTextLines(node.detail, 22, 3)
.map(
(line, lineIndex) =>
`<text x="${x + 16}" y="${194 + lineIndex * 18}" font-size="11" fill="#637089">${esc(line)}</text>`
)
.join('')
const connector =
index < path.length - 1
? `<path d="M ${x + 190} 174 H ${x + 222}" stroke="#23b8b2" stroke-width="3"/><path d="M ${x + 216} 168 L ${x + 224} 174 L ${x + 216} 180" fill="none" stroke="#23b8b2" stroke-width="3"/>`
: ''
return `<g><rect x="${x}" y="64" width="190" height="210" rx="16" fill="#fff" stroke="#dce3ed"/><rect x="${x}" y="64" width="190" height="8" rx="4" fill="${node.status === 'blocked' ? '#d95c63' : '#23b8b2'}"/><text x="${x + 16}" y="94" font-size="11" font-weight="700" fill="#477070">${esc(ownerLabel(node.owner))}</text>${title}${detail}${connector}</g>`
})
.join('')
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}"><rect width="100%" height="100%" fill="#eef2f7"/><text x="50" y="35" font-size="22" font-weight="800" fill="#172238">${esc(c.title)} · 人 + Agent 决策树</text>${cards}<text x="50" y="316" font-size="13" font-weight="700" fill="#172238">实际证据路径</text><text x="50" y="342" font-size="12" fill="#637089">场景 ${esc(c.scenarioId)} · ${esc(c.decisionTree.evidenceBindings.scenarioPackSha256)}</text><text x="50" y="370" font-size="12" fill="#637089">此 SVG 由验证书中的结构化决策树即时导出;完整旁路与证据链接请在 Web 页面审阅。</text></svg>`
const url = URL.createObjectURL(new Blob([svg], { type: 'image/svg+xml;charset=utf-8' }))
const link = document.createElement('a')
link.href = url
link.download = `${c.scenarioId.replace(/[^a-z0-9._-]/gi, '-')}-decision-tree.svg`
link.click()
setTimeout(() => URL.revokeObjectURL(url), 0)
}
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.decisionTree.nodes.length} 节点 · 证据绑定</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 decision-tree-section" id="decision-tree"><div class="section-head"><div><h2>人 + Agent 场景决策树</h2><p>从业务确认到交付门禁,展示本次实际路径以及必须补问、拒绝或阻断的旁路。</p></div><span class="evidence-tag">EVIDENCE-BOUND · ${c.decisionTree.nodes.length} NODES</span></div>${renderDecisionTree(c.decisionTree)}</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)
$('#chapter [data-tree-mode]')?.addEventListener('click', (event) => {
const tree = event.currentTarget.closest('[data-decision-tree]')
const pathOnly = tree.classList.toggle('path-only')
event.currentTarget.setAttribute('aria-pressed', String(pathOnly))
event.currentTarget.textContent = pathOnly ? '显示完整分支' : '只看实际路径'
})
$('#chapter [data-tree-export]')?.addEventListener('click', () => downloadDecisionTreeSvg(c))
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

@@ -1,132 +0,0 @@
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

@@ -1,137 +0,0 @@
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 validator = readFileSync(join(root, 'scripts', 'validate-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')
)
const decisionTreeSchema = JSON.parse(readFileSync(join(root, 'decision-tree.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('场景决策树由证据数据驱动并支持完整分支、实际路径和 SVG 导出', () => {
assert.match(builder, /buildDecisionTree/)
assert.match(builder, /solver\.human-agent-decision-tree\/v1/)
assert.match(builder, /scenarioPackSha256/)
assert.match(javascript, /renderDecisionTree/)
assert.match(javascript, /decisionTreePath/)
assert.match(javascript, /data-tree-mode/)
assert.match(javascript, /data-tree-export/)
assert.match(javascript, /downloadDecisionTreeSvg/)
assert.match(css, /\.tree-flow/)
assert.match(css, /\.tree-alternatives/)
assert.match(css, /\.decision-tree\.path-only/)
assert.match(builder, /workflowKind === 'optimization'/)
assert.match(builder, /workflowKind === 'validation'/)
assert.match(builder, /OptimizationEvidenceLedger/)
assert.match(builder, /cannot hide an observed OptimizationCompile step/)
assert.match(builder, /workflowKind !== 'optimization' && compile/)
assert.match(builder, /solveSummary\.status/)
assert.match(builder, /validation\.verdict/)
assert.doesNotMatch(builder, /const resultStatus = pack\.oracle/)
assert.match(builder, /exactly one selected outgoing edge/)
assert.match(validator, /validateDecisionTreeSchema\(tree\)/)
assert.match(validator, /\['optimization', 'validation', 'recovery'\]\.includes/)
assert.match(validator, /source\.data\.status !== 'passed'/)
})
test('决策树 Schema 自描述人类、Agent、校核和证据绑定边界', () => {
assert.equal(decisionTreeSchema.$schema, 'http://json-schema.org/draft-07/schema#')
assert.match(decisionTreeSchema.description, /humans confirm/)
assert.deepEqual(decisionTreeSchema.properties.nodes.items.properties.owner.enum, [
'human',
'agent',
'shared',
'validator',
'platform',
])
assert.match(decisionTreeSchema.properties.evidenceBindings.description, /Immutable bindings/)
for (const field of [
'sourceSessionSha256',
'sourceToolInvocationsSha256',
'resultPayloadSha256',
'validationReportSha256',
]) {
assert.ok(decisionTreeSchema.properties.evidenceBindings.required.includes(field))
assert.ok(decisionTreeSchema.properties.evidenceBindings.properties[field].description)
}
assert.match(decisionTreeSchema.properties.observed.description, /oracle expectations never substitute/i)
})
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)
})