feat: 数据删除机制(purge 物理清空 + 报告页隐私区块 + 删后回同意门)+ 测试桩补 purge
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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
@@ -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} 项校验失败`);
|
||||
|
||||
Reference in New Issue
Block a user