feat: 数据删除机制(purge 物理清空 + 报告页隐私区块 + 删后回同意门)+ 测试桩补 purge

This commit is contained in:
Jayway Liu
2026-10-04 07:42:47 +08:00
parent ade1f719e1
commit e022faf975
14 changed files with 330 additions and 14 deletions
+1
View File
@@ -50,6 +50,7 @@ class MemoryStore implements EventStore {
events: StoryEvent[] = [];
async list(sessionId?: string) { return this.events.filter((e) => !sessionId || e.sessionId === sessionId); }
async append(e: StoryEvent) { this.events.push(e); }
async purge() { this.events = []; }
}
class FixedJev extends MockJevClient {
dimension: ProfileDimension;
+3
View File
@@ -34,6 +34,9 @@ class MemoryStore implements EventStore {
async list(sessionId?: string) {
return sessionId ? this.events.filter((e) => e.sessionId === sessionId) : [...this.events];
}
async purge() {
this.events = [];
}
}
const key = loadApiKey();
+31 -1
View File
@@ -7,7 +7,9 @@
* 5. EventStore 各实现(含 SqliteEventStore)的 append-only 性质(接口层面断言无 update/delete;
* SQLite 实连只能在 Tauri 运行时内进行,Node 下不实例化)。
* 6. 同意门(ADR-0004):consent 事件存证、幂等、跨会话跳过;
* 7. 条件文本:flag 推导(附录 A)、【若】标记零泄漏、差分显隐、条件不满足节点跳过无死路。
* 7. 条件文本:flag 推导(附录 A)、【若】标记零泄漏、差分显隐、条件不满足节点跳过无死路;
* 8. 删除全部个人数据(ADR-0004 数据删除权):purge 后事件列表为空、进度清空、
* consent 存证删除后回到未同意态、可重新同意并继续走剧情。
* 运行:pnpm verify
*/
import { readFileSync } from 'node:fs';
@@ -53,6 +55,9 @@ class MemoryStore implements EventStore {
async list(sessionId?: string) {
return sessionId ? this.events.filter((e) => e.sessionId === sessionId) : [...this.events];
}
async purge() {
this.events = [];
}
}
const chapters = [1, 2, 3].map(
@@ -550,6 +555,31 @@ for (const mode of ['farewell_done', 'farewell_partial', 'exit_room'] as const)
check(restored.c3_02_mode === mode && restored.c3_02_fantasy_density === e.state.flags.c3_02_fantasy_density, `问心之室 ${mode}:恢复后叙事状态一致`);
}
// --- 14. 删除全部个人数据(ADR-0004 数据删除权):purge 整库物理清空 + 进度清空 + 回到未同意态 ---
{
const s = new MemoryStore();
const progress = new MemoryProgressStore();
const eng = new StoryEngine(chapters, s, new MockJevClient(), undefined, progress);
await eng.grantConsent();
await walkTo(eng, 'c1-d5'); // 途经 C1-01 身份点、C1-02 讲述点、C1-03 选择点
check((await s.list()).length > 0 && progress.load() !== null, '删除前:事件流与进度快照均非空');
await s.purge();
progress.clear();
check((await s.list()).length === 0, 'purge 后事件列表为空(全部事件物理删除)');
check(progress.load() === null, 'clear 后进度快照为空');
check(!(await eng.hasConsent()), 'purge 后 consent 存证一并删除 → 回到未同意状态(同意门重新出现)');
await s.purge();
check((await s.list()).length === 0, 'purge 幂等:重复清空不报错且仍为空');
// 删除后可重新同意并正常继续走剧情(存储清空后仍可 append,事件不串旧轨迹)
await eng.grantConsent();
check(s.events.filter((e) => e.kind === 'consent').length === 1, '重新同意:新 consent 存证落库(只存证一次)');
await walkTo(eng, 'c1-05'); // 从 c1-d5 出发途经 C1-04 选择点
check(
s.events.some((e) => e.kind === 'choice') && !s.events.some((e) => e.kind === 'profile'),
'删除后可正常继续走剧情并落新事件,旧轨迹事件不残留(无已删除的 profile 事件)',
);
}
if (failures > 0) {
// 主线失败先退出,避免掩盖原有回归。
console.error(`\n${failures} 项校验失败`);