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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
| ----------------------------------------- | ---------------------------------------------------- | ----- |
| [Token 任务炼金炉](./labs/token-forge/) | 把闲置 Token、仓库上下文和目标转换为可验收的开发任务 | Alpha |
| [AI 故障侦探](./labs/incident-detective/) | 在合成事故中练习按证据排障,而不是让 AI 猜根因 | Alpha |
| [SMART / RMA 报告机](./labs/smart-rma/) | 本地解析硬盘信息,分离工具状态、关键指标与未知项 | Alpha |
| [SMART / RMA 报告机](./labs/smart-rma/) | 本地解析并遮蔽硬盘标识,分离关键指标与未知项 | Alpha |

## 产品原则

Expand Down Expand Up @@ -58,6 +58,7 @@ Web 端采用 **Astro + TypeScript + Preact Islands**,首期生成静态 HTML
- Incident Detective 分享卡:遵循 [Score-only 隐私 SVG](./docs/incident-detective-share-card.md)。
- SMART / RMA 合成输入:遵循 [完全合成 Fixture 合同](./docs/smart-rma-synthetic-fixtures.md)。
- SMART / RMA 浏览器解析:遵循 [Parse Result v1 与失败边界](./docs/smart-rma-parser-v1.md)。
- SMART / RMA 本地脱敏:遵循 [预览、Boundary Projection 与 sink 边界](./docs/smart-rma-redaction-v1.md)。
- 处理用户输入:先读 [隐私模型](./docs/privacy-model.md) 和 [脱敏包](./packages/redaction/README.md)。
- 发布站点:遵循 [Cloudflare Workers 部署](./docs/cloudflare-deployment.md)。

Expand Down Expand Up @@ -91,5 +92,6 @@ Attempt v1。P2-004 已加入五维 100 分确定性评分、逐条判定和改
Score-only 的确定性 SVG 下载,Incident Detective MVP 六个任务全部闭环。P3-001 已建立
7 份完全合成的 ATA、NVMe 与未知协议 `smartctl` 输入及版本化索引,覆盖健康、预警、危险、
缺失字段、厂商扩展、SMART 不可用和冲突信号。P3-002 已上线浏览器端只读解析工作台和
Parse Result v1:原文只在当前页面内存中处理,输出排除原文与设备标识,且明确不做健康或
保修判断;下一步是 P3-003 完整输入脱敏。
Parse Result v1。P3-003 已增加序列号、WWN、主机名、IP 与常见 Secret 的本地脱敏预览,
以及不含自由文本的 Boundary Projection v1;URL、日志和 Analytics 进一步收窄为固定元数据,
AI 与导出尚未启用。下一步是 P3-004 确定性健康分类,现有结果仍不构成厂商保修判断。
15 changes: 10 additions & 5 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,12 @@
- 验收:结构化提取版本、协议、设备类别、SMART 支持、工具报告状态、ATA/NVMe 指标、稳定
信号和缺失字段;64 KiB 上限与错误失败关闭;不上传、持久化或回显原文和设备标识;不做
健康分类、AI 解释或保修判断。
- [ ] **P3-003 [M] 实现序列号、WWN、主机名和 IP 脱敏**
- [x] **P3-003 [M] 实现序列号、WWN、主机名和 IP 脱敏**
- 产物:smartctl 标识格式扩展、本地脱敏预览、Boundary Projection v1 Schema、五类 sink
最小投影和工作台隐私报告。
- 验收:覆盖序列号/WWN/主机名/IPv4/IPv6 的重复、大小写与分隔符变体;原文与自由文本不
进入 URL、日志、Analytics、AI 请求或导出;Secret 本地遮蔽;错误不回显;无网络、存储
和日志副作用。
- [ ] **P3-004 [M] 实现规则化健康指标与未知状态**
- [ ] **P3-005 [M] 实现 AI 通俗解释**
- [ ] **P3-006 [M] 生成中文摘要与英文 RMA Markdown**
Expand All @@ -125,7 +130,7 @@

## 推荐下一项任务

接下来做 **P3-003**:在浏览器内对用户粘贴的真实 SMART 文本实现序列号、WWN、主机名和
IP 完整脱敏,并为后续导出与 AI 边界提供只含允许字段的结构化投影。必须覆盖大小上限、
不同 smartctl 格式、重复标识、大小写与分隔符变体,以及 URL、日志、Analytics、导出和 AI
请求不含原文的失败测试。每次仍只处理一个任务;健康分类由 P3-004 单独实现。
接下来做 **P3-004**:基于 Parse Result v1 和 Boundary Projection v1 实现确定性的健康、
注意、危险和未知分类。规则必须处理总体 PASSED 与关键计数非零的冲突,未知厂商属性不得
强行判故障,SMART 不可用或关键字段缺失必须显式为未知;每条结论需指向输入信号和规则,
不得调用 AI 或声称厂商必须保修。每次仍只处理一个任务。
18 changes: 16 additions & 2 deletions apps/web/scripts/verify-static-output.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,20 @@ const checks = [
"本阶段不调用",
"不写入 URL",
"Local Storage",
"不包含序列号、WWN、型号原文和原始文本",
"序列号、WWN、主机名和 IP",
"无自由文本的允许字段投影",
].every((label) => smartRmaHtml.includes(label)),
],
[
"SMART / RMA local redaction preview",
[
"本地解析并脱敏",
"本地脱敏预览",
"主机名 / 域名",
"查看脱敏后的本地文本",
"[REDACTED:SERIAL_NUMBER]",
"[REDACTED:WWN]",
"Boundary Projection v1",
].every((label) => smartRmaHtml.includes(label)),
],
[
Expand Down Expand Up @@ -323,7 +336,8 @@ const checks = [
styles.includes(".smart-rma-form textarea") &&
styles.includes(".smart-rma-summary-grid") &&
styles.includes(".smart-rma-table-scroll") &&
styles.includes(".smart-rma-nvme-grid"),
styles.includes(".smart-rma-nvme-grid") &&
styles.includes(".smart-rma-redaction-counts"),
],
];

Expand Down
124 changes: 106 additions & 18 deletions apps/web/src/components/SmartRmaWorkbench.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { useMemo, useState } from "preact/hooks";

import {
type SmartRmaRedactionPreview,
SmartRmaRedactionError,
redactSmartctlText,
} from "../lib/smart-rma-redaction";
import {
type SmartRmaDeviceKind,
type SmartRmaMissingField,
Expand Down Expand Up @@ -84,7 +89,7 @@ const initialStatus: WorkbenchStatus = {
tone: "info",
title: "合成样例已就绪",
message:
"选择一个完全合成的 smartctl 样例,或粘贴文本后在当前浏览器标签页本地解析。",
"选择一个完全合成的 smartctl 样例,或粘贴文本后在当前浏览器标签页解析并脱敏。",
};

const formatMetric = (value: number | null, suffix = ""): string =>
Expand All @@ -101,8 +106,13 @@ export default function SmartRmaWorkbench({

const [selectedSampleId, setSelectedSampleId] = useState(initialSample.id);
const [rawText, setRawText] = useState(initialSample.raw);
const [result, setResult] = useState<SmartRmaParseResult | null>(() =>
parseSmartctlText(initialSample.raw),
const [redactionPreview, setRedactionPreview] =
useState<SmartRmaRedactionPreview | null>(() =>
redactSmartctlText(initialSample.raw),
);
const [result, setResult] = useState<SmartRmaParseResult | null>(
() =>
redactionPreview?.parse_result ?? parseSmartctlText(initialSample.raw),
);
const [status, setStatus] = useState<WorkbenchStatus>(initialStatus);
const selectedSample = useMemo(
Expand All @@ -113,43 +123,60 @@ export default function SmartRmaWorkbench({
const loadSelectedSample = (): void => {
setRawText(selectedSample.raw);
setResult(null);
setRedactionPreview(null);
setStatus({
tone: "info",
title: "合成样例已载入",
message: `${selectedSample.label} 已放入输入框;点击“本地解析”查看结构化证据。`,
message: `${selectedSample.label} 已放入输入框;点击“本地解析并脱敏”查看结果。`,
});
};

const parseLocally = (): void => {
try {
const parsed = parseSmartctlText(rawText);
setResult(parsed);
const preview = redactSmartctlText(rawText);
setRedactionPreview(preview);
setResult(preview.parse_result);
setStatus({
tone: "ready",
title: "本地解析完成",
title: "本地解析与脱敏完成",
message:
"只展示结构化字段和解析信号;原始文本、型号原文、序列号与 WWN 都不进入结果。",
"序列号、WWN、主机名、IP 与其他常见敏感值已遮蔽;跨边界只使用无自由文本的结构化投影。",
});
} catch (error) {
setResult(null);
setRedactionPreview(null);
setStatus({
tone: "error",
title: "无法解析",
title: "无法解析或脱敏",
message:
error instanceof SmartRmaRedactionError ||
error instanceof SmartRmaParserError
? error.message
: "输入没有通过本地解析合同,请检查是否为 smartctl 文本。",
: "输入没有通过本地解析与脱敏合同,请检查是否为 smartctl 文本。",
});
}
};

const resetWorkbench = (): void => {
const preview = redactSmartctlText(initialSample.raw);
setSelectedSampleId(initialSample.id);
setRawText(initialSample.raw);
setResult(parseSmartctlText(initialSample.raw));
setRedactionPreview(preview);
setResult(preview.parse_result);
setStatus(initialStatus);
};

const updateRawText = (value: string): void => {
setRawText(value);
setResult(null);
setRedactionPreview(null);
setStatus({
tone: "info",
title: "输入已改变",
message: "点击“本地解析并脱敏”生成与当前文本对应的新结果。",
});
};

return (
<div class="smart-rma-shell">
<section
Expand Down Expand Up @@ -205,8 +232,7 @@ export default function SmartRmaWorkbench({
<label for="smartctl-text">
<span>smartctl 文本</span>
<small id="smartctl-text-help">
当前版本不导出原文;P3-003
完成前,请不要把真实文本复制到其他位置。
当前版本不导出原文;解析时会同时生成仅供本页查看的脱敏预览。
</small>
</label>
<textarea
Expand All @@ -215,11 +241,11 @@ export default function SmartRmaWorkbench({
aria-describedby="smartctl-text-help"
value={rawText}
spellcheck={false}
onInput={(event) => setRawText(event.currentTarget.value)}
onInput={(event) => updateRawText(event.currentTarget.value)}
/>
<div class="smart-rma-actions">
<button class="button button--primary" type="submit">
本地解析
本地解析并脱敏
</button>
<button class="button button--secondary" type="reset">
恢复默认样例
Expand All @@ -231,14 +257,75 @@ export default function SmartRmaWorkbench({
<strong>当前隐私边界</strong>
<p>
原始文本只存在于这个页面的内存,不写入 URL、Local
Storage、Analytics、日志或 AI
请求。解析结果也不包含序列号、WWN、型号原文和原始文本。
Storage、Analytics、日志、导出或 AI 请求。序列号、WWN、主机名和 IP
会在本地预览中遮蔽;未来跨边界能力只能读取无自由文本的允许字段投影。
</p>
</aside>

<StatusNotice tone={status.tone} title={status.title}>
{status.message}
</StatusNotice>

{redactionPreview && (
<section
class="smart-rma-redaction"
aria-labelledby="smart-rma-redaction-heading"
>
<div class="smart-rma-detail-heading">
<div>
<p class="section-kicker">LOCAL REDACTION</p>
<h3 id="smart-rma-redaction-heading">本地脱敏预览</h3>
</div>
<p>
共遮蔽 {redactionPreview.projection.privacy.redactions_total}{" "}
处;报告只保留类型和数量。
</p>
</div>

<dl class="smart-rma-redaction-counts">
<div>
<dt>序列号</dt>
<dd>
{redactionPreview.projection.privacy.counts.serial_number}
</dd>
</div>
<div>
<dt>WWN</dt>
<dd>{redactionPreview.projection.privacy.counts.wwn}</dd>
</div>
<div>
<dt>主机名 / 域名</dt>
<dd>{redactionPreview.projection.privacy.counts.domain}</dd>
</div>
<div>
<dt>IP</dt>
<dd>{redactionPreview.projection.privacy.counts.ip}</dd>
</div>
<div>
<dt>邮箱</dt>
<dd>{redactionPreview.projection.privacy.counts.email}</dd>
</div>
<div>
<dt>Secret</dt>
<dd>
{redactionPreview.projection.privacy.counts.authorization +
redactionPreview.projection.privacy.counts.cookie +
redactionPreview.projection.privacy.counts.token}
</dd>
</div>
</dl>

<details>
<summary>查看脱敏后的本地文本</summary>
<pre>{redactionPreview.redacted_text}</pre>
</details>
<p class="smart-rma-boundary-note">
AI
与导出不会接收这段文本,只能使用协议、计数、枚举和受支持指标组成的
Boundary Projection v1。
</p>
</section>
)}
</section>

<section
Expand Down Expand Up @@ -406,7 +493,8 @@ export default function SmartRmaWorkbench({
</>
) : (
<p class="smart-rma-empty">
尚无解析结果。载入合成样例或粘贴 smartctl 文本后,点击“本地解析”。
尚无解析结果。载入合成样例或粘贴 smartctl
文本后,点击“本地解析并脱敏”。
</p>
)}

Expand Down
Loading